Browse code

+ Commit default runtime options with a layer

Solomon Hykes authored on 2013/05/02 03:33:21
Showing 6 changed files
... ...
@@ -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)
... ...
@@ -24,6 +24,7 @@ type Image struct {
24 24
 	ContainerConfig Config    `json:"container_config,omitempty"`
25 25
 	DockerVersion   string    `json:"docker_version,omitempty"`
26 26
 	Author          string    `json:"author,omitempty"`
27
+	Config          *Config   `json:"config,omitempty"`
27 28
 	graph           *Graph
28 29
 }
29 30
 
... ...
@@ -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
 	}