From ed492661bbcf2e93e8d58148c3dae9913f6d81da Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Tue, 5 Nov 2013 19:29:55 -0600 Subject: [PATCH 01/20] test: put each arg in a separate string Each arg to docker run should be placed in a separate string. Otherwise, when starting the command via exec.Cmd, the command is interpreted as "echo test", which can't be found. Upstream-commit: 30ea0bebce340dfc257b5b45835234cb921f3a48 Component: engine --- components/engine/integration/server_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/integration/server_test.go b/components/engine/integration/server_test.go index 6c61bedafb..b19c2e1a91 100644 --- a/components/engine/integration/server_test.go +++ b/components/engine/integration/server_test.go @@ -109,7 +109,7 @@ func TestCreateRmVolumes(t *testing.T) { srv := mkServerFromEngine(eng, t) defer mkRuntimeFromEngine(eng, t).Nuke() - config, hostConfig, _, err := docker.ParseRun([]string{"-v", "/srv", unitTestImageID, "echo test"}, nil) + config, hostConfig, _, err := docker.ParseRun([]string{"-v", "/srv", unitTestImageID, "echo", "test"}, nil) if err != nil { t.Fatal(err) } @@ -240,7 +240,7 @@ func TestRmi(t *testing.T) { t.Fatal(err) } - config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil) + config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "echo", "test"}, nil) if err != nil { t.Fatal(err) } From 4527e7358654d6328968731b2590e8fc761bc4a0 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Mon, 11 Nov 2013 21:57:29 -0600 Subject: [PATCH 02/20] test: fix TestCreateStartRestartStopStartKillRm cat needs stdin opened, otherwise it dies immediately. Upstream-commit: baa687bed2f1f9ee6e44c70d95baad8757ac529c Component: engine --- components/engine/integration/server_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/integration/server_test.go b/components/engine/integration/server_test.go index b19c2e1a91..8cd3e30fdf 100644 --- a/components/engine/integration/server_test.go +++ b/components/engine/integration/server_test.go @@ -164,7 +164,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { srv := mkServerFromEngine(eng, t) defer mkRuntimeFromEngine(eng, t).Nuke() - config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "/bin/cat"}, nil) + config, hostConfig, _, err := docker.ParseRun([]string{"-i", unitTestImageID, "/bin/cat"}, nil) if err != nil { t.Fatal(err) } From b0f8db9d1bc9eb3c5ff3e21ac60cb390d0939d5d Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Wed, 6 Nov 2013 09:57:43 -0600 Subject: [PATCH 03/20] test: skip TestCreate on Fedora due to lxc utils bug In the dind environment running on a Fedora host, the lxc utils get confused by the /sys/fs/cgroup/cpuacct,cpu cgroup mount and lxc-start fails trying to access the wrong cgroup directory. Upstream-commit: 72d02ecdde2ba6f39739a0a942f2c4a057b4d45f Component: engine --- components/engine/integration/container_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/engine/integration/container_test.go b/components/engine/integration/container_test.go index 3658d9d4dc..b602488006 100644 --- a/components/engine/integration/container_test.go +++ b/components/engine/integration/container_test.go @@ -330,6 +330,11 @@ func TestCommitRun(t *testing.T) { } func TestStart(t *testing.T) { + _, err1 := os.Stat("/sys/fs/cgroup/cpuacct,cpu") + _, err2 := os.Stat("/sys/fs/cgroup/cpu,cpuacct") + if err1 == nil || err2 == nil { + t.Skip("Fixme. Setting cpu cgroup shares doesn't work in dind on a Fedora host. The lxc utils are confused by the cpu,cpuacct mount.") + } runtime := mkRuntime(t) defer nuke(runtime) container, _, _ := mkContainer(runtime, []string{"-m", "33554432", "-c", "1000", "-i", "_", "/bin/cat"}, t) From 0c60a3c3a04a043e19532746281bc35168c6d6d1 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Tue, 12 Nov 2013 14:16:51 -0600 Subject: [PATCH 04/20] test: 2 second timeout (not 2000) Upstream-commit: fe302fbfd26fc7db5d751d4bec8a0bd4ce6030a4 Component: engine --- components/engine/integration/commands_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/integration/commands_test.go b/components/engine/integration/commands_test.go index ab186f4a2c..440d8e5469 100644 --- a/components/engine/integration/commands_test.go +++ b/components/engine/integration/commands_test.go @@ -329,7 +329,7 @@ func TestRunDisconnectTty(t *testing.T) { // Client disconnect after run -i should keep stdin out in TTY mode container := globalRuntime.List()[0] - setTimeout(t, "Read/Write assertion timed out", 2000*time.Second, func() { + setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() { if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil { t.Fatal(err) } From ef3400502cdcaa9709392e38d42131a203a29564 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Tue, 12 Nov 2013 10:21:02 -0600 Subject: [PATCH 05/20] test: fix TestRmi race condition Upstream-commit: fef41ef7bf83ed04c7df8e0247e60c0d495eefdc Component: engine --- components/engine/integration/server_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/components/engine/integration/server_test.go b/components/engine/integration/server_test.go index 8cd3e30fdf..494e23fef3 100644 --- a/components/engine/integration/server_test.go +++ b/components/engine/integration/server_test.go @@ -256,6 +256,10 @@ func TestRmi(t *testing.T) { t.Fatal(err) } + if _, err := srv.ContainerWait(containerID); err != nil { + t.Fatal(err) + } + imageID, err := srv.ContainerCommit(containerID, "test", "", "", "", nil) if err != nil { t.Fatal(err) @@ -277,6 +281,10 @@ func TestRmi(t *testing.T) { t.Fatal(err) } + if _, err := srv.ContainerWait(containerID); err != nil { + t.Fatal(err) + } + _, err = srv.ContainerCommit(containerID, "test", "", "", "", nil) if err != nil { t.Fatal(err) From c4cad6571f72603b5b68a7326c3db0082301265c Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 8 Nov 2013 11:08:18 -0600 Subject: [PATCH 06/20] test: remove extra args in TestExitCode The extra blank argument isn't needed and confuses libvirt. Upstream-commit: 4b80ec9aae2f43e831de64f3746c7838252e9203 Component: engine --- components/engine/integration/container_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/integration/container_test.go b/components/engine/integration/container_test.go index b602488006..a8c21ef1ea 100644 --- a/components/engine/integration/container_test.go +++ b/components/engine/integration/container_test.go @@ -568,7 +568,7 @@ func TestExitCode(t *testing.T) { trueContainer, _, err := runtime.Create(&docker.Config{ Image: GetTestImage(runtime).ID, - Cmd: []string{"/bin/true", ""}, + Cmd: []string{"/bin/true"}, }, "") if err != nil { t.Fatal(err) @@ -583,7 +583,7 @@ func TestExitCode(t *testing.T) { falseContainer, _, err := runtime.Create(&docker.Config{ Image: GetTestImage(runtime).ID, - Cmd: []string{"/bin/false", ""}, + Cmd: []string{"/bin/false"}, }, "") if err != nil { t.Fatal(err) From d8912e66aae3e5f26fca6cc5e3044773e9f6af7e Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 19 Nov 2013 11:02:10 -0800 Subject: [PATCH 07/20] speedup docker ps Upstream-commit: 145c2008ae3bbd8bca6e11bac93c6c0098a3ba42 Component: engine --- components/engine/server.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/components/engine/server.go b/components/engine/server.go index 79ead96a62..feda5088cd 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -471,6 +471,12 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API var displayed int out := []APIContainers{} + names := map[string][]string{} + srv.runtime.containerGraph.Walk("/", func(p string, e *gograph.Entity) error { + names[e.ID()] = append(names[e.ID()], p) + return nil + }, -1) + for _, container := range srv.runtime.List() { if !container.State.Running && !all && n == -1 && since == "" && before == "" { continue @@ -491,25 +497,17 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API break } displayed++ - c := createAPIContainer(container, size, srv.runtime) + c := createAPIContainer(names[container.ID], container, size, srv.runtime) out = append(out, c) } return out } -func createAPIContainer(container *Container, size bool, runtime *Runtime) APIContainers { +func createAPIContainer(names []string, container *Container, size bool, runtime *Runtime) APIContainers { c := APIContainers{ ID: container.ID, } - names := []string{} - runtime.containerGraph.Walk("/", func(p string, e *gograph.Entity) error { - if e.ID() == container.ID { - names = append(names, p) - } - return nil - }, -1) c.Names = names - c.Image = runtime.repositories.ImageName(container.Image) c.Command = fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) c.Created = container.Created.Unix() From dcb211eeb01b556b6b33c24899891649bd7c5ae0 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 18 Nov 2013 17:05:05 -0800 Subject: [PATCH 08/20] improve tests on the engine Upstream-commit: 8e5ab5bfca2a7077f4e85096808c8f309cf692df Component: engine --- components/engine/engine/engine_test.go | 55 +++++++++++ components/engine/engine/env_test.go | 94 +++++++++++++++++++ .../engine/{utils.go => helpers_test.go} | 4 - components/engine/engine/job.go | 6 +- 4 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 components/engine/engine/engine_test.go rename components/engine/engine/{utils.go => helpers_test.go} (92%) diff --git a/components/engine/engine/engine_test.go b/components/engine/engine/engine_test.go new file mode 100644 index 0000000000..fdc0b0ec7f --- /dev/null +++ b/components/engine/engine/engine_test.go @@ -0,0 +1,55 @@ +package engine + +import ( + "testing" +) + +func TestRegister(t *testing.T) { + if err := Register("dummy1", nil); err != nil { + t.Fatal(err) + } + + if err := Register("dummy1", nil); err == nil { + t.Fatalf("Expecting error, got none") + } + + eng := newTestEngine(t) + + //Should fail because globan handlers are copied + //at the engine creation + if err := eng.Register("dummy1", nil); err == nil { + t.Fatalf("Expecting error, got none") + } + + if err := eng.Register("dummy2", nil); err != nil { + t.Fatal(err) + } + + if err := eng.Register("dummy2", nil); err == nil { + t.Fatalf("Expecting error, got none") + } +} + +func TestJob(t *testing.T) { + eng := newTestEngine(t) + job1 := eng.Job("dummy1", "--level=awesome") + + if job1.handler != nil { + t.Fatalf("job1.handler should be empty") + } + + h := func(j *Job) string { + return j.Name + } + + eng.Register("dummy2", h) + job2 := eng.Job("dummy2", "--level=awesome") + + if job2.handler == nil { + t.Fatalf("job2.handler shouldn't be nil") + } + + if job2.handler(job2) != job2.Name { + t.Fatalf("handler dummy2 was not found in job2") + } +} diff --git a/components/engine/engine/env_test.go b/components/engine/engine/env_test.go index 3acbddad77..343da31cec 100644 --- a/components/engine/engine/env_test.go +++ b/components/engine/engine/env_test.go @@ -23,7 +23,101 @@ func TestSetenv(t *testing.T) { if val := job.Getenv("foo"); val != "bar" { t.Fatalf("Getenv returns incorrect value: %s", val) } + + job.Setenv("bar", "") + if val := job.Getenv("bar"); val != "" { + t.Fatalf("Getenv returns incorrect value: %s", val) + } if val := job.Getenv("nonexistent"); val != "" { t.Fatalf("Getenv returns incorrect value: %s", val) } } + +func TestSetenvBool(t *testing.T) { + job := mkJob(t, "dummy") + job.SetenvBool("foo", true) + if val := job.GetenvBool("foo"); !val { + t.Fatalf("GetenvBool returns incorrect value: %b", val) + } + + job.SetenvBool("bar", false) + if val := job.GetenvBool("bar"); val { + t.Fatalf("GetenvBool returns incorrect value: %b", val) + } + + if val := job.GetenvBool("nonexistent"); val { + t.Fatalf("GetenvBool returns incorrect value: %b", val) + } +} + +func TestSetenvInt(t *testing.T) { + job := mkJob(t, "dummy") + + job.SetenvInt("foo", -42) + if val := job.GetenvInt("foo"); val != -42 { + t.Fatalf("GetenvInt returns incorrect value: %d", val) + } + + job.SetenvInt("bar", 42) + if val := job.GetenvInt("bar"); val != 42 { + t.Fatalf("GetenvInt returns incorrect value: %d", val) + } + if val := job.GetenvInt("nonexistent"); val != -1 { + t.Fatalf("GetenvInt returns incorrect value: %d", val) + } +} + +func TestSetenvList(t *testing.T) { + job := mkJob(t, "dummy") + + job.SetenvList("foo", []string{"bar"}) + if val := job.GetenvList("foo"); len(val) != 1 || val[0] != "bar" { + t.Fatalf("GetenvList returns incorrect value: %v", val) + } + + job.SetenvList("bar", nil) + if val := job.GetenvList("bar"); val != nil { + t.Fatalf("GetenvList returns incorrect value: %v", val) + } + if val := job.GetenvList("nonexistent"); val != nil { + t.Fatalf("GetenvList returns incorrect value: %v", val) + } +} + +func TestImportEnv(t *testing.T) { + type dummy struct { + DummyInt int + DummyStringArray []string + } + + job := mkJob(t, "dummy") + if err := job.ImportEnv(&dummy{42, []string{"foo", "bar"}}); err != nil { + t.Fatal(err) + } + + dmy := dummy{} + if err := job.ExportEnv(&dmy); err != nil { + t.Fatal(err) + } + + if dmy.DummyInt != 42 { + t.Fatalf("Expected 42, got %d", dmy.DummyInt) + } + + if len(dmy.DummyStringArray) != 2 || dmy.DummyStringArray[0] != "foo" || dmy.DummyStringArray[1] != "bar" { + t.Fatalf("Expected {foo, bar}, got %v", dmy.DummyStringArray) + } + +} + +func TestEnviron(t *testing.T) { + job := mkJob(t, "dummy") + job.Setenv("foo", "bar") + val, exists := job.Environ()["foo"] + if !exists { + t.Fatalf("foo not found in the environ") + } + if val != "bar" { + t.Fatalf("bar not found in the environ") + } +} diff --git a/components/engine/engine/utils.go b/components/engine/engine/helpers_test.go similarity index 92% rename from components/engine/engine/utils.go rename to components/engine/engine/helpers_test.go index 33b1269aca..5b1c0baf60 100644 --- a/components/engine/engine/utils.go +++ b/components/engine/engine/helpers_test.go @@ -11,10 +11,6 @@ import ( var globalTestID string -func init() { - Register("dummy", func(job *Job) string { return "" }) -} - func newTestEngine(t *testing.T) *Engine { // Use the caller function name as a prefix. // This helps trace temp directories back to their test. diff --git a/components/engine/engine/job.go b/components/engine/engine/job.go index 3ccaa8d1a0..365c94e06b 100644 --- a/components/engine/engine/job.go +++ b/components/engine/engine/job.go @@ -205,8 +205,12 @@ func (job *Job) SetenvInt(key string, value int64) { job.Setenv(key, fmt.Sprintf("%d", value)) } +// Returns nil if key not found func (job *Job) GetenvList(key string) []string { sval := job.Getenv(key) + if sval == "" { + return nil + } l := make([]string, 0, 1) if err := json.Unmarshal([]byte(sval), &l); err != nil { l = append(l, sval) @@ -234,7 +238,7 @@ func (job *Job) Setenv(key, value string) { // DecodeEnv decodes `src` as a json dictionary, and adds // each decoded key-value pair to the environment. // -// If `text` cannot be decoded as a json dictionary, an error +// If `src` cannot be decoded as a json dictionary, an error // is returned. func (job *Job) DecodeEnv(src io.Reader) error { m := make(map[string]interface{}) From 4af17f4fede3b48ae9c590b8377ffd9ca62db371 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 19 Nov 2013 12:41:29 -0800 Subject: [PATCH 09/20] rename file to remove testing flags from docker binary Upstream-commit: 66beafa9f3ee72c26773158c7102cb727613ac65 Component: engine --- components/engine/{api_unit_tests.go => api_unit_test.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename components/engine/{api_unit_tests.go => api_unit_test.go} (100%) diff --git a/components/engine/api_unit_tests.go b/components/engine/api_unit_test.go similarity index 100% rename from components/engine/api_unit_tests.go rename to components/engine/api_unit_test.go From 56750f8076cf3e0a5ead21c903e150861d6cb862 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 19 Nov 2013 19:25:54 -0800 Subject: [PATCH 10/20] Pip python deps to a specific version Upstream-commit: 2cccbbdadd8ee7e28824c244e48acebfca96d78a Component: engine --- components/engine/Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/engine/Dockerfile b/components/engine/Dockerfile index 559017507e..4414a735dd 100644 --- a/components/engine/Dockerfile +++ b/components/engine/Dockerfile @@ -46,10 +46,9 @@ run apt-get install -y -q ruby1.9.3 rubygems libffi-dev run gem install --no-rdoc --no-ri fpm run apt-get install -y -q reprepro dpkg-sig -# Install s3cmd 1.0.1 (earlier versions don't support env variables in the config) run apt-get install -y -q python-pip -run pip install s3cmd -run pip install python-magic +run pip install s3cmd==1.1.0-beta3 +run pip install python-magic==0.4.6 run /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY\n' > /.s3cfg # Runtime dependencies From 390195822fe784687ce03cb2ecb6f1e60c8be585 Mon Sep 17 00:00:00 2001 From: "Frederick F. Kautz IV" Date: Mon, 2 Sep 2013 09:06:17 -0700 Subject: [PATCH 11/20] Offline Image Transfers #1155 Upstream-commit: 7eaa59f626fa9e9b3bf61d3d7eff0fb70b3d011a Component: engine --- components/engine/api.go | 19 +++ components/engine/commands.go | 38 +++++ .../engine/docs/sources/commandline/cli.rst | 22 +++ components/engine/server.go | 149 ++++++++++++++++++ 4 files changed, 228 insertions(+) diff --git a/components/engine/api.go b/components/engine/api.go index 93322fd8aa..cf46ee316e 100644 --- a/components/engine/api.go +++ b/components/engine/api.go @@ -534,6 +534,23 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http return nil } +func getImagesGet(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + name := vars["name"] + err := srv.ImageExport(name, w) + if err != nil { + return err + } + return nil +} + +func postImagesLoad(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + err := srv.ImageLoad(r.Body) + if err != nil { + return err + } + return nil +} + func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return nil @@ -1036,6 +1053,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { "/images/json": getImagesJSON, "/images/viz": getImagesViz, "/images/search": getImagesSearch, + "/images/{name:.*}/get": getImagesGet, "/images/{name:.*}/history": getImagesHistory, "/images/{name:.*}/json": getImagesByName, "/containers/ps": getContainersJSON, @@ -1052,6 +1070,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { "/build": postBuild, "/images/create": postImagesCreate, "/images/{name:.*}/insert": postImagesInsert, + "/images/load": postImagesLoad, "/images/{name:.*}/push": postImagesPush, "/images/{name:.*}/tag": postImagesTag, "/containers/create": postContainersCreate, diff --git a/components/engine/commands.go b/components/engine/commands.go index 889aa72ffb..736334e40b 100644 --- a/components/engine/commands.go +++ b/components/engine/commands.go @@ -92,6 +92,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { {"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"}, {"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"}, @@ -102,6 +103,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { {"rm", "Remove one or more containers"}, {"rmi", "Remove one or more images"}, {"run", "Run a command in a new container"}, + {"save", "Save an image to a tar archive"}, {"search", "Search for an image in the docker index"}, {"start", "Start a stopped container"}, {"stop", "Stop a running container"}, @@ -1961,6 +1963,42 @@ func (cli *DockerCli) CmdCp(args ...string) error { return nil } +func (cli *DockerCli) CmdSave(args ...string) error { + cmd := Subcmd("save", "IMAGE DESTINATION", "Save an image to a tar archive") + if err := cmd.Parse(args); err != nil { + cmd.Usage() + return nil + } + + if cmd.NArg() != 1 { + cmd.Usage() + return nil + } + + image := cmd.Arg(0) + + if err := cli.stream("GET", "/images/"+image+"/get", nil, cli.out, nil); err != nil { + return err + } + return nil +} + +func (cli *DockerCli) CmdLoad(args ...string) error { + cmd := Subcmd("load", "SOURCE", "Load an image from a tar archive") + + if cmd.NArg() != 0 { + cmd.Usage() + return nil + } + + err := cli.stream("POST", "/images/load", cli.in, cli.out, nil) + if err != nil { + fmt.Println("Send failed", err) + } + + return nil +} + func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, error) { var params io.Reader if data != nil { diff --git a/components/engine/docs/sources/commandline/cli.rst b/components/engine/docs/sources/commandline/cli.rst index d0a8d83c0c..2adfe48f90 100644 --- a/components/engine/docs/sources/commandline/cli.rst +++ b/components/engine/docs/sources/commandline/cli.rst @@ -559,6 +559,17 @@ Known Issues (kill) * :issue:`197` indicates that ``docker kill`` may leave directories behind and make it difficult to remove the container. +.. _cli_load: + +``load`` +-------- + +:: + Usage: docker load < repository.tar + + Loads a tarred repository from the standard input stream. + Restores both images and tags. + .. _cli_login: ``login`` @@ -852,6 +863,17 @@ Known Issues (run -volumes-from) could indicate a permissions problem with AppArmor. Please see the issue for a workaround. +.. _cli_save: + +``save`` + +:: + + Usage: docker save image > repository.tar + + Streams a tarred repository to the standard output stream. + Contains all parent layers, and all tags + versions. + .. _cli_search: ``search`` diff --git a/components/engine/server.go b/components/engine/server.go index e9a76f8d84..ac6401b2da 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -197,6 +197,155 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error { return fmt.Errorf("No such container: %s", name) } +// ImageExport exports all images with the given tag. All versions +// containing the same tag are exported. The resulting output is an +// uncompressed tar ball. +// name is the set of tags to export. +// out is the writer where the images are written to. +func (srv *Server) ImageExport(name string, out io.Writer) error { + // get image json + tempdir, err := ioutil.TempDir("", "docker-export-") + if err != nil { + utils.Debugf("save", name, "") + return err + } + utils.Debugf("Serializing %s", name) + + rootRepo := srv.runtime.repositories.Repositories[name] + for _, rootImage := range rootRepo { + image, _ := srv.ImageInspect(rootImage) + for i := image; i != nil; { + // temporary directory + tmpImageDir := path.Join(tempdir, i.ID) + os.Mkdir(tmpImageDir, os.ModeDir) + + // serialize json + b, err := json.Marshal(i) + if err != nil { + utils.Debugf("%s", err) + os.RemoveAll(tempdir) + return err + } + ioutil.WriteFile(path.Join(tmpImageDir, "json"), b, os.ModeAppend) + + // serialize filesystem + fs, err := Tar(path.Join(srv.runtime.graph.Root, i.ID, "layer"), Uncompressed) + if err != nil { + utils.Debugf("%s", err) + os.RemoveAll(tempdir) + return err + } + fsTar, err := os.Create(path.Join(tmpImageDir, "layer.tar")) + if err != nil { + os.RemoveAll(tempdir) + utils.Debugf("%s", err) + return err + } + _, err = io.Copy(fsTar, fs) + if err != nil { + utils.Debugf("%s", err) + os.RemoveAll(tempdir) + return err + } + fsTar.Close() + + // find parent + if i.Parent != "" { + i, err = srv.ImageInspect(i.Parent) + if err != nil { + utils.Debugf("%s", err) + os.RemoveAll(tempdir) + return err + } + } else { + i = nil + } + } + } + + // write repositories + rootRepoMap := map[string]Repository{} + rootRepoMap[name] = rootRepo + rootRepoJson, _ := json.Marshal(rootRepoMap) + + ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.ModeAppend) + + fs, err := Tar(tempdir, Uncompressed) + if err != nil { + os.RemoveAll(tempdir) + return err + } + if _, err := io.Copy(out, fs); err != nil { + os.RemoveAll(tempdir) + return err + } + os.RemoveAll(tempdir) + return nil +} + +// Loads a set of images into the repository. This is the complementary of ImageExport. +// The input stream is an uncompressed tar ball containing images and metadata. +func (srv *Server) ImageLoad(in io.Reader) error { + tmpImageDir, _ := ioutil.TempDir("", "docker-import-") + repoTarFile := path.Join(tmpImageDir, "repo.tar") + repoDir := path.Join(tmpImageDir, "repo") + tarFile, _ := os.Create(repoTarFile) + io.Copy(tarFile, in) + tarFile.Close() + repoFile, _ := os.Open(repoTarFile) + os.Mkdir(repoDir, os.ModeDir) + Untar(repoFile, repoDir) + repositoriesJson, _ := ioutil.ReadFile(path.Join(tmpImageDir, "repo", "repositories")) + repositories := map[string]Repository{} + json.Unmarshal(repositoriesJson, &repositories) + + for imageName, tagMap := range repositories { + for tag, address := range tagMap { + err := srv.recursiveLoad(address, tmpImageDir) + if err != nil { + utils.Debugf("Error loading repository") + } + srv.runtime.repositories.Set(imageName, tag, address, true) + } + } + os.RemoveAll(tmpImageDir) + return nil +} + +func (srv *Server) recursiveLoad(address, tmpImageDir string) error { + _, err := srv.ImageInspect(address) + utils.Debugf("Attempting to load %s", "address") + if err != nil { + utils.Debugf("Loading %s", address) + imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json")) + if err != nil { + return err + utils.Debugf("Error reading json", err) + } + layer, err := os.Open(path.Join(tmpImageDir, "repo", address, "layer.tar")) + if err != nil { + utils.Debugf("Error reading embedded tar", err) + return err + } + img, err := NewImgJSON(imageJson) + if err != nil { + utils.Debugf("Error unmarshalling json", err) + return err + } + if img.Parent != "" { + if !srv.runtime.graph.Exists(img.Parent) { + srv.recursiveLoad(img.Parent, tmpImageDir) + } + } + err = srv.runtime.graph.Register(imageJson, layer, img) + if err != nil { + utils.Debugf("Error registering image") + } + } + utils.Debugf("Completed processing %s", address) + return nil +} + func (srv *Server) ImagesSearch(term string) ([]registry.SearchResult, error) { r, err := registry.NewRegistry(srv.runtime.config.Root, nil, srv.HTTPRequestFactory(nil)) if err != nil { From bac700fc36af068f46da93512c59f92629c89f91 Mon Sep 17 00:00:00 2001 From: "Frederick F. Kautz IV" Date: Fri, 11 Oct 2013 22:11:49 -0700 Subject: [PATCH 12/20] Adding version info, switching to defer for cleanup Upstream-commit: 61a8020e51ac6de9bc777098a04e05a185c2cd32 Component: engine --- components/engine/server.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/components/engine/server.go b/components/engine/server.go index ac6401b2da..564ae7c0fb 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -218,12 +218,15 @@ func (srv *Server) ImageExport(name string, out io.Writer) error { // temporary directory tmpImageDir := path.Join(tempdir, i.ID) os.Mkdir(tmpImageDir, os.ModeDir) + defer os.RemoveAll(tempdir) + var version = "0.7" + var versionBuf = []byte(version) + ioutil.WriteFile(path.Join(tmpImageDir, "VERSION"), versionBuf, os.ModeAppend) // serialize json b, err := json.Marshal(i) if err != nil { utils.Debugf("%s", err) - os.RemoveAll(tempdir) return err } ioutil.WriteFile(path.Join(tmpImageDir, "json"), b, os.ModeAppend) @@ -232,19 +235,16 @@ func (srv *Server) ImageExport(name string, out io.Writer) error { fs, err := Tar(path.Join(srv.runtime.graph.Root, i.ID, "layer"), Uncompressed) if err != nil { utils.Debugf("%s", err) - os.RemoveAll(tempdir) return err } fsTar, err := os.Create(path.Join(tmpImageDir, "layer.tar")) if err != nil { - os.RemoveAll(tempdir) utils.Debugf("%s", err) return err } _, err = io.Copy(fsTar, fs) if err != nil { utils.Debugf("%s", err) - os.RemoveAll(tempdir) return err } fsTar.Close() @@ -254,7 +254,6 @@ func (srv *Server) ImageExport(name string, out io.Writer) error { i, err = srv.ImageInspect(i.Parent) if err != nil { utils.Debugf("%s", err) - os.RemoveAll(tempdir) return err } } else { @@ -271,15 +270,13 @@ func (srv *Server) ImageExport(name string, out io.Writer) error { ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.ModeAppend) fs, err := Tar(tempdir, Uncompressed) + defer os.RemoveAll(tempdir) if err != nil { - os.RemoveAll(tempdir) return err } if _, err := io.Copy(out, fs); err != nil { - os.RemoveAll(tempdir) return err } - os.RemoveAll(tempdir) return nil } @@ -287,6 +284,7 @@ func (srv *Server) ImageExport(name string, out io.Writer) error { // The input stream is an uncompressed tar ball containing images and metadata. func (srv *Server) ImageLoad(in io.Reader) error { tmpImageDir, _ := ioutil.TempDir("", "docker-import-") + defer os.RemoveAll(tmpImageDir) repoTarFile := path.Join(tmpImageDir, "repo.tar") repoDir := path.Join(tmpImageDir, "repo") tarFile, _ := os.Create(repoTarFile) @@ -308,7 +306,6 @@ func (srv *Server) ImageLoad(in io.Reader) error { srv.runtime.repositories.Set(imageName, tag, address, true) } } - os.RemoveAll(tmpImageDir) return nil } From 9f3f2484044f3c7e3d22f041b0551667e5d127ef Mon Sep 17 00:00:00 2001 From: "Frederick F. Kautz IV" Date: Sat, 2 Nov 2013 01:18:55 -0700 Subject: [PATCH 13/20] Adding spaces to fix docs build Upstream-commit: bf504f2afab679922e92b8bb5fe28957f5f6bf02 Component: engine --- components/engine/docs/sources/commandline/cli.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/docs/sources/commandline/cli.rst b/components/engine/docs/sources/commandline/cli.rst index 2adfe48f90..c25b890faf 100644 --- a/components/engine/docs/sources/commandline/cli.rst +++ b/components/engine/docs/sources/commandline/cli.rst @@ -565,6 +565,7 @@ Known Issues (kill) -------- :: + Usage: docker load < repository.tar Loads a tarred repository from the standard input stream. From 623a7ac7d40b232f20cbc5f2546a2b237ef00ebc Mon Sep 17 00:00:00 2001 From: "Frederick F. Kautz IV" Date: Mon, 4 Nov 2013 17:43:57 -0800 Subject: [PATCH 14/20] Setting offline package version to 1.0 Upstream-commit: 6014db4a7e8d76580edf995bc974a55dace0c8ba Component: engine --- components/engine/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/server.go b/components/engine/server.go index 564ae7c0fb..a457550387 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -219,7 +219,7 @@ func (srv *Server) ImageExport(name string, out io.Writer) error { tmpImageDir := path.Join(tempdir, i.ID) os.Mkdir(tmpImageDir, os.ModeDir) defer os.RemoveAll(tempdir) - var version = "0.7" + var version = "1.0" var versionBuf = []byte(version) ioutil.WriteFile(path.Join(tmpImageDir, "VERSION"), versionBuf, os.ModeAppend) From 5b716c3df7cff1777194aaae38ec1aa2b5f91851 Mon Sep 17 00:00:00 2001 From: "Frederick F. Kautz IV" Date: Wed, 13 Nov 2013 23:41:42 +0000 Subject: [PATCH 15/20] Fixing build Upstream-commit: 844c13bce6429087e2763038136e6485da425741 Component: engine --- components/engine/server.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/engine/server.go b/components/engine/server.go index a457550387..46d27b2ae8 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -232,7 +232,7 @@ func (srv *Server) ImageExport(name string, out io.Writer) error { ioutil.WriteFile(path.Join(tmpImageDir, "json"), b, os.ModeAppend) // serialize filesystem - fs, err := Tar(path.Join(srv.runtime.graph.Root, i.ID, "layer"), Uncompressed) + fs, err := archive.Tar(path.Join(srv.runtime.graph.Root, i.ID, "layer"), archive.Uncompressed) if err != nil { utils.Debugf("%s", err) return err @@ -269,7 +269,7 @@ func (srv *Server) ImageExport(name string, out io.Writer) error { ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.ModeAppend) - fs, err := Tar(tempdir, Uncompressed) + fs, err := archive.Tar(tempdir, archive.Uncompressed) defer os.RemoveAll(tempdir) if err != nil { return err @@ -292,7 +292,7 @@ func (srv *Server) ImageLoad(in io.Reader) error { tarFile.Close() repoFile, _ := os.Open(repoTarFile) os.Mkdir(repoDir, os.ModeDir) - Untar(repoFile, repoDir) + archive.Untar(repoFile, repoDir) repositoriesJson, _ := ioutil.ReadFile(path.Join(tmpImageDir, "repo", "repositories")) repositories := map[string]Repository{} json.Unmarshal(repositoriesJson, &repositories) From b7d4ec1f01110ef94615181167d2607e4624db7e Mon Sep 17 00:00:00 2001 From: "Frederick F. Kautz IV" Date: Thu, 14 Nov 2013 01:05:37 +0000 Subject: [PATCH 16/20] Adding content type to images/(name)/get Upstream-commit: 1211065c8d81fcc480ddea5d377982cab871d472 Component: engine --- components/engine/api.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/engine/api.go b/components/engine/api.go index cf46ee316e..74388d805b 100644 --- a/components/engine/api.go +++ b/components/engine/api.go @@ -536,6 +536,9 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http func getImagesGet(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { name := vars["name"] + if version > 1.0 { + w.Header().Set("Content-Type", "application/x-tar") + } err := srv.ImageExport(name, w) if err != nil { return err From 46ebe14722544366a1667b1e46c27aacb9d001ca Mon Sep 17 00:00:00 2001 From: "Frederick F. Kautz IV" Date: Thu, 14 Nov 2013 01:05:46 +0000 Subject: [PATCH 17/20] Adding 1.7 documentation Upstream-commit: 383f95bba1fef213473725fdf6193be0de5b73fd Component: engine --- .../sources/api/docker_remote_api_v1.7.rst | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/components/engine/docs/sources/api/docker_remote_api_v1.7.rst b/components/engine/docs/sources/api/docker_remote_api_v1.7.rst index 13aa5df71b..251b162250 100644 --- a/components/engine/docs/sources/api/docker_remote_api_v1.7.rst +++ b/components/engine/docs/sources/api/docker_remote_api_v1.7.rst @@ -1171,6 +1171,53 @@ Monitor Docker's events :statuscode 200: no error :statuscode 500: server error +Get a tarball containing all images and tags in a repository +************************************************************ + +.. http:get:: /images/(name)/get + + Get a tarball containing all images and metadata for the repository specified by ``name``. + + **Example request** + + .. sourcecode:: http + + GET /images/ubuntu/get + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/x-tar + + Binary data stream + :statuscode 200: no error + :statuscode 500: server error + +Load a tarball with a set of images and tags into docker +******************************************************** + +.. http:post:: /images/load + + Load a set of images and tags into the docker repository. + + **Example request** + + .. sourcecode:: http + + POST /images/load + + Tarball in body + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :statuscode 200: no error + :statuscode 500: server error 3. Going further ================ From 3f60be4255f0465b2584844783aecf89f187ff6d Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 18 Nov 2013 11:48:55 -0800 Subject: [PATCH 18/20] Fix error checks for offline transfer and remove unneeded debug output Upstream-commit: 0ef6fed5c7b7b1ab4084ecc99880ffc6a7c8eaca Component: engine --- components/engine/server.go | 100 ++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 34 deletions(-) diff --git a/components/engine/server.go b/components/engine/server.go index 46d27b2ae8..8b34c024df 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -206,7 +206,6 @@ func (srv *Server) ImageExport(name string, out io.Writer) error { // get image json tempdir, err := ioutil.TempDir("", "docker-export-") if err != nil { - utils.Debugf("save", name, "") return err } utils.Debugf("Serializing %s", name) @@ -217,34 +216,38 @@ func (srv *Server) ImageExport(name string, out io.Writer) error { for i := image; i != nil; { // temporary directory tmpImageDir := path.Join(tempdir, i.ID) - os.Mkdir(tmpImageDir, os.ModeDir) + if err := os.Mkdir(tmpImageDir, os.ModeDir); err != nil { + return err + } defer os.RemoveAll(tempdir) + var version = "1.0" var versionBuf = []byte(version) - ioutil.WriteFile(path.Join(tmpImageDir, "VERSION"), versionBuf, os.ModeAppend) + + if err := ioutil.WriteFile(path.Join(tmpImageDir, "VERSION"), versionBuf, os.ModeAppend); err != nil { + return err + } // serialize json b, err := json.Marshal(i) if err != nil { - utils.Debugf("%s", err) return err } - ioutil.WriteFile(path.Join(tmpImageDir, "json"), b, os.ModeAppend) + if err := ioutil.WriteFile(path.Join(tmpImageDir, "json"), b, os.ModeAppend); err != nil { + return err + } // serialize filesystem fs, err := archive.Tar(path.Join(srv.runtime.graph.Root, i.ID, "layer"), archive.Uncompressed) if err != nil { - utils.Debugf("%s", err) return err } + fsTar, err := os.Create(path.Join(tmpImageDir, "layer.tar")) if err != nil { - utils.Debugf("%s", err) return err } - _, err = io.Copy(fsTar, fs) - if err != nil { - utils.Debugf("%s", err) + if _, err = io.Copy(fsTar, fs); err != nil { return err } fsTar.Close() @@ -253,7 +256,6 @@ func (srv *Server) ImageExport(name string, out io.Writer) error { if i.Parent != "" { i, err = srv.ImageInspect(i.Parent) if err != nil { - utils.Debugf("%s", err) return err } } else { @@ -267,13 +269,16 @@ func (srv *Server) ImageExport(name string, out io.Writer) error { rootRepoMap[name] = rootRepo rootRepoJson, _ := json.Marshal(rootRepoMap) - ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.ModeAppend) + if err := ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.ModeAppend); err != nil { + return err + } fs, err := archive.Tar(tempdir, archive.Uncompressed) - defer os.RemoveAll(tempdir) if err != nil { return err } + defer os.RemoveAll(tempdir) + if _, err := io.Copy(out, fs); err != nil { return err } @@ -283,42 +288,68 @@ func (srv *Server) ImageExport(name string, out io.Writer) error { // Loads a set of images into the repository. This is the complementary of ImageExport. // The input stream is an uncompressed tar ball containing images and metadata. func (srv *Server) ImageLoad(in io.Reader) error { - tmpImageDir, _ := ioutil.TempDir("", "docker-import-") + tmpImageDir, err := ioutil.TempDir("", "docker-import-") + if err != nil { + return err + } defer os.RemoveAll(tmpImageDir) - repoTarFile := path.Join(tmpImageDir, "repo.tar") - repoDir := path.Join(tmpImageDir, "repo") - tarFile, _ := os.Create(repoTarFile) - io.Copy(tarFile, in) + + var ( + repoTarFile = path.Join(tmpImageDir, "repo.tar") + repoDir = path.Join(tmpImageDir, "repo") + ) + + tarFile, err := os.Create(repoTarFile) + if err != nil { + return err + } + if _, err := io.Copy(tarFile, in); err != nil { + return err + } tarFile.Close() - repoFile, _ := os.Open(repoTarFile) - os.Mkdir(repoDir, os.ModeDir) - archive.Untar(repoFile, repoDir) - repositoriesJson, _ := ioutil.ReadFile(path.Join(tmpImageDir, "repo", "repositories")) + + repoFile, err := os.Open(repoTarFile) + if err != nil { + return err + } + if err := os.Mkdir(repoDir, os.ModeDir); err != nil { + return err + } + if err := archive.Untar(repoFile, repoDir); err != nil { + return err + } + repositoriesJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", "repositories")) + if err != nil { + return err + } repositories := map[string]Repository{} - json.Unmarshal(repositoriesJson, &repositories) + if err := json.Unmarshal(repositoriesJson, &repositories); err != nil { + return err + } for imageName, tagMap := range repositories { for tag, address := range tagMap { - err := srv.recursiveLoad(address, tmpImageDir) - if err != nil { - utils.Debugf("Error loading repository") + if err := srv.recursiveLoad(address, tmpImageDir); err != nil { + return err + } + if err := srv.runtime.repositories.Set(imageName, tag, address, true); err != nil { + return err } - srv.runtime.repositories.Set(imageName, tag, address, true) } } return nil } func (srv *Server) recursiveLoad(address, tmpImageDir string) error { - _, err := srv.ImageInspect(address) - utils.Debugf("Attempting to load %s", "address") - if err != nil { + if _, err := srv.ImageInspect(address); err != nil { utils.Debugf("Loading %s", address) + imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json")) if err != nil { return err utils.Debugf("Error reading json", err) } + layer, err := os.Open(path.Join(tmpImageDir, "repo", address, "layer.tar")) if err != nil { utils.Debugf("Error reading embedded tar", err) @@ -331,12 +362,13 @@ func (srv *Server) recursiveLoad(address, tmpImageDir string) error { } if img.Parent != "" { if !srv.runtime.graph.Exists(img.Parent) { - srv.recursiveLoad(img.Parent, tmpImageDir) + if err := srv.recursiveLoad(img.Parent, tmpImageDir); err != nil { + return err + } } } - err = srv.runtime.graph.Register(imageJson, layer, img) - if err != nil { - utils.Debugf("Error registering image") + if err := srv.runtime.graph.Register(imageJson, layer, img); err != nil { + return err } } utils.Debugf("Completed processing %s", address) From 92e4e393e605b4296bd1e336f83234a0c24169bd Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 18 Nov 2013 12:34:34 -0800 Subject: [PATCH 19/20] Add typo in remove all. Ensure tmpDir is removed Upstream-commit: 9de45904980dba224e8ab133f12cfc53835e2dd4 Component: engine --- components/engine/server.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/engine/server.go b/components/engine/server.go index 8b34c024df..de6520c24f 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -208,6 +208,8 @@ func (srv *Server) ImageExport(name string, out io.Writer) error { if err != nil { return err } + defer os.RemoveAll(tempdir) + utils.Debugf("Serializing %s", name) rootRepo := srv.runtime.repositories.Repositories[name] @@ -219,7 +221,7 @@ func (srv *Server) ImageExport(name string, out io.Writer) error { if err := os.Mkdir(tmpImageDir, os.ModeDir); err != nil { return err } - defer os.RemoveAll(tempdir) + defer os.RemoveAll(tmpImageDir) var version = "1.0" var versionBuf = []byte(version) @@ -277,7 +279,6 @@ func (srv *Server) ImageExport(name string, out io.Writer) error { if err != nil { return err } - defer os.RemoveAll(tempdir) if _, err := io.Copy(out, fs); err != nil { return err From 5763c9619e8d87cf061126bfa903c799d2ad5dc7 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Wed, 20 Nov 2013 11:27:43 -0600 Subject: [PATCH 20/20] resize pty after starting Since ptyMaster is created during container start (startPty), it should resized after starting, not before. Upstream-commit: 171d681724443cf69f669dcaf4887f5d99894b25 Component: engine --- components/engine/commands.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/components/engine/commands.go b/components/engine/commands.go index 736334e40b..0539be9006 100644 --- a/components/engine/commands.go +++ b/components/engine/commands.go @@ -575,6 +575,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { } var cErr chan error + var tty bool if *attach || *openStdin { if cmd.NArg() > 1 { return fmt.Errorf("Impossible to start and attach multiple containers at once.") @@ -591,17 +592,13 @@ func (cli *DockerCli) CmdStart(args ...string) error { return err } + tty = container.Config.Tty + if !container.Config.Tty { sigc := cli.forwardAllSignals(cmd.Arg(0)) defer utils.StopCatch(sigc) } - if container.Config.Tty && cli.isTerminal { - if err := cli.monitorTtySize(cmd.Arg(0)); err != nil { - return err - } - } - var in io.ReadCloser v := url.Values{} @@ -639,7 +636,13 @@ func (cli *DockerCli) CmdStart(args ...string) error { } return encounteredError } + if *openStdin || *attach { + if tty && cli.isTerminal { + if err := cli.monitorTtySize(cmd.Arg(0)); err != nil { + utils.Errorf("Error monitoring TTY size: %s\n", err) + } + } return <-cErr } return nil