diff --git a/commands.go b/commands.go index 918855e6c7..7d48376125 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,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. "+`(ex: -config '{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`) if err := cmd.Parse(args); err != nil { return nil } @@ -734,7 +735,15 @@ 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) + + config := &Config{} + if *flConfig != "" { + if err := json.Unmarshal([]byte(*flConfig), config); err != nil { + return err + } + } + + img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, *flAuthor, config) if err != nil { return err } @@ -925,10 +934,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/container_test.go b/container_test.go index b498f4f186..c3a891cf4c 100644 --- a/container_test.go +++ b/container_test.go @@ -184,7 +184,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) } @@ -217,6 +217,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 { @@ -248,7 +326,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.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/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) 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 3bd7f4299b..8148db04a4 100644 --- a/runtime.go +++ b/runtime.go @@ -78,12 +78,58 @@ 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 } + + if img.Config != nil { + runtime.mergeConfig(config, img.Config) + } + + if config.Cmd == nil { + return nil, fmt.Errorf("No command specified") + } + // Generate id id := GenerateId() // Generate default hostname @@ -104,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. @@ -265,7 +312,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) @@ -277,7 +324,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 }