| ... | ... |
@@ -477,7 +477,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args . |
| 477 | 477 |
} |
| 478 | 478 |
archive = ProgressReader(resp.Body, int(resp.ContentLength), stdout, "Importing %v/%v (%v)") |
| 479 | 479 |
} |
| 480 |
- img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "") |
|
| 480 |
+ img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil) |
|
| 481 | 481 |
if err != nil {
|
| 482 | 482 |
return err |
| 483 | 483 |
} |
| ... | ... |
@@ -726,6 +726,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri |
| 726 | 726 |
"Create a new image from a container's changes") |
| 727 | 727 |
flComment := cmd.String("m", "", "Commit message")
|
| 728 | 728 |
flAuthor := cmd.String("author", "", "Author (eg. \"John Hannibal Smith <hannibal@a-team.com>\"")
|
| 729 |
+ flConfig := cmd.String("config", "", "Config automatically applied when the image is run. "+`(ex: -config '{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`)
|
|
| 729 | 730 |
if err := cmd.Parse(args); err != nil {
|
| 730 | 731 |
return nil |
| 731 | 732 |
} |
| ... | ... |
@@ -734,7 +735,15 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri |
| 734 | 734 |
cmd.Usage() |
| 735 | 735 |
return nil |
| 736 | 736 |
} |
| 737 |
- img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, *flAuthor) |
|
| 737 |
+ |
|
| 738 |
+ config := &Config{}
|
|
| 739 |
+ if *flConfig != "" {
|
|
| 740 |
+ if err := json.Unmarshal([]byte(*flConfig), config); err != nil {
|
|
| 741 |
+ return err |
|
| 742 |
+ } |
|
| 743 |
+ } |
|
| 744 |
+ |
|
| 745 |
+ img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, *flAuthor, config) |
|
| 738 | 746 |
if err != nil {
|
| 739 | 747 |
return err |
| 740 | 748 |
} |
| ... | ... |
@@ -925,10 +934,6 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s |
| 925 | 925 |
fmt.Fprintln(stdout, "Error: Image not specified") |
| 926 | 926 |
return fmt.Errorf("Image not specified")
|
| 927 | 927 |
} |
| 928 |
- if len(config.Cmd) == 0 {
|
|
| 929 |
- fmt.Fprintln(stdout, "Error: Command not specified") |
|
| 930 |
- return fmt.Errorf("Command not specified")
|
|
| 931 |
- } |
|
| 932 | 928 |
|
| 933 | 929 |
if config.Tty {
|
| 934 | 930 |
stdout.SetOptionRawTerminal() |
| ... | ... |
@@ -184,7 +184,7 @@ func TestDiff(t *testing.T) {
|
| 184 | 184 |
if err != nil {
|
| 185 | 185 |
t.Error(err) |
| 186 | 186 |
} |
| 187 |
- img, err := runtime.graph.Create(rwTar, container1, "unit test commited image - diff", "") |
|
| 187 |
+ img, err := runtime.graph.Create(rwTar, container1, "unit test commited image - diff", "", nil) |
|
| 188 | 188 |
if err != nil {
|
| 189 | 189 |
t.Error(err) |
| 190 | 190 |
} |
| ... | ... |
@@ -217,6 +217,84 @@ func TestDiff(t *testing.T) {
|
| 217 | 217 |
} |
| 218 | 218 |
} |
| 219 | 219 |
|
| 220 |
+func TestCommitAutoRun(t *testing.T) {
|
|
| 221 |
+ runtime, err := newTestRuntime() |
|
| 222 |
+ if err != nil {
|
|
| 223 |
+ t.Fatal(err) |
|
| 224 |
+ } |
|
| 225 |
+ defer nuke(runtime) |
|
| 226 |
+ container1, err := runtime.Create( |
|
| 227 |
+ &Config{
|
|
| 228 |
+ Image: GetTestImage(runtime).Id, |
|
| 229 |
+ Cmd: []string{"/bin/sh", "-c", "echo hello > /world"},
|
|
| 230 |
+ }, |
|
| 231 |
+ ) |
|
| 232 |
+ if err != nil {
|
|
| 233 |
+ t.Fatal(err) |
|
| 234 |
+ } |
|
| 235 |
+ defer runtime.Destroy(container1) |
|
| 236 |
+ |
|
| 237 |
+ if container1.State.Running {
|
|
| 238 |
+ t.Errorf("Container shouldn't be running")
|
|
| 239 |
+ } |
|
| 240 |
+ if err := container1.Run(); err != nil {
|
|
| 241 |
+ t.Fatal(err) |
|
| 242 |
+ } |
|
| 243 |
+ if container1.State.Running {
|
|
| 244 |
+ t.Errorf("Container shouldn't be running")
|
|
| 245 |
+ } |
|
| 246 |
+ |
|
| 247 |
+ rwTar, err := container1.ExportRw() |
|
| 248 |
+ if err != nil {
|
|
| 249 |
+ t.Error(err) |
|
| 250 |
+ } |
|
| 251 |
+ img, err := runtime.graph.Create(rwTar, container1, "unit test commited image", "", &Config{Cmd: []string{"cat", "/world"}})
|
|
| 252 |
+ if err != nil {
|
|
| 253 |
+ t.Error(err) |
|
| 254 |
+ } |
|
| 255 |
+ |
|
| 256 |
+ // FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world |
|
| 257 |
+ |
|
| 258 |
+ container2, err := runtime.Create( |
|
| 259 |
+ &Config{
|
|
| 260 |
+ Image: img.Id, |
|
| 261 |
+ }, |
|
| 262 |
+ ) |
|
| 263 |
+ if err != nil {
|
|
| 264 |
+ t.Fatal(err) |
|
| 265 |
+ } |
|
| 266 |
+ defer runtime.Destroy(container2) |
|
| 267 |
+ stdout, err := container2.StdoutPipe() |
|
| 268 |
+ if err != nil {
|
|
| 269 |
+ t.Fatal(err) |
|
| 270 |
+ } |
|
| 271 |
+ stderr, err := container2.StderrPipe() |
|
| 272 |
+ if err != nil {
|
|
| 273 |
+ t.Fatal(err) |
|
| 274 |
+ } |
|
| 275 |
+ if err := container2.Start(); err != nil {
|
|
| 276 |
+ t.Fatal(err) |
|
| 277 |
+ } |
|
| 278 |
+ container2.Wait() |
|
| 279 |
+ output, err := ioutil.ReadAll(stdout) |
|
| 280 |
+ if err != nil {
|
|
| 281 |
+ t.Fatal(err) |
|
| 282 |
+ } |
|
| 283 |
+ output2, err := ioutil.ReadAll(stderr) |
|
| 284 |
+ if err != nil {
|
|
| 285 |
+ t.Fatal(err) |
|
| 286 |
+ } |
|
| 287 |
+ if err := stdout.Close(); err != nil {
|
|
| 288 |
+ t.Fatal(err) |
|
| 289 |
+ } |
|
| 290 |
+ if err := stderr.Close(); err != nil {
|
|
| 291 |
+ t.Fatal(err) |
|
| 292 |
+ } |
|
| 293 |
+ if string(output) != "hello\n" {
|
|
| 294 |
+ t.Fatalf("Unexpected output. Expected %s, received: %s (err: %s)", "hello\n", output, output2)
|
|
| 295 |
+ } |
|
| 296 |
+} |
|
| 297 |
+ |
|
| 220 | 298 |
func TestCommitRun(t *testing.T) {
|
| 221 | 299 |
runtime, err := newTestRuntime() |
| 222 | 300 |
if err != nil {
|
| ... | ... |
@@ -248,7 +326,7 @@ func TestCommitRun(t *testing.T) {
|
| 248 | 248 |
if err != nil {
|
| 249 | 249 |
t.Error(err) |
| 250 | 250 |
} |
| 251 |
- img, err := runtime.graph.Create(rwTar, container1, "unit test commited image", "") |
|
| 251 |
+ img, err := runtime.graph.Create(rwTar, container1, "unit test commited image", "", nil) |
|
| 252 | 252 |
if err != nil {
|
| 253 | 253 |
t.Error(err) |
| 254 | 254 |
} |
| ... | ... |
@@ -84,13 +84,14 @@ func (graph *Graph) Get(name string) (*Image, error) {
|
| 84 | 84 |
} |
| 85 | 85 |
|
| 86 | 86 |
// Create creates a new image and registers it in the graph. |
| 87 |
-func (graph *Graph) Create(layerData Archive, container *Container, comment, author string) (*Image, error) {
|
|
| 87 |
+func (graph *Graph) Create(layerData Archive, container *Container, comment, author string, config *Config) (*Image, error) {
|
|
| 88 | 88 |
img := &Image{
|
| 89 | 89 |
Id: GenerateId(), |
| 90 | 90 |
Comment: comment, |
| 91 | 91 |
Created: time.Now(), |
| 92 | 92 |
DockerVersion: VERSION, |
| 93 | 93 |
Author: author, |
| 94 |
+ Config: config, |
|
| 94 | 95 |
} |
| 95 | 96 |
if container != nil {
|
| 96 | 97 |
img.Parent = container.Image |
| ... | ... |
@@ -62,7 +62,7 @@ func TestGraphCreate(t *testing.T) {
|
| 62 | 62 |
if err != nil {
|
| 63 | 63 |
t.Fatal(err) |
| 64 | 64 |
} |
| 65 |
- image, err := graph.Create(archive, nil, "Testing", "") |
|
| 65 |
+ image, err := graph.Create(archive, nil, "Testing", "", nil) |
|
| 66 | 66 |
if err != nil {
|
| 67 | 67 |
t.Fatal(err) |
| 68 | 68 |
} |
| ... | ... |
@@ -122,7 +122,7 @@ func TestMount(t *testing.T) {
|
| 122 | 122 |
if err != nil {
|
| 123 | 123 |
t.Fatal(err) |
| 124 | 124 |
} |
| 125 |
- image, err := graph.Create(archive, nil, "Testing", "") |
|
| 125 |
+ image, err := graph.Create(archive, nil, "Testing", "", nil) |
|
| 126 | 126 |
if err != nil {
|
| 127 | 127 |
t.Fatal(err) |
| 128 | 128 |
} |
| ... | ... |
@@ -166,7 +166,7 @@ func createTestImage(graph *Graph, t *testing.T) *Image {
|
| 166 | 166 |
if err != nil {
|
| 167 | 167 |
t.Fatal(err) |
| 168 | 168 |
} |
| 169 |
- img, err := graph.Create(archive, nil, "Test image", "") |
|
| 169 |
+ img, err := graph.Create(archive, nil, "Test image", "", nil) |
|
| 170 | 170 |
if err != nil {
|
| 171 | 171 |
t.Fatal(err) |
| 172 | 172 |
} |
| ... | ... |
@@ -181,7 +181,7 @@ func TestDelete(t *testing.T) {
|
| 181 | 181 |
t.Fatal(err) |
| 182 | 182 |
} |
| 183 | 183 |
assertNImages(graph, t, 0) |
| 184 |
- img, err := graph.Create(archive, nil, "Bla bla", "") |
|
| 184 |
+ img, err := graph.Create(archive, nil, "Bla bla", "", nil) |
|
| 185 | 185 |
if err != nil {
|
| 186 | 186 |
t.Fatal(err) |
| 187 | 187 |
} |
| ... | ... |
@@ -192,11 +192,11 @@ func TestDelete(t *testing.T) {
|
| 192 | 192 |
assertNImages(graph, t, 0) |
| 193 | 193 |
|
| 194 | 194 |
// Test 2 create (same name) / 1 delete |
| 195 |
- img1, err := graph.Create(archive, nil, "Testing", "") |
|
| 195 |
+ img1, err := graph.Create(archive, nil, "Testing", "", nil) |
|
| 196 | 196 |
if err != nil {
|
| 197 | 197 |
t.Fatal(err) |
| 198 | 198 |
} |
| 199 |
- if _, err = graph.Create(archive, nil, "Testing", ""); err != nil {
|
|
| 199 |
+ if _, err = graph.Create(archive, nil, "Testing", "", nil); err != nil {
|
|
| 200 | 200 |
t.Fatal(err) |
| 201 | 201 |
} |
| 202 | 202 |
assertNImages(graph, t, 2) |
| ... | ... |
@@ -78,12 +78,58 @@ func (runtime *Runtime) containerRoot(id string) string {
|
| 78 | 78 |
return path.Join(runtime.repository, id) |
| 79 | 79 |
} |
| 80 | 80 |
|
| 81 |
+func (runtime *Runtime) mergeConfig(userConf, imageConf *Config) {
|
|
| 82 |
+ if userConf.Hostname != "" {
|
|
| 83 |
+ userConf.Hostname = imageConf.Hostname |
|
| 84 |
+ } |
|
| 85 |
+ if userConf.User != "" {
|
|
| 86 |
+ userConf.User = imageConf.User |
|
| 87 |
+ } |
|
| 88 |
+ if userConf.Memory == 0 {
|
|
| 89 |
+ userConf.Memory = imageConf.Memory |
|
| 90 |
+ } |
|
| 91 |
+ if userConf.MemorySwap == 0 {
|
|
| 92 |
+ userConf.MemorySwap = imageConf.MemorySwap |
|
| 93 |
+ } |
|
| 94 |
+ if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 {
|
|
| 95 |
+ userConf.PortSpecs = imageConf.PortSpecs |
|
| 96 |
+ } |
|
| 97 |
+ if !userConf.Tty {
|
|
| 98 |
+ userConf.Tty = userConf.Tty |
|
| 99 |
+ } |
|
| 100 |
+ if !userConf.OpenStdin {
|
|
| 101 |
+ userConf.OpenStdin = imageConf.OpenStdin |
|
| 102 |
+ } |
|
| 103 |
+ if !userConf.StdinOnce {
|
|
| 104 |
+ userConf.StdinOnce = imageConf.StdinOnce |
|
| 105 |
+ } |
|
| 106 |
+ if userConf.Env == nil || len(userConf.Env) == 0 {
|
|
| 107 |
+ userConf.Env = imageConf.Env |
|
| 108 |
+ } |
|
| 109 |
+ if userConf.Cmd == nil || len(userConf.Cmd) == 0 {
|
|
| 110 |
+ userConf.Cmd = imageConf.Cmd |
|
| 111 |
+ } |
|
| 112 |
+ if userConf.Dns == nil || len(userConf.Dns) == 0 {
|
|
| 113 |
+ userConf.Dns = imageConf.Dns |
|
| 114 |
+ } |
|
| 115 |
+} |
|
| 116 |
+ |
|
| 81 | 117 |
func (runtime *Runtime) Create(config *Config) (*Container, error) {
|
| 118 |
+ |
|
| 82 | 119 |
// Lookup image |
| 83 | 120 |
img, err := runtime.repositories.LookupImage(config.Image) |
| 84 | 121 |
if err != nil {
|
| 85 | 122 |
return nil, err |
| 86 | 123 |
} |
| 124 |
+ |
|
| 125 |
+ if img.Config != nil {
|
|
| 126 |
+ runtime.mergeConfig(config, img.Config) |
|
| 127 |
+ } |
|
| 128 |
+ |
|
| 129 |
+ if config.Cmd == nil {
|
|
| 130 |
+ return nil, fmt.Errorf("No command specified")
|
|
| 131 |
+ } |
|
| 132 |
+ |
|
| 87 | 133 |
// Generate id |
| 88 | 134 |
id := GenerateId() |
| 89 | 135 |
// Generate default hostname |
| ... | ... |
@@ -104,6 +150,7 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) {
|
| 104 | 104 |
// FIXME: do we need to store this in the container? |
| 105 | 105 |
SysInitPath: sysInitPath, |
| 106 | 106 |
} |
| 107 |
+ |
|
| 107 | 108 |
container.root = runtime.containerRoot(container.Id) |
| 108 | 109 |
// Step 1: create the container directory. |
| 109 | 110 |
// This doubles as a barrier to avoid race conditions. |
| ... | ... |
@@ -265,7 +312,7 @@ func (runtime *Runtime) Destroy(container *Container) error {
|
| 265 | 265 |
|
| 266 | 266 |
// Commit creates a new filesystem image from the current state of a container. |
| 267 | 267 |
// The image can optionally be tagged into a repository |
| 268 |
-func (runtime *Runtime) Commit(id, repository, tag, comment, author string) (*Image, error) {
|
|
| 268 |
+func (runtime *Runtime) Commit(id, repository, tag, comment, author string, config *Config) (*Image, error) {
|
|
| 269 | 269 |
container := runtime.Get(id) |
| 270 | 270 |
if container == nil {
|
| 271 | 271 |
return nil, fmt.Errorf("No such container: %s", id)
|
| ... | ... |
@@ -277,7 +324,7 @@ func (runtime *Runtime) Commit(id, repository, tag, comment, author string) (*Im |
| 277 | 277 |
return nil, err |
| 278 | 278 |
} |
| 279 | 279 |
// Create a new image from the container's base layers + a new layer from container changes |
| 280 |
- img, err := runtime.graph.Create(rwTar, container, comment, author) |
|
| 280 |
+ img, err := runtime.graph.Create(rwTar, container, comment, author, config) |
|
| 281 | 281 |
if err != nil {
|
| 282 | 282 |
return nil, err |
| 283 | 283 |
} |