From 51d6228261cdc379aade581ee504b2c59a3e02a9 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 25 Apr 2013 16:48:31 -0700 Subject: [PATCH 1/5] Implement -config and -command in CmdCommit in order to allow autorun --- commands.go | 25 ++++++++++++++++++------ graph.go | 3 ++- image.go | 1 + runtime.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 74 insertions(+), 11 deletions(-) diff --git a/commands.go b/commands.go index f0013d41e5..8e0a351728 100644 --- a/commands.go +++ b/commands.go @@ -477,7 +477,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args . } archive = ProgressReader(resp.Body, int(resp.ContentLength), stdout, "Importing %v/%v (%v)") } - img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "") + img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil) if err != nil { return err } @@ -726,6 +726,8 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri "Create a new image from a container's changes") flComment := cmd.String("m", "", "Commit message") flAuthor := cmd.String("author", "", "Author (eg. \"John Hannibal Smith \"") + flConfig := cmd.String("config", "", "Config automatically applied when the image is run. This option must be the last one.") + flCommand := cmd.String("command", "", "Command to run when starting the image") if err := cmd.Parse(args); err != nil { return nil } @@ -734,7 +736,22 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri cmd.Usage() return nil } - img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, *flAuthor) + + var config []string + if *flConfig != "" { + config = strings.Split(*flConfig, " ") + } + if *flCommand != "" { + config = append(config, "", "/bin/sh", "-c", *flCommand) + } else if *flConfig != "" { + config = append(config, "", "") + } + c, err := ParseRun(config, stdout, srv.runtime.capabilities) + if err != nil { + return err + } + + img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, *flAuthor, c) if err != nil { return err } @@ -925,10 +942,6 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s fmt.Fprintln(stdout, "Error: Image not specified") return fmt.Errorf("Image not specified") } - if len(config.Cmd) == 0 { - fmt.Fprintln(stdout, "Error: Command not specified") - return fmt.Errorf("Command not specified") - } if config.Tty { stdout.SetOptionRawTerminal() diff --git a/graph.go b/graph.go index c0e5000913..bf22bb19f0 100644 --- a/graph.go +++ b/graph.go @@ -84,13 +84,14 @@ func (graph *Graph) Get(name string) (*Image, error) { } // Create creates a new image and registers it in the graph. -func (graph *Graph) Create(layerData Archive, container *Container, comment, author string) (*Image, error) { +func (graph *Graph) Create(layerData Archive, container *Container, comment, author string, config *Config) (*Image, error) { img := &Image{ Id: GenerateId(), Comment: comment, Created: time.Now(), DockerVersion: VERSION, Author: author, + Config: config, } if container != nil { img.Parent = container.Image diff --git a/image.go b/image.go index 78a7f02c68..09c0f8dcf6 100644 --- a/image.go +++ b/image.go @@ -24,6 +24,7 @@ type Image struct { ContainerConfig Config `json:"container_config,omitempty"` DockerVersion string `json:"docker_version,omitempty"` Author string `json:"author,omitempty"` + Config *Config `json:"config,omitempty"` graph *Graph } diff --git a/runtime.go b/runtime.go index d6eb0f3c91..9d2d889e8d 100644 --- a/runtime.go +++ b/runtime.go @@ -77,12 +77,59 @@ func (runtime *Runtime) containerRoot(id string) string { return path.Join(runtime.repository, id) } +func (runtime *Runtime) mergeConfig(userConf, imageConf *Config) { + if userConf.Hostname != "" { + userConf.Hostname = imageConf.Hostname + } + if userConf.User != "" { + userConf.User = imageConf.User + } + if userConf.Memory == 0 { + userConf.Memory = imageConf.Memory + } + if userConf.MemorySwap == 0 { + userConf.MemorySwap = imageConf.MemorySwap + } + if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 { + userConf.PortSpecs = imageConf.PortSpecs + } + if !userConf.Tty { + userConf.Tty = userConf.Tty + } + if !userConf.OpenStdin { + userConf.OpenStdin = imageConf.OpenStdin + } + if !userConf.StdinOnce { + userConf.StdinOnce = imageConf.StdinOnce + } + if userConf.Env == nil || len(userConf.Env) == 0 { + userConf.Env = imageConf.Env + } + if userConf.Cmd == nil || len(userConf.Cmd) == 0 { + userConf.Cmd = imageConf.Cmd + } + if userConf.Dns == nil || len(userConf.Dns) == 0 { + userConf.Dns = imageConf.Dns + } +} + func (runtime *Runtime) Create(config *Config) (*Container, error) { + // Lookup image img, err := runtime.repositories.LookupImage(config.Image) if err != nil { return nil, err } + + //runtime.mergeConfig(config, img.Config) + if img.Config != nil { + config = img.Config + } + + if config.Cmd == nil { + return nil, fmt.Errorf("No command specified") + } + // Generate id id := GenerateId() // Generate default hostname @@ -103,6 +150,7 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) { // FIXME: do we need to store this in the container? SysInitPath: sysInitPath, } + container.root = runtime.containerRoot(container.Id) // Step 1: create the container directory. // This doubles as a barrier to avoid race conditions. @@ -249,7 +297,7 @@ func (runtime *Runtime) Destroy(container *Container) error { // Commit creates a new filesystem image from the current state of a container. // The image can optionally be tagged into a repository -func (runtime *Runtime) Commit(id, repository, tag, comment, author string) (*Image, error) { +func (runtime *Runtime) Commit(id, repository, tag, comment, author string, config *Config) (*Image, error) { container := runtime.Get(id) if container == nil { return nil, fmt.Errorf("No such container: %s", id) @@ -261,7 +309,7 @@ func (runtime *Runtime) Commit(id, repository, tag, comment, author string) (*Im return nil, err } // Create a new image from the container's base layers + a new layer from container changes - img, err := runtime.graph.Create(rwTar, container, comment, author) + img, err := runtime.graph.Create(rwTar, container, comment, author, config) if err != nil { return nil, err } @@ -314,13 +362,13 @@ func NewRuntime() (*Runtime, error) { _, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes")) runtime.capabilities.MemoryLimit = err1 == nil && err2 == nil if !runtime.capabilities.MemoryLimit { - log.Printf("WARNING: Your kernel does not support cgroup memory limit.") + log.Printf("WARNING: Your kernel does not support cgroup memory limit.") } _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes")) runtime.capabilities.SwapLimit = err == nil if !runtime.capabilities.SwapLimit { - log.Printf("WARNING: Your kernel does not support cgroup swap limit.") + log.Printf("WARNING: Your kernel does not support cgroup swap limit.") } } return runtime, nil From 724e2d6b0aa5b1cbe95f39c5d22e733124cee9be Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 25 Apr 2013 17:02:38 -0700 Subject: [PATCH 2/5] Update unit test in order to comply with new api --- container_test.go | 4 ++-- graph_test.go | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/container_test.go b/container_test.go index fef5331e3a..1a23d73156 100644 --- a/container_test.go +++ b/container_test.go @@ -193,7 +193,7 @@ func TestDiff(t *testing.T) { if err != nil { t.Error(err) } - img, err := runtime.graph.Create(rwTar, container1, "unit test commited image - diff", "") + img, err := runtime.graph.Create(rwTar, container1, "unit test commited image - diff", "", nil) if err != nil { t.Error(err) } @@ -258,7 +258,7 @@ func TestCommitRun(t *testing.T) { if err != nil { t.Error(err) } - img, err := runtime.graph.Create(rwTar, container1, "unit test commited image", "") + img, err := runtime.graph.Create(rwTar, container1, "unit test commited image", "", nil) if err != nil { t.Error(err) } diff --git a/graph_test.go b/graph_test.go index 1bd05aaa97..b7ec81698f 100644 --- a/graph_test.go +++ b/graph_test.go @@ -62,7 +62,7 @@ func TestGraphCreate(t *testing.T) { if err != nil { t.Fatal(err) } - image, err := graph.Create(archive, nil, "Testing", "") + image, err := graph.Create(archive, nil, "Testing", "", nil) if err != nil { t.Fatal(err) } @@ -122,7 +122,7 @@ func TestMount(t *testing.T) { if err != nil { t.Fatal(err) } - image, err := graph.Create(archive, nil, "Testing", "") + image, err := graph.Create(archive, nil, "Testing", "", nil) if err != nil { t.Fatal(err) } @@ -166,7 +166,7 @@ func createTestImage(graph *Graph, t *testing.T) *Image { if err != nil { t.Fatal(err) } - img, err := graph.Create(archive, nil, "Test image", "") + img, err := graph.Create(archive, nil, "Test image", "", nil) if err != nil { t.Fatal(err) } @@ -181,7 +181,7 @@ func TestDelete(t *testing.T) { t.Fatal(err) } assertNImages(graph, t, 0) - img, err := graph.Create(archive, nil, "Bla bla", "") + img, err := graph.Create(archive, nil, "Bla bla", "", nil) if err != nil { t.Fatal(err) } @@ -192,11 +192,11 @@ func TestDelete(t *testing.T) { assertNImages(graph, t, 0) // Test 2 create (same name) / 1 delete - img1, err := graph.Create(archive, nil, "Testing", "") + img1, err := graph.Create(archive, nil, "Testing", "", nil) if err != nil { t.Fatal(err) } - if _, err = graph.Create(archive, nil, "Testing", ""); err != nil { + if _, err = graph.Create(archive, nil, "Testing", "", nil); err != nil { t.Fatal(err) } assertNImages(graph, t, 2) From 30d327d37ecca58a41f4a370b581ee638ed3ff04 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 25 Apr 2013 17:03:13 -0700 Subject: [PATCH 3/5] Add TestCommitAutoRun --- container_test.go | 78 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/container_test.go b/container_test.go index 1a23d73156..397e5c3717 100644 --- a/container_test.go +++ b/container_test.go @@ -226,6 +226,84 @@ func TestDiff(t *testing.T) { } } +func TestCommitAutoRun(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + container1, err := runtime.Create( + &Config{ + Image: GetTestImage(runtime).Id, + Cmd: []string{"/bin/sh", "-c", "echo hello > /world"}, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container1) + + if container1.State.Running { + t.Errorf("Container shouldn't be running") + } + if err := container1.Run(); err != nil { + t.Fatal(err) + } + if container1.State.Running { + t.Errorf("Container shouldn't be running") + } + + rwTar, err := container1.ExportRw() + if err != nil { + t.Error(err) + } + img, err := runtime.graph.Create(rwTar, container1, "unit test commited image", "", &Config{Cmd: []string{"cat", "/world"}}) + if err != nil { + t.Error(err) + } + + // FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world + + container2, err := runtime.Create( + &Config{ + Image: img.Id, + }, + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container2) + stdout, err := container2.StdoutPipe() + if err != nil { + t.Fatal(err) + } + stderr, err := container2.StderrPipe() + if err != nil { + t.Fatal(err) + } + if err := container2.Start(); err != nil { + t.Fatal(err) + } + container2.Wait() + output, err := ioutil.ReadAll(stdout) + if err != nil { + t.Fatal(err) + } + output2, err := ioutil.ReadAll(stderr) + if err != nil { + t.Fatal(err) + } + if err := stdout.Close(); err != nil { + t.Fatal(err) + } + if err := stderr.Close(); err != nil { + t.Fatal(err) + } + if string(output) != "hello\n" { + t.Fatalf("Unexpected output. Expected %s, received: %s (err: %s)", "hello\n", output, output2) + } +} + func TestCommitRun(t *testing.T) { runtime, err := newTestRuntime() if err != nil { From ae97477284fade20520b5991709d6b65d5fd4442 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 26 Apr 2013 10:48:33 -0700 Subject: [PATCH 4/5] Remove -command in CmdCommit and make -config use Json --- commands.go | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/commands.go b/commands.go index 8e0a351728..3349a1aa27 100644 --- a/commands.go +++ b/commands.go @@ -726,8 +726,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri "Create a new image from a container's changes") flComment := cmd.String("m", "", "Commit message") flAuthor := cmd.String("author", "", "Author (eg. \"John Hannibal Smith \"") - flConfig := cmd.String("config", "", "Config automatically applied when the image is run. This option must be the last one.") - flCommand := cmd.String("command", "", "Command to run when starting the image") + flConfig := cmd.String("config", "", "Config automatically applied when the image is run. "+`(ex: -config '{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`) if err := cmd.Parse(args); err != nil { return nil } @@ -737,21 +736,14 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri return nil } - var config []string + config := &Config{} if *flConfig != "" { - config = strings.Split(*flConfig, " ") - } - if *flCommand != "" { - config = append(config, "", "/bin/sh", "-c", *flCommand) - } else if *flConfig != "" { - config = append(config, "", "") - } - c, err := ParseRun(config, stdout, srv.runtime.capabilities) - if err != nil { - return err + if err := json.Unmarshal([]byte(*flConfig), config); err != nil { + return err + } } - img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, *flAuthor, c) + img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, *flAuthor, config) if err != nil { return err } From 7ff65d40d557e0a128534c096f3cab0c10a79f7b Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 1 May 2013 11:22:06 -0700 Subject: [PATCH 5/5] Actually use the mergeConfig function --- runtime.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/runtime.go b/runtime.go index 9d2d889e8d..c0502363ef 100644 --- a/runtime.go +++ b/runtime.go @@ -121,9 +121,8 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) { return nil, err } - //runtime.mergeConfig(config, img.Config) if img.Config != nil { - config = img.Config + runtime.mergeConfig(config, img.Config) } if config.Cmd == nil {