Browse code

Merge pull request #12253 from calavera/remove_job_from_start_and_create

Remove engine.Job from Start and Create

Brian Goff authored on 2015/04/16 10:49:25
Showing 23 changed files
... ...
@@ -33,6 +33,7 @@ import (
33 33
 	"github.com/docker/docker/pkg/streamformatter"
34 34
 	"github.com/docker/docker/pkg/version"
35 35
 	"github.com/docker/docker/registry"
36
+	"github.com/docker/docker/runconfig"
36 37
 	"github.com/docker/docker/utils"
37 38
 )
38 39
 
... ...
@@ -809,30 +810,23 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re
809 809
 		return err
810 810
 	}
811 811
 	var (
812
-		job          = eng.Job("create", r.Form.Get("name"))
813
-		outWarnings  []string
814
-		stdoutBuffer = bytes.NewBuffer(nil)
815
-		warnings     = bytes.NewBuffer(nil)
812
+		warnings []string
813
+		name     = r.Form.Get("name")
816 814
 	)
817 815
 
818
-	if err := job.DecodeEnv(r.Body); err != nil {
816
+	config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
817
+	if err != nil {
819 818
 		return err
820 819
 	}
821
-	// Read container ID from the first line of stdout
822
-	job.Stdout.Add(stdoutBuffer)
823
-	// Read warnings from stderr
824
-	job.Stderr.Add(warnings)
825
-	if err := job.Run(); err != nil {
820
+
821
+	containerId, warnings, err := getDaemon(eng).ContainerCreate(name, config, hostConfig)
822
+	if err != nil {
826 823
 		return err
827 824
 	}
828
-	// Parse warnings from stderr
829
-	scanner := bufio.NewScanner(warnings)
830
-	for scanner.Scan() {
831
-		outWarnings = append(outWarnings, scanner.Text())
832
-	}
825
+
833 826
 	return writeJSON(w, http.StatusCreated, &types.ContainerCreateResponse{
834
-		ID:       engine.Tail(stdoutBuffer, 1),
835
-		Warnings: outWarnings,
827
+		ID:       containerId,
828
+		Warnings: warnings,
836 829
 	})
837 830
 }
838 831
 
... ...
@@ -924,10 +918,6 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res
924 924
 	if vars == nil {
925 925
 		return fmt.Errorf("Missing parameter")
926 926
 	}
927
-	var (
928
-		name = vars["name"]
929
-		job  = eng.Job("start", name)
930
-	)
931 927
 
932 928
 	// If contentLength is -1, we can assumed chunked encoding
933 929
 	// or more technically that the length is unknown
... ...
@@ -935,17 +925,21 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res
935 935
 	// net/http otherwise seems to swallow any headers related to chunked encoding
936 936
 	// including r.TransferEncoding
937 937
 	// allow a nil body for backwards compatibility
938
+	var hostConfig *runconfig.HostConfig
938 939
 	if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) {
939 940
 		if err := checkForJson(r); err != nil {
940 941
 			return err
941 942
 		}
942 943
 
943
-		if err := job.DecodeEnv(r.Body); err != nil {
944
+		c, err := runconfig.DecodeHostConfig(r.Body)
945
+		if err != nil {
944 946
 			return err
945 947
 		}
948
+
949
+		hostConfig = c
946 950
 	}
947 951
 
948
-	if err := job.Run(); err != nil {
952
+	if err := getDaemon(eng).ContainerStart(vars["name"], hostConfig); err != nil {
949 953
 		if err.Error() == "Container already started" {
950 954
 			w.WriteHeader(http.StatusNotModified)
951 955
 			return nil
... ...
@@ -262,7 +262,7 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
262 262
 	b.Config.Cmd = config.Cmd
263 263
 	runconfig.Merge(b.Config, config)
264 264
 
265
-	defer func(cmd []string) { b.Config.Cmd = cmd }(cmd)
265
+	defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd)
266 266
 
267 267
 	logrus.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd)
268 268
 
... ...
@@ -301,13 +301,15 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
301 301
 // Argument handling is the same as RUN.
302 302
 //
303 303
 func cmd(b *Builder, args []string, attributes map[string]bool, original string) error {
304
-	b.Config.Cmd = handleJsonArgs(args, attributes)
304
+	cmdSlice := handleJsonArgs(args, attributes)
305 305
 
306 306
 	if !attributes["json"] {
307
-		b.Config.Cmd = append([]string{"/bin/sh", "-c"}, b.Config.Cmd...)
307
+		cmdSlice = append([]string{"/bin/sh", "-c"}, cmdSlice...)
308 308
 	}
309 309
 
310
-	if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %q", b.Config.Cmd)); err != nil {
310
+	b.Config.Cmd = runconfig.NewCommand(cmdSlice...)
311
+
312
+	if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
311 313
 		return err
312 314
 	}
313 315
 
... ...
@@ -332,13 +334,13 @@ func entrypoint(b *Builder, args []string, attributes map[string]bool, original
332 332
 	switch {
333 333
 	case attributes["json"]:
334 334
 		// ENTRYPOINT ["echo", "hi"]
335
-		b.Config.Entrypoint = parsed
335
+		b.Config.Entrypoint = runconfig.NewEntrypoint(parsed...)
336 336
 	case len(parsed) == 0:
337 337
 		// ENTRYPOINT []
338 338
 		b.Config.Entrypoint = nil
339 339
 	default:
340 340
 		// ENTRYPOINT echo hi
341
-		b.Config.Entrypoint = []string{"/bin/sh", "-c", parsed[0]}
341
+		b.Config.Entrypoint = runconfig.NewEntrypoint("/bin/sh", "-c", parsed[0])
342 342
 	}
343 343
 
344 344
 	// when setting the entrypoint if a CMD was not explicitly set then
... ...
@@ -61,7 +61,7 @@ func (b *Builder) readContext(context io.Reader) error {
61 61
 	return nil
62 62
 }
63 63
 
64
-func (b *Builder) commit(id string, autoCmd []string, comment string) error {
64
+func (b *Builder) commit(id string, autoCmd *runconfig.Command, comment string) error {
65 65
 	if b.disableCommit {
66 66
 		return nil
67 67
 	}
... ...
@@ -71,8 +71,8 @@ func (b *Builder) commit(id string, autoCmd []string, comment string) error {
71 71
 	b.Config.Image = b.image
72 72
 	if id == "" {
73 73
 		cmd := b.Config.Cmd
74
-		b.Config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
75
-		defer func(cmd []string) { b.Config.Cmd = cmd }(cmd)
74
+		b.Config.Cmd = runconfig.NewCommand("/bin/sh", "-c", "#(nop) "+comment)
75
+		defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd)
76 76
 
77 77
 		hit, err := b.probeCache()
78 78
 		if err != nil {
... ...
@@ -182,8 +182,8 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowDecomp
182 182
 	}
183 183
 
184 184
 	cmd := b.Config.Cmd
185
-	b.Config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest)}
186
-	defer func(cmd []string) { b.Config.Cmd = cmd }(cmd)
185
+	b.Config.Cmd = runconfig.NewCommand("/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest))
186
+	defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd)
187 187
 
188 188
 	hit, err := b.probeCache()
189 189
 	if err != nil {
... ...
@@ -560,12 +560,13 @@ func (b *Builder) create() (*daemon.Container, error) {
560 560
 	b.TmpContainers[c.ID] = struct{}{}
561 561
 	fmt.Fprintf(b.OutStream, " ---> Running in %s\n", stringid.TruncateID(c.ID))
562 562
 
563
-	if len(config.Cmd) > 0 {
563
+	if config.Cmd.Len() > 0 {
564 564
 		// override the entry point that may have been picked up from the base image
565
-		c.Path = config.Cmd[0]
566
-		c.Args = config.Cmd[1:]
565
+		s := config.Cmd.Slice()
566
+		c.Path = s[0]
567
+		c.Args = s[1:]
567 568
 	} else {
568
-		config.Cmd = []string{}
569
+		config.Cmd = runconfig.NewCommand()
569 570
 	}
570 571
 
571 572
 	return c, nil
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"fmt"
5 5
 	"strings"
6 6
 
7
-	"github.com/docker/docker/engine"
8 7
 	"github.com/docker/docker/graph"
9 8
 	"github.com/docker/docker/image"
10 9
 	"github.com/docker/docker/pkg/parsers"
... ...
@@ -12,36 +11,28 @@ import (
12 12
 	"github.com/docker/libcontainer/label"
13 13
 )
14 14
 
15
-func (daemon *Daemon) ContainerCreate(job *engine.Job) error {
16
-	var name string
17
-	if len(job.Args) == 1 {
18
-		name = job.Args[0]
19
-	} else if len(job.Args) > 1 {
20
-		return fmt.Errorf("Usage: %s", job.Name)
21
-	}
22
-
23
-	config := runconfig.ContainerConfigFromJob(job)
24
-	hostConfig := runconfig.ContainerHostConfigFromJob(job)
15
+func (daemon *Daemon) ContainerCreate(name string, config *runconfig.Config, hostConfig *runconfig.HostConfig) (string, []string, error) {
16
+	var warnings []string
25 17
 
26
-	if len(hostConfig.LxcConf) > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") {
27
-		return fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name())
18
+	if hostConfig.LxcConf.Len() > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") {
19
+		return "", warnings, fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name())
28 20
 	}
29 21
 	if hostConfig.Memory != 0 && hostConfig.Memory < 4194304 {
30
-		return fmt.Errorf("Minimum memory limit allowed is 4MB")
22
+		return "", warnings, fmt.Errorf("Minimum memory limit allowed is 4MB")
31 23
 	}
32 24
 	if hostConfig.Memory > 0 && !daemon.SystemConfig().MemoryLimit {
33
-		job.Errorf("Your kernel does not support memory limit capabilities. Limitation discarded.\n")
25
+		warnings = append(warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.\n")
34 26
 		hostConfig.Memory = 0
35 27
 	}
36 28
 	if hostConfig.Memory > 0 && hostConfig.MemorySwap != -1 && !daemon.SystemConfig().SwapLimit {
37
-		job.Errorf("Your kernel does not support swap limit capabilities. Limitation discarded.\n")
29
+		warnings = append(warnings, "Your kernel does not support swap limit capabilities. Limitation discarded.\n")
38 30
 		hostConfig.MemorySwap = -1
39 31
 	}
40 32
 	if hostConfig.Memory > 0 && hostConfig.MemorySwap > 0 && hostConfig.MemorySwap < hostConfig.Memory {
41
-		return fmt.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.\n")
33
+		return "", warnings, fmt.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.\n")
42 34
 	}
43 35
 	if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 {
44
-		return fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.\n")
36
+		return "", warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.\n")
45 37
 	}
46 38
 
47 39
 	container, buildWarnings, err := daemon.Create(config, hostConfig, name)
... ...
@@ -51,22 +42,18 @@ func (daemon *Daemon) ContainerCreate(job *engine.Job) error {
51 51
 			if tag == "" {
52 52
 				tag = graph.DEFAULTTAG
53 53
 			}
54
-			return fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag)
54
+			return "", warnings, fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag)
55 55
 		}
56
-		return err
56
+		return "", warnings, err
57 57
 	}
58 58
 	if !container.Config.NetworkDisabled && daemon.SystemConfig().IPv4ForwardingDisabled {
59
-		job.Errorf("IPv4 forwarding is disabled.\n")
59
+		warnings = append(warnings, "IPv4 forwarding is disabled.\n")
60 60
 	}
61
-	container.LogEvent("create")
62 61
 
63
-	job.Printf("%s\n", container.ID)
64
-
65
-	for _, warning := range buildWarnings {
66
-		job.Errorf("%s\n", warning)
67
-	}
62
+	container.LogEvent("create")
63
+	warnings = append(warnings, buildWarnings...)
68 64
 
69
-	return nil
65
+	return container.ID, warnings, nil
70 66
 }
71 67
 
72 68
 // Create creates a new container from the given configuration with a given name.
... ...
@@ -119,10 +119,8 @@ type Daemon struct {
119 119
 func (daemon *Daemon) Install(eng *engine.Engine) error {
120 120
 	for name, method := range map[string]engine.Handler{
121 121
 		"container_inspect": daemon.ContainerInspect,
122
-		"create":            daemon.ContainerCreate,
123 122
 		"info":              daemon.CmdInfo,
124 123
 		"restart":           daemon.ContainerRestart,
125
-		"start":             daemon.ContainerStart,
126 124
 		"execCreate":        daemon.ContainerExecCreate,
127 125
 		"execStart":         daemon.ContainerExecStart,
128 126
 	} {
... ...
@@ -483,7 +481,7 @@ func (daemon *Daemon) mergeAndVerifyConfig(config *runconfig.Config, img *image.
483 483
 			return nil, err
484 484
 		}
485 485
 	}
486
-	if len(config.Entrypoint) == 0 && len(config.Cmd) == 0 {
486
+	if config.Entrypoint.Len() == 0 && config.Cmd.Len() == 0 {
487 487
 		return nil, fmt.Errorf("No command specified")
488 488
 	}
489 489
 	return warnings, nil
... ...
@@ -575,17 +573,20 @@ func (daemon *Daemon) generateHostname(id string, config *runconfig.Config) {
575 575
 	}
576 576
 }
577 577
 
578
-func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint, configCmd []string) (string, []string) {
578
+func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint *runconfig.Entrypoint, configCmd *runconfig.Command) (string, []string) {
579 579
 	var (
580 580
 		entrypoint string
581 581
 		args       []string
582 582
 	)
583
-	if len(configEntrypoint) != 0 {
584
-		entrypoint = configEntrypoint[0]
585
-		args = append(configEntrypoint[1:], configCmd...)
583
+
584
+	cmdSlice := configCmd.Slice()
585
+	if configEntrypoint.Len() != 0 {
586
+		eSlice := configEntrypoint.Slice()
587
+		entrypoint = eSlice[0]
588
+		args = append(eSlice[1:], cmdSlice...)
586 589
 	} else {
587
-		entrypoint = configCmd[0]
588
-		args = configCmd[1:]
590
+		entrypoint = cmdSlice[0]
591
+		args = cmdSlice[1:]
589 592
 	}
590 593
 	return entrypoint, args
591 594
 }
... ...
@@ -132,7 +132,8 @@ func (d *Daemon) ContainerExecCreate(job *engine.Job) error {
132 132
 		return err
133 133
 	}
134 134
 
135
-	entrypoint, args := d.getEntrypointAndArgs(nil, config.Cmd)
135
+	cmd := runconfig.NewCommand(config.Cmd...)
136
+	entrypoint, args := d.getEntrypointAndArgs(runconfig.NewEntrypoint(), cmd)
136 137
 
137 138
 	processConfig := execdriver.ProcessConfig{
138 139
 		Tty:        config.Tty,
... ...
@@ -3,18 +3,10 @@ package daemon
3 3
 import (
4 4
 	"fmt"
5 5
 
6
-	"github.com/docker/docker/engine"
7 6
 	"github.com/docker/docker/runconfig"
8 7
 )
9 8
 
10
-func (daemon *Daemon) ContainerStart(job *engine.Job) error {
11
-	if len(job.Args) < 1 {
12
-		return fmt.Errorf("Usage: %s container_id", job.Name)
13
-	}
14
-	var (
15
-		name = job.Args[0]
16
-	)
17
-
9
+func (daemon *Daemon) ContainerStart(name string, hostConfig *runconfig.HostConfig) error {
18 10
 	container, err := daemon.Get(name)
19 11
 	if err != nil {
20 12
 		return err
... ...
@@ -28,15 +20,14 @@ func (daemon *Daemon) ContainerStart(job *engine.Job) error {
28 28
 		return fmt.Errorf("Container already started")
29 29
 	}
30 30
 
31
-	// If no environment was set, then no hostconfig was passed.
32 31
 	// This is kept for backward compatibility - hostconfig should be passed when
33 32
 	// creating a container, not during start.
34
-	if len(job.Environ()) > 0 {
35
-		hostConfig := runconfig.ContainerHostConfigFromJob(job)
33
+	if hostConfig != nil {
36 34
 		if err := daemon.setHostConfig(container, hostConfig); err != nil {
37 35
 			return err
38 36
 		}
39 37
 	}
38
+
40 39
 	if err := container.Start(); err != nil {
41 40
 		container.LogEvent("die")
42 41
 		return fmt.Errorf("Cannot start container %s: %s", name, err)
... ...
@@ -42,7 +42,8 @@ func mergeLxcConfIntoOptions(hostConfig *runconfig.HostConfig) ([]string, error)
42 42
 
43 43
 	// merge in the lxc conf options into the generic config map
44 44
 	if lxcConf := hostConfig.LxcConf; lxcConf != nil {
45
-		for _, pair := range lxcConf {
45
+		lxSlice := lxcConf.Slice()
46
+		for _, pair := range lxSlice {
46 47
 			// because lxc conf gets the driver name lxc.XXXX we need to trim it off
47 48
 			// and let the lxc driver add it back later if needed
48 49
 			if !strings.Contains(pair.Key, ".") {
... ...
@@ -7,10 +7,11 @@ import (
7 7
 )
8 8
 
9 9
 func TestMergeLxcConfig(t *testing.T) {
10
+	kv := []runconfig.KeyValuePair{
11
+		{"lxc.cgroups.cpuset", "1,2"},
12
+	}
10 13
 	hostConfig := &runconfig.HostConfig{
11
-		LxcConf: []runconfig.KeyValuePair{
12
-			{Key: "lxc.cgroups.cpuset", Value: "1,2"},
13
-		},
14
+		LxcConf: runconfig.NewLxcConfig(kv),
14 15
 	}
15 16
 
16 17
 	out, err := mergeLxcConfIntoOptions(hostConfig)
... ...
@@ -31,7 +31,7 @@ func (s *TagStore) History(name string) ([]*types.ImageHistory, error) {
31 31
 		history = append(history, &types.ImageHistory{
32 32
 			ID:        img.ID,
33 33
 			Created:   img.Created.Unix(),
34
-			CreatedBy: strings.Join(img.ContainerConfig.Cmd, " "),
34
+			CreatedBy: strings.Join(img.ContainerConfig.Cmd.Slice(), " "),
35 35
 			Tags:      lookupMap[img.ID],
36 36
 			Size:      img.Size,
37 37
 			Comment:   img.Comment,
... ...
@@ -91,7 +91,7 @@ func TestGetContainersTop(t *testing.T) {
91 91
 	containerID := createTestContainer(eng,
92 92
 		&runconfig.Config{
93 93
 			Image:     unitTestImageID,
94
-			Cmd:       []string{"/bin/sh", "-c", "cat"},
94
+			Cmd:       runconfig.NewCommand("/bin/sh", "-c", "cat"),
95 95
 			OpenStdin: true,
96 96
 		},
97 97
 		t,
... ...
@@ -168,7 +168,7 @@ func TestPostCommit(t *testing.T) {
168 168
 	containerID := createTestContainer(eng,
169 169
 		&runconfig.Config{
170 170
 			Image: unitTestImageID,
171
-			Cmd:   []string{"touch", "/test"},
171
+			Cmd:   runconfig.NewCommand("touch", "/test"),
172 172
 		},
173 173
 		t,
174 174
 	)
... ...
@@ -201,9 +201,8 @@ func TestPostContainersCreate(t *testing.T) {
201 201
 	defer mkDaemonFromEngine(eng, t).Nuke()
202 202
 
203 203
 	configJSON, err := json.Marshal(&runconfig.Config{
204
-		Image:  unitTestImageID,
205
-		Memory: 33554432,
206
-		Cmd:    []string{"touch", "/test"},
204
+		Image: unitTestImageID,
205
+		Cmd:   runconfig.NewCommand("touch", "/test"),
207 206
 	})
208 207
 	if err != nil {
209 208
 		t.Fatal(err)
... ...
@@ -242,9 +241,8 @@ func TestPostJsonVerify(t *testing.T) {
242 242
 	defer mkDaemonFromEngine(eng, t).Nuke()
243 243
 
244 244
 	configJSON, err := json.Marshal(&runconfig.Config{
245
-		Image:  unitTestImageID,
246
-		Memory: 33554432,
247
-		Cmd:    []string{"touch", "/test"},
245
+		Image: unitTestImageID,
246
+		Cmd:   runconfig.NewCommand("touch", "/test"),
248 247
 	})
249 248
 	if err != nil {
250 249
 		t.Fatal(err)
... ...
@@ -330,8 +328,8 @@ func TestPostCreateNull(t *testing.T) {
330 330
 	containerAssertExists(eng, containerID, t)
331 331
 
332 332
 	c, _ := daemon.Get(containerID)
333
-	if c.Config.Cpuset != "" {
334
-		t.Fatalf("Cpuset should have been empty - instead its:" + c.Config.Cpuset)
333
+	if c.HostConfig().CpusetCpus != "" {
334
+		t.Fatalf("Cpuset should have been empty - instead its:" + c.HostConfig().CpusetCpus)
335 335
 	}
336 336
 }
337 337
 
... ...
@@ -342,7 +340,7 @@ func TestPostContainersKill(t *testing.T) {
342 342
 	containerID := createTestContainer(eng,
343 343
 		&runconfig.Config{
344 344
 			Image:     unitTestImageID,
345
-			Cmd:       []string{"/bin/cat"},
345
+			Cmd:       runconfig.NewCommand("/bin/cat"),
346 346
 			OpenStdin: true,
347 347
 		},
348 348
 		t,
... ...
@@ -379,7 +377,7 @@ func TestPostContainersRestart(t *testing.T) {
379 379
 	containerID := createTestContainer(eng,
380 380
 		&runconfig.Config{
381 381
 			Image:     unitTestImageID,
382
-			Cmd:       []string{"/bin/top"},
382
+			Cmd:       runconfig.NewCommand("/bin/top"),
383 383
 			OpenStdin: true,
384 384
 		},
385 385
 		t,
... ...
@@ -423,7 +421,7 @@ func TestPostContainersStart(t *testing.T) {
423 423
 		eng,
424 424
 		&runconfig.Config{
425 425
 			Image:     unitTestImageID,
426
-			Cmd:       []string{"/bin/cat"},
426
+			Cmd:       runconfig.NewCommand("/bin/cat"),
427 427
 			OpenStdin: true,
428 428
 		},
429 429
 		t,
... ...
@@ -473,7 +471,7 @@ func TestPostContainersStop(t *testing.T) {
473 473
 	containerID := createTestContainer(eng,
474 474
 		&runconfig.Config{
475 475
 			Image:     unitTestImageID,
476
-			Cmd:       []string{"/bin/top"},
476
+			Cmd:       runconfig.NewCommand("/bin/top"),
477 477
 			OpenStdin: true,
478 478
 		},
479 479
 		t,
... ...
@@ -525,7 +523,7 @@ func TestPostContainersWait(t *testing.T) {
525 525
 	containerID := createTestContainer(eng,
526 526
 		&runconfig.Config{
527 527
 			Image:     unitTestImageID,
528
-			Cmd:       []string{"/bin/sleep", "1"},
528
+			Cmd:       runconfig.NewCommand("/bin/sleep", "1"),
529 529
 			OpenStdin: true,
530 530
 		},
531 531
 		t,
... ...
@@ -561,7 +559,7 @@ func TestPostContainersAttach(t *testing.T) {
561 561
 	containerID := createTestContainer(eng,
562 562
 		&runconfig.Config{
563 563
 			Image:     unitTestImageID,
564
-			Cmd:       []string{"/bin/cat"},
564
+			Cmd:       runconfig.NewCommand("/bin/cat"),
565 565
 			OpenStdin: true,
566 566
 		},
567 567
 		t,
... ...
@@ -637,7 +635,7 @@ func TestPostContainersAttachStderr(t *testing.T) {
637 637
 	containerID := createTestContainer(eng,
638 638
 		&runconfig.Config{
639 639
 			Image:     unitTestImageID,
640
-			Cmd:       []string{"/bin/sh", "-c", "/bin/cat >&2"},
640
+			Cmd:       runconfig.NewCommand("/bin/sh", "-c", "/bin/cat >&2"),
641 641
 			OpenStdin: true,
642 642
 		},
643 643
 		t,
... ...
@@ -818,7 +816,7 @@ func TestPostContainersCopy(t *testing.T) {
818 818
 	containerID := createTestContainer(eng,
819 819
 		&runconfig.Config{
820 820
 			Image: unitTestImageID,
821
-			Cmd:   []string{"touch", "/test.txt"},
821
+			Cmd:   runconfig.NewCommand("touch", "/test.txt"),
822 822
 		},
823 823
 		t,
824 824
 	)
... ...
@@ -14,7 +14,7 @@ func TestRestartStdin(t *testing.T) {
14 14
 	defer nuke(daemon)
15 15
 	container, _, err := daemon.Create(&runconfig.Config{
16 16
 		Image: GetTestImage(daemon).ID,
17
-		Cmd:   []string{"cat"},
17
+		Cmd:   runconfig.NewCommand("cat"),
18 18
 
19 19
 		OpenStdin: true,
20 20
 	},
... ...
@@ -79,7 +79,7 @@ func TestStdin(t *testing.T) {
79 79
 	defer nuke(daemon)
80 80
 	container, _, err := daemon.Create(&runconfig.Config{
81 81
 		Image: GetTestImage(daemon).ID,
82
-		Cmd:   []string{"cat"},
82
+		Cmd:   runconfig.NewCommand("cat"),
83 83
 
84 84
 		OpenStdin: true,
85 85
 	},
... ...
@@ -119,7 +119,7 @@ func TestTty(t *testing.T) {
119 119
 	defer nuke(daemon)
120 120
 	container, _, err := daemon.Create(&runconfig.Config{
121 121
 		Image: GetTestImage(daemon).ID,
122
-		Cmd:   []string{"cat"},
122
+		Cmd:   runconfig.NewCommand("cat"),
123 123
 
124 124
 		OpenStdin: true,
125 125
 	},
... ...
@@ -160,7 +160,7 @@ func BenchmarkRunSequential(b *testing.B) {
160 160
 	for i := 0; i < b.N; i++ {
161 161
 		container, _, err := daemon.Create(&runconfig.Config{
162 162
 			Image: GetTestImage(daemon).ID,
163
-			Cmd:   []string{"echo", "-n", "foo"},
163
+			Cmd:   runconfig.NewCommand("echo", "-n", "foo"),
164 164
 		},
165 165
 			&runconfig.HostConfig{},
166 166
 			"",
... ...
@@ -194,7 +194,7 @@ func BenchmarkRunParallel(b *testing.B) {
194 194
 		go func(i int, complete chan error) {
195 195
 			container, _, err := daemon.Create(&runconfig.Config{
196 196
 				Image: GetTestImage(daemon).ID,
197
-				Cmd:   []string{"echo", "-n", "foo"},
197
+				Cmd:   runconfig.NewCommand("echo", "-n", "foo"),
198 198
 			},
199 199
 				&runconfig.HostConfig{},
200 200
 				"",
... ...
@@ -255,7 +255,7 @@ func TestDaemonCreate(t *testing.T) {
255 255
 
256 256
 	container, _, err := daemon.Create(&runconfig.Config{
257 257
 		Image: GetTestImage(daemon).ID,
258
-		Cmd:   []string{"ls", "-al"},
258
+		Cmd:   runconfig.NewCommand("ls", "-al"),
259 259
 	},
260 260
 		&runconfig.HostConfig{},
261 261
 		"",
... ...
@@ -296,15 +296,16 @@ func TestDaemonCreate(t *testing.T) {
296 296
 	}
297 297
 
298 298
 	// Test that conflict error displays correct details
299
+	cmd := runconfig.NewCommand("ls", "-al")
299 300
 	testContainer, _, _ := daemon.Create(
300 301
 		&runconfig.Config{
301 302
 			Image: GetTestImage(daemon).ID,
302
-			Cmd:   []string{"ls", "-al"},
303
+			Cmd:   cmd,
303 304
 		},
304 305
 		&runconfig.HostConfig{},
305 306
 		"conflictname",
306 307
 	)
307
-	if _, _, err := daemon.Create(&runconfig.Config{Image: GetTestImage(daemon).ID, Cmd: []string{"ls", "-al"}}, &runconfig.HostConfig{}, testContainer.Name); err == nil || !strings.Contains(err.Error(), stringid.TruncateID(testContainer.ID)) {
308
+	if _, _, err := daemon.Create(&runconfig.Config{Image: GetTestImage(daemon).ID, Cmd: cmd}, &runconfig.HostConfig{}, testContainer.Name); err == nil || !strings.Contains(err.Error(), stringid.TruncateID(testContainer.ID)) {
308 309
 		t.Fatalf("Name conflict error doesn't include the correct short id. Message was: %v", err)
309 310
 	}
310 311
 
... ...
@@ -316,7 +317,7 @@ func TestDaemonCreate(t *testing.T) {
316 316
 	if _, _, err := daemon.Create(
317 317
 		&runconfig.Config{
318 318
 			Image: GetTestImage(daemon).ID,
319
-			Cmd:   []string{},
319
+			Cmd:   runconfig.NewCommand(),
320 320
 		},
321 321
 		&runconfig.HostConfig{},
322 322
 		"",
... ...
@@ -326,7 +327,7 @@ func TestDaemonCreate(t *testing.T) {
326 326
 
327 327
 	config := &runconfig.Config{
328 328
 		Image:     GetTestImage(daemon).ID,
329
-		Cmd:       []string{"/bin/ls"},
329
+		Cmd:       runconfig.NewCommand("/bin/ls"),
330 330
 		PortSpecs: []string{"80"},
331 331
 	}
332 332
 	container, _, err = daemon.Create(config, &runconfig.HostConfig{}, "")
... ...
@@ -339,7 +340,7 @@ func TestDaemonCreate(t *testing.T) {
339 339
 	// test expose 80:8000
340 340
 	container, warnings, err := daemon.Create(&runconfig.Config{
341 341
 		Image:     GetTestImage(daemon).ID,
342
-		Cmd:       []string{"ls", "-al"},
342
+		Cmd:       runconfig.NewCommand("ls", "-al"),
343 343
 		PortSpecs: []string{"80:8000"},
344 344
 	},
345 345
 		&runconfig.HostConfig{},
... ...
@@ -359,7 +360,7 @@ func TestDestroy(t *testing.T) {
359 359
 
360 360
 	container, _, err := daemon.Create(&runconfig.Config{
361 361
 		Image: GetTestImage(daemon).ID,
362
-		Cmd:   []string{"ls", "-al"},
362
+		Cmd:   runconfig.NewCommand("ls", "-al"),
363 363
 	},
364 364
 		&runconfig.HostConfig{},
365 365
 		"")
... ...
@@ -422,14 +423,13 @@ func TestGet(t *testing.T) {
422 422
 
423 423
 func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daemon.Container, string) {
424 424
 	var (
425
-		err          error
426
-		id           string
427
-		outputBuffer = bytes.NewBuffer(nil)
428
-		strPort      string
429
-		eng          = NewTestEngine(t)
430
-		daemon       = mkDaemonFromEngine(eng, t)
431
-		port         = 5554
432
-		p            nat.Port
425
+		err     error
426
+		id      string
427
+		strPort string
428
+		eng     = NewTestEngine(t)
429
+		daemon  = mkDaemonFromEngine(eng, t)
430
+		port    = 5554
431
+		p       nat.Port
433 432
 	)
434 433
 	defer func() {
435 434
 		if err != nil {
... ...
@@ -452,16 +452,14 @@ func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daem
452 452
 		p = nat.Port(fmt.Sprintf("%s/%s", strPort, proto))
453 453
 		ep[p] = struct{}{}
454 454
 
455
-		jobCreate := eng.Job("create")
456
-		jobCreate.Setenv("Image", unitTestImageID)
457
-		jobCreate.SetenvList("Cmd", []string{"sh", "-c", cmd})
458
-		jobCreate.SetenvList("PortSpecs", []string{fmt.Sprintf("%s/%s", strPort, proto)})
459
-		jobCreate.SetenvJson("ExposedPorts", ep)
460
-		jobCreate.Stdout.Add(outputBuffer)
461
-		if err := jobCreate.Run(); err != nil {
462
-			t.Fatal(err)
455
+		c := &runconfig.Config{
456
+			Image:        unitTestImageID,
457
+			Cmd:          runconfig.NewCommand("sh", "-c", cmd),
458
+			PortSpecs:    []string{fmt.Sprintf("%s/%s", strPort, proto)},
459
+			ExposedPorts: ep,
463 460
 		}
464
-		id = engine.Tail(outputBuffer, 1)
461
+
462
+		id, _, err = daemon.ContainerCreate(unitTestImageID, c, &runconfig.HostConfig{})
465 463
 		// FIXME: this relies on the undocumented behavior of daemon.Create
466 464
 		// which will return a nil error AND container if the exposed ports
467 465
 		// are invalid. That behavior should be fixed!
... ...
@@ -472,15 +470,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daem
472 472
 
473 473
 	}
474 474
 
475
-	jobStart := eng.Job("start", id)
476
-	portBindings := make(map[nat.Port][]nat.PortBinding)
477
-	portBindings[p] = []nat.PortBinding{
478
-		{},
479
-	}
480
-	if err := jobStart.SetenvJson("PortsBindings", portBindings); err != nil {
481
-		t.Fatal(err)
482
-	}
483
-	if err := jobStart.Run(); err != nil {
475
+	if err := daemon.ContainerStart(id, &runconfig.HostConfig{}); err != nil {
484 476
 		t.Fatal(err)
485 477
 	}
486 478
 
... ...
@@ -731,20 +721,15 @@ func TestContainerNameValidation(t *testing.T) {
731 731
 			t.Fatal(err)
732 732
 		}
733 733
 
734
-		var outputBuffer = bytes.NewBuffer(nil)
735
-		job := eng.Job("create", test.Name)
736
-		if err := job.ImportEnv(config); err != nil {
737
-			t.Fatal(err)
738
-		}
739
-		job.Stdout.Add(outputBuffer)
740
-		if err := job.Run(); err != nil {
734
+		containerId, _, err := daemon.ContainerCreate(test.Name, config, &runconfig.HostConfig{})
735
+		if err != nil {
741 736
 			if !test.Valid {
742 737
 				continue
743 738
 			}
744 739
 			t.Fatal(err)
745 740
 		}
746 741
 
747
-		container, err := daemon.Get(engine.Tail(outputBuffer, 1))
742
+		container, err := daemon.Get(containerId)
748 743
 		if err != nil {
749 744
 			t.Fatal(err)
750 745
 		}
... ...
@@ -759,7 +744,6 @@ func TestContainerNameValidation(t *testing.T) {
759 759
 			t.Fatalf("Container /%s has ID %s instead of %s", test.Name, c.ID, container.ID)
760 760
 		}
761 761
 	}
762
-
763 762
 }
764 763
 
765 764
 func TestLinkChildContainer(t *testing.T) {
... ...
@@ -876,7 +860,7 @@ func TestDestroyWithInitLayer(t *testing.T) {
876 876
 
877 877
 	container, _, err := daemon.Create(&runconfig.Config{
878 878
 		Image: GetTestImage(daemon).ID,
879
-		Cmd:   []string{"ls", "-al"},
879
+		Cmd:   runconfig.NewCommand("ls", "-al"),
880 880
 	},
881 881
 		&runconfig.HostConfig{},
882 882
 		"")
... ...
@@ -44,16 +44,11 @@ func mkDaemon(f Fataler) *daemon.Daemon {
44 44
 }
45 45
 
46 46
 func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler, name string) (shortId string) {
47
-	job := eng.Job("create", name)
48
-	if err := job.ImportEnv(config); err != nil {
49
-		f.Fatal(err)
50
-	}
51
-	var outputBuffer = bytes.NewBuffer(nil)
52
-	job.Stdout.Add(outputBuffer)
53
-	if err := job.Run(); err != nil {
47
+	containerId, _, err := getDaemon(eng).ContainerCreate(name, config, &runconfig.HostConfig{})
48
+	if err != nil {
54 49
 		f.Fatal(err)
55 50
 	}
56
-	return engine.Tail(outputBuffer, 1)
51
+	return containerId
57 52
 }
58 53
 
59 54
 func createTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler) (shortId string) {
... ...
@@ -61,8 +56,7 @@ func createTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler
61 61
 }
62 62
 
63 63
 func startContainer(eng *engine.Engine, id string, t Fataler) {
64
-	job := eng.Job("start", id)
65
-	if err := job.Run(); err != nil {
64
+	if err := getDaemon(eng).ContainerStart(id, &runconfig.HostConfig{}); err != nil {
66 65
 		t.Fatal(err)
67 66
 	}
68 67
 }
... ...
@@ -10,25 +10,25 @@ func Compare(a, b *Config) bool {
10 10
 	if a.AttachStdout != b.AttachStdout ||
11 11
 		a.AttachStderr != b.AttachStderr ||
12 12
 		a.User != b.User ||
13
-		a.Memory != b.Memory ||
14
-		a.MemorySwap != b.MemorySwap ||
15
-		a.CpuShares != b.CpuShares ||
16 13
 		a.OpenStdin != b.OpenStdin ||
17 14
 		a.Tty != b.Tty {
18 15
 		return false
19 16
 	}
20
-	if len(a.Cmd) != len(b.Cmd) ||
17
+
18
+	if a.Cmd.Len() != b.Cmd.Len() ||
21 19
 		len(a.Env) != len(b.Env) ||
22 20
 		len(a.Labels) != len(b.Labels) ||
23 21
 		len(a.PortSpecs) != len(b.PortSpecs) ||
24 22
 		len(a.ExposedPorts) != len(b.ExposedPorts) ||
25
-		len(a.Entrypoint) != len(b.Entrypoint) ||
23
+		a.Entrypoint.Len() != b.Entrypoint.Len() ||
26 24
 		len(a.Volumes) != len(b.Volumes) {
27 25
 		return false
28 26
 	}
29 27
 
30
-	for i := 0; i < len(a.Cmd); i++ {
31
-		if a.Cmd[i] != b.Cmd[i] {
28
+	aCmd := a.Cmd.Slice()
29
+	bCmd := b.Cmd.Slice()
30
+	for i := 0; i < len(aCmd); i++ {
31
+		if aCmd[i] != bCmd[i] {
32 32
 			return false
33 33
 		}
34 34
 	}
... ...
@@ -52,8 +52,11 @@ func Compare(a, b *Config) bool {
52 52
 			return false
53 53
 		}
54 54
 	}
55
-	for i := 0; i < len(a.Entrypoint); i++ {
56
-		if a.Entrypoint[i] != b.Entrypoint[i] {
55
+
56
+	aEntrypoint := a.Entrypoint.Slice()
57
+	bEntrypoint := b.Entrypoint.Slice()
58
+	for i := 0; i < len(aEntrypoint); i++ {
59
+		if aEntrypoint[i] != bEntrypoint[i] {
57 60
 			return false
58 61
 		}
59 62
 	}
... ...
@@ -1,10 +1,103 @@
1 1
 package runconfig
2 2
 
3 3
 import (
4
-	"github.com/docker/docker/engine"
4
+	"encoding/json"
5
+	"io"
6
+
5 7
 	"github.com/docker/docker/nat"
6 8
 )
7 9
 
10
+// Entrypoint encapsulates the container entrypoint.
11
+// It might be represented as a string or an array of strings.
12
+// We need to override the json decoder to accept both options.
13
+// The JSON decoder will fail if the api sends an string and
14
+//  we try to decode it into an array of string.
15
+type Entrypoint struct {
16
+	parts []string
17
+}
18
+
19
+func (e *Entrypoint) MarshalJSON() ([]byte, error) {
20
+	if e == nil {
21
+		return []byte{}, nil
22
+	}
23
+	return json.Marshal(e.Slice())
24
+}
25
+
26
+// UnmarshalJSON decoded the entrypoint whether it's a string or an array of strings.
27
+func (e *Entrypoint) UnmarshalJSON(b []byte) error {
28
+	if len(b) == 0 {
29
+		return nil
30
+	}
31
+
32
+	p := make([]string, 0, 1)
33
+	if err := json.Unmarshal(b, &p); err != nil {
34
+		p = append(p, string(b))
35
+	}
36
+	e.parts = p
37
+	return nil
38
+}
39
+
40
+func (e *Entrypoint) Len() int {
41
+	if e == nil {
42
+		return 0
43
+	}
44
+	return len(e.parts)
45
+}
46
+
47
+func (e *Entrypoint) Slice() []string {
48
+	if e == nil {
49
+		return nil
50
+	}
51
+	return e.parts
52
+}
53
+
54
+func NewEntrypoint(parts ...string) *Entrypoint {
55
+	return &Entrypoint{parts}
56
+}
57
+
58
+type Command struct {
59
+	parts []string
60
+}
61
+
62
+func (e *Command) MarshalJSON() ([]byte, error) {
63
+	if e == nil {
64
+		return []byte{}, nil
65
+	}
66
+	return json.Marshal(e.Slice())
67
+}
68
+
69
+// UnmarshalJSON decoded the entrypoint whether it's a string or an array of strings.
70
+func (e *Command) UnmarshalJSON(b []byte) error {
71
+	if len(b) == 0 {
72
+		return nil
73
+	}
74
+
75
+	p := make([]string, 0, 1)
76
+	if err := json.Unmarshal(b, &p); err != nil {
77
+		p = append(p, string(b))
78
+	}
79
+	e.parts = p
80
+	return nil
81
+}
82
+
83
+func (e *Command) Len() int {
84
+	if e == nil {
85
+		return 0
86
+	}
87
+	return len(e.parts)
88
+}
89
+
90
+func (e *Command) Slice() []string {
91
+	if e == nil {
92
+		return nil
93
+	}
94
+	return e.parts
95
+}
96
+
97
+func NewCommand(parts ...string) *Command {
98
+	return &Command{parts}
99
+}
100
+
8 101
 // Note: the Config structure should hold only portable information about the container.
9 102
 // Here, "portable" means "independent from the host we are running on".
10 103
 // Non-portable information *should* appear in HostConfig.
... ...
@@ -12,10 +105,6 @@ type Config struct {
12 12
 	Hostname        string
13 13
 	Domainname      string
14 14
 	User            string
15
-	Memory          int64  // FIXME: we keep it for backward compatibility, it has been moved to hostConfig.
16
-	MemorySwap      int64  // FIXME: it has been moved to hostConfig.
17
-	CpuShares       int64  // FIXME: it has been moved to hostConfig.
18
-	Cpuset          string // FIXME: it has been moved to hostConfig and renamed to CpusetCpus.
19 15
 	AttachStdin     bool
20 16
 	AttachStdout    bool
21 17
 	AttachStderr    bool
... ...
@@ -25,53 +114,37 @@ type Config struct {
25 25
 	OpenStdin       bool // Open stdin
26 26
 	StdinOnce       bool // If true, close stdin after the 1 attached client disconnects.
27 27
 	Env             []string
28
-	Cmd             []string
28
+	Cmd             *Command
29 29
 	Image           string // Name of the image as it was passed by the operator (eg. could be symbolic)
30 30
 	Volumes         map[string]struct{}
31 31
 	WorkingDir      string
32
-	Entrypoint      []string
32
+	Entrypoint      *Entrypoint
33 33
 	NetworkDisabled bool
34 34
 	MacAddress      string
35 35
 	OnBuild         []string
36 36
 	Labels          map[string]string
37 37
 }
38 38
 
39
-func ContainerConfigFromJob(job *engine.Job) *Config {
40
-	config := &Config{
41
-		Hostname:        job.Getenv("Hostname"),
42
-		Domainname:      job.Getenv("Domainname"),
43
-		User:            job.Getenv("User"),
44
-		Memory:          job.GetenvInt64("Memory"),
45
-		MemorySwap:      job.GetenvInt64("MemorySwap"),
46
-		CpuShares:       job.GetenvInt64("CpuShares"),
47
-		Cpuset:          job.Getenv("Cpuset"),
48
-		AttachStdin:     job.GetenvBool("AttachStdin"),
49
-		AttachStdout:    job.GetenvBool("AttachStdout"),
50
-		AttachStderr:    job.GetenvBool("AttachStderr"),
51
-		Tty:             job.GetenvBool("Tty"),
52
-		OpenStdin:       job.GetenvBool("OpenStdin"),
53
-		StdinOnce:       job.GetenvBool("StdinOnce"),
54
-		Image:           job.Getenv("Image"),
55
-		WorkingDir:      job.Getenv("WorkingDir"),
56
-		NetworkDisabled: job.GetenvBool("NetworkDisabled"),
57
-		MacAddress:      job.Getenv("MacAddress"),
58
-	}
59
-	job.GetenvJson("ExposedPorts", &config.ExposedPorts)
60
-	job.GetenvJson("Volumes", &config.Volumes)
61
-	if PortSpecs := job.GetenvList("PortSpecs"); PortSpecs != nil {
62
-		config.PortSpecs = PortSpecs
63
-	}
64
-	if Env := job.GetenvList("Env"); Env != nil {
65
-		config.Env = Env
66
-	}
67
-	if Cmd := job.GetenvList("Cmd"); Cmd != nil {
68
-		config.Cmd = Cmd
39
+type ContainerConfigWrapper struct {
40
+	*Config
41
+	*hostConfigWrapper
42
+}
43
+
44
+func (c ContainerConfigWrapper) HostConfig() *HostConfig {
45
+	if c.hostConfigWrapper == nil {
46
+		return new(HostConfig)
69 47
 	}
70 48
 
71
-	job.GetenvJson("Labels", &config.Labels)
49
+	return c.hostConfigWrapper.GetHostConfig()
50
+}
72 51
 
73
-	if Entrypoint := job.GetenvList("Entrypoint"); Entrypoint != nil {
74
-		config.Entrypoint = Entrypoint
52
+func DecodeContainerConfig(src io.Reader) (*Config, *HostConfig, error) {
53
+	decoder := json.NewDecoder(src)
54
+
55
+	var w ContainerConfigWrapper
56
+	if err := decoder.Decode(&w); err != nil {
57
+		return nil, nil, err
75 58
 	}
76
-	return config
59
+
60
+	return w.Config, w.HostConfig(), nil
77 61
 }
... ...
@@ -1,7 +1,9 @@
1 1
 package runconfig
2 2
 
3 3
 import (
4
+	"bytes"
4 5
 	"fmt"
6
+	"io/ioutil"
5 7
 	"strings"
6 8
 	"testing"
7 9
 
... ...
@@ -260,5 +262,39 @@ func TestMerge(t *testing.T) {
260 260
 			t.Fatalf("Expected %q or %q or %q or %q, found %s", 0, 1111, 2222, 3333, portSpecs)
261 261
 		}
262 262
 	}
263
+}
264
+
265
+func TestDecodeContainerConfig(t *testing.T) {
266
+	fixtures := []struct {
267
+		file       string
268
+		entrypoint *Entrypoint
269
+	}{
270
+		{"fixtures/container_config_1_14.json", NewEntrypoint()},
271
+		{"fixtures/container_config_1_17.json", NewEntrypoint("bash")},
272
+		{"fixtures/container_config_1_19.json", NewEntrypoint("bash")},
273
+	}
274
+
275
+	for _, f := range fixtures {
276
+		b, err := ioutil.ReadFile(f.file)
277
+		if err != nil {
278
+			t.Fatal(err)
279
+		}
280
+
281
+		c, h, err := DecodeContainerConfig(bytes.NewReader(b))
282
+		if err != nil {
283
+			t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err))
284
+		}
263 285
 
286
+		if c.Image != "ubuntu" {
287
+			t.Fatalf("Expected ubuntu image, found %s\n", c.Image)
288
+		}
289
+
290
+		if c.Entrypoint.Len() != f.entrypoint.Len() {
291
+			t.Fatalf("Expected %v, found %v\n", f.entrypoint, c.Entrypoint)
292
+		}
293
+
294
+		if h.Memory != 1000 {
295
+			t.Fatalf("Expected memory to be 1000, found %d\n", h.Memory)
296
+		}
297
+	}
264 298
 }
265 299
new file mode 100644
... ...
@@ -0,0 +1,30 @@
0
+{
1
+     "Hostname":"",
2
+     "Domainname": "",
3
+     "User":"",
4
+     "Memory": 1000,
5
+     "MemorySwap":0,
6
+     "CpuShares": 512,
7
+     "Cpuset": "0,1",
8
+     "AttachStdin":false,
9
+     "AttachStdout":true,
10
+     "AttachStderr":true,
11
+     "PortSpecs":null,
12
+     "Tty":false,
13
+     "OpenStdin":false,
14
+     "StdinOnce":false,
15
+     "Env":null,
16
+     "Cmd":[
17
+             "bash"
18
+     ],
19
+     "Image":"ubuntu",
20
+     "Volumes":{
21
+             "/tmp": {}
22
+     },
23
+     "WorkingDir":"",
24
+     "NetworkDisabled": false,
25
+     "ExposedPorts":{
26
+             "22/tcp": {}
27
+     },
28
+     "RestartPolicy": { "Name": "always" }
29
+}
0 30
new file mode 100644
... ...
@@ -0,0 +1,49 @@
0
+{
1
+     "Hostname": "",
2
+     "Domainname": "",
3
+     "User": "",
4
+     "Memory": 1000,
5
+     "MemorySwap": 0,
6
+     "CpuShares": 512,
7
+     "Cpuset": "0,1",
8
+     "AttachStdin": false,
9
+     "AttachStdout": true,
10
+     "AttachStderr": true,
11
+     "Tty": false,
12
+     "OpenStdin": false,
13
+     "StdinOnce": false,
14
+     "Env": null,
15
+     "Cmd": [
16
+             "date"
17
+     ],
18
+     "Entrypoint": "bash",
19
+     "Image": "ubuntu",
20
+     "Volumes": {
21
+             "/tmp": {}
22
+     },
23
+     "WorkingDir": "",
24
+     "NetworkDisabled": false,
25
+     "MacAddress": "12:34:56:78:9a:bc",
26
+     "ExposedPorts": {
27
+             "22/tcp": {}
28
+     },
29
+     "SecurityOpt": [""],
30
+     "HostConfig": {
31
+       "Binds": ["/tmp:/tmp"],
32
+       "Links": ["redis3:redis"],
33
+       "LxcConf": {"lxc.utsname":"docker"},
34
+       "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
35
+       "PublishAllPorts": false,
36
+       "Privileged": false,
37
+       "ReadonlyRootfs": false,
38
+       "Dns": ["8.8.8.8"],
39
+       "DnsSearch": [""],
40
+       "ExtraHosts": null,
41
+       "VolumesFrom": ["parent", "other:ro"],
42
+       "CapAdd": ["NET_ADMIN"],
43
+       "CapDrop": ["MKNOD"],
44
+       "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
45
+       "NetworkMode": "bridge",
46
+       "Devices": []
47
+    }
48
+}
0 49
new file mode 100644
... ...
@@ -0,0 +1,57 @@
0
+{
1
+     "Hostname": "",
2
+     "Domainname": "",
3
+     "User": "",
4
+     "AttachStdin": false,
5
+     "AttachStdout": true,
6
+     "AttachStderr": true,
7
+     "Tty": false,
8
+     "OpenStdin": false,
9
+     "StdinOnce": false,
10
+     "Env": null,
11
+     "Cmd": [
12
+             "date"
13
+     ],
14
+     "Entrypoint": "bash",
15
+     "Image": "ubuntu",
16
+     "Labels": {
17
+             "com.example.vendor": "Acme",
18
+             "com.example.license": "GPL",
19
+             "com.example.version": "1.0"
20
+     },
21
+     "Volumes": {
22
+             "/tmp": {}
23
+     },
24
+     "WorkingDir": "",
25
+     "NetworkDisabled": false,
26
+     "MacAddress": "12:34:56:78:9a:bc",
27
+     "ExposedPorts": {
28
+             "22/tcp": {}
29
+     },
30
+     "HostConfig": {
31
+       "Binds": ["/tmp:/tmp"],
32
+       "Links": ["redis3:redis"],
33
+       "LxcConf": {"lxc.utsname":"docker"},
34
+       "Memory": 1000,
35
+       "MemorySwap": 0,
36
+       "CpuShares": 512,
37
+       "CpusetCpus": "0,1",
38
+       "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
39
+       "PublishAllPorts": false,
40
+       "Privileged": false,
41
+       "ReadonlyRootfs": false,
42
+       "Dns": ["8.8.8.8"],
43
+       "DnsSearch": [""],
44
+       "ExtraHosts": null,
45
+       "VolumesFrom": ["parent", "other:ro"],
46
+       "CapAdd": ["NET_ADMIN"],
47
+       "CapDrop": ["MKNOD"],
48
+       "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
49
+       "NetworkMode": "bridge",
50
+       "Devices": [],
51
+       "Ulimits": [{}],
52
+       "LogConfig": { "Type": "json-file", "Config": {} },
53
+       "SecurityOpt": [""],
54
+       "CgroupParent": ""
55
+    }
56
+}
... ...
@@ -1,9 +1,10 @@
1 1
 package runconfig
2 2
 
3 3
 import (
4
+	"encoding/json"
5
+	"io"
4 6
 	"strings"
5 7
 
6
-	"github.com/docker/docker/engine"
7 8
 	"github.com/docker/docker/nat"
8 9
 	"github.com/docker/docker/pkg/ulimit"
9 10
 )
... ...
@@ -108,10 +109,59 @@ type LogConfig struct {
108 108
 	Config map[string]string
109 109
 }
110 110
 
111
+type LxcConfig struct {
112
+	values []KeyValuePair
113
+}
114
+
115
+func (c *LxcConfig) MarshalJSON() ([]byte, error) {
116
+	if c == nil {
117
+		return []byte{}, nil
118
+	}
119
+	return json.Marshal(c.Slice())
120
+}
121
+
122
+func (c *LxcConfig) UnmarshalJSON(b []byte) error {
123
+	if len(b) == 0 {
124
+		return nil
125
+	}
126
+
127
+	var kv []KeyValuePair
128
+	if err := json.Unmarshal(b, &kv); err != nil {
129
+		var h map[string]string
130
+		if err := json.Unmarshal(b, &h); err != nil {
131
+			return err
132
+		}
133
+		for k, v := range h {
134
+			kv = append(kv, KeyValuePair{k, v})
135
+		}
136
+	}
137
+	c.values = kv
138
+
139
+	return nil
140
+}
141
+
142
+func (c *LxcConfig) Len() int {
143
+	if c == nil {
144
+		return 0
145
+	}
146
+	return len(c.values)
147
+}
148
+
149
+func (c *LxcConfig) Slice() []KeyValuePair {
150
+	if c == nil {
151
+		return nil
152
+	}
153
+	return c.values
154
+}
155
+
156
+func NewLxcConfig(values []KeyValuePair) *LxcConfig {
157
+	return &LxcConfig{values}
158
+}
159
+
111 160
 type HostConfig struct {
112 161
 	Binds           []string
113 162
 	ContainerIDFile string
114
-	LxcConf         []KeyValuePair
163
+	LxcConf         *LxcConfig
115 164
 	Memory          int64  // Memory limit (in bytes)
116 165
 	MemorySwap      int64  // Total memory usage (memory + swap); set `-1` to disable swap
117 166
 	CpuShares       int64  // CPU shares (relative weight vs. other containers)
... ...
@@ -139,96 +189,55 @@ type HostConfig struct {
139 139
 	CgroupParent    string // Parent cgroup.
140 140
 }
141 141
 
142
-// This is used by the create command when you want to set both the
143
-// Config and the HostConfig in the same call
144
-type ConfigAndHostConfig struct {
145
-	Config
146
-	HostConfig HostConfig
142
+func MergeConfigs(config *Config, hostConfig *HostConfig) *ContainerConfigWrapper {
143
+	return &ContainerConfigWrapper{
144
+		config,
145
+		&hostConfigWrapper{InnerHostConfig: hostConfig},
146
+	}
147 147
 }
148 148
 
149
-func MergeConfigs(config *Config, hostConfig *HostConfig) *ConfigAndHostConfig {
150
-	return &ConfigAndHostConfig{
151
-		*config,
152
-		*hostConfig,
153
-	}
149
+type hostConfigWrapper struct {
150
+	InnerHostConfig *HostConfig `json:"HostConfig,omitempty"`
151
+	Cpuset          string      `json:",omitempty"` // Deprecated. Exported for backwards compatibility.
152
+
153
+	*HostConfig // Deprecated. Exported to read attrubutes from json that are not in the inner host config structure.
154 154
 }
155 155
 
156
-func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
157
-	if job.EnvExists("HostConfig") {
158
-		hostConfig := HostConfig{}
159
-		job.GetenvJson("HostConfig", &hostConfig)
156
+func (w hostConfigWrapper) GetHostConfig() *HostConfig {
157
+	hc := w.HostConfig
160 158
 
161
-		// FIXME: These are for backward compatibility, if people use these
162
-		// options with `HostConfig`, we should still make them workable.
163
-		if job.EnvExists("Memory") && hostConfig.Memory == 0 {
164
-			hostConfig.Memory = job.GetenvInt64("Memory")
165
-		}
166
-		if job.EnvExists("MemorySwap") && hostConfig.MemorySwap == 0 {
167
-			hostConfig.MemorySwap = job.GetenvInt64("MemorySwap")
159
+	if hc == nil && w.InnerHostConfig != nil {
160
+		hc = w.InnerHostConfig
161
+	} else if w.InnerHostConfig != nil {
162
+		if hc.Memory != 0 && w.InnerHostConfig.Memory == 0 {
163
+			w.InnerHostConfig.Memory = hc.Memory
168 164
 		}
169
-		if job.EnvExists("CpuShares") && hostConfig.CpuShares == 0 {
170
-			hostConfig.CpuShares = job.GetenvInt64("CpuShares")
165
+		if hc.MemorySwap != 0 && w.InnerHostConfig.MemorySwap == 0 {
166
+			w.InnerHostConfig.MemorySwap = hc.MemorySwap
171 167
 		}
172
-		if job.EnvExists("Cpuset") && hostConfig.CpusetCpus == "" {
173
-			hostConfig.CpusetCpus = job.Getenv("Cpuset")
168
+		if hc.CpuShares != 0 && w.InnerHostConfig.CpuShares == 0 {
169
+			w.InnerHostConfig.CpuShares = hc.CpuShares
174 170
 		}
175 171
 
176
-		return &hostConfig
172
+		hc = w.InnerHostConfig
177 173
 	}
178 174
 
179
-	hostConfig := &HostConfig{
180
-		ContainerIDFile: job.Getenv("ContainerIDFile"),
181
-		Memory:          job.GetenvInt64("Memory"),
182
-		MemorySwap:      job.GetenvInt64("MemorySwap"),
183
-		CpuShares:       job.GetenvInt64("CpuShares"),
184
-		CpusetCpus:      job.Getenv("CpusetCpus"),
185
-		Privileged:      job.GetenvBool("Privileged"),
186
-		PublishAllPorts: job.GetenvBool("PublishAllPorts"),
187
-		NetworkMode:     NetworkMode(job.Getenv("NetworkMode")),
188
-		IpcMode:         IpcMode(job.Getenv("IpcMode")),
189
-		PidMode:         PidMode(job.Getenv("PidMode")),
190
-		ReadonlyRootfs:  job.GetenvBool("ReadonlyRootfs"),
191
-		CgroupParent:    job.Getenv("CgroupParent"),
175
+	if hc != nil && w.Cpuset != "" && hc.CpusetCpus == "" {
176
+		hc.CpusetCpus = w.Cpuset
192 177
 	}
193 178
 
194
-	// FIXME: This is for backward compatibility, if people use `Cpuset`
195
-	// in json, make it workable, we will only pass hostConfig.CpusetCpus
196
-	// to execDriver.
197
-	if job.EnvExists("Cpuset") && hostConfig.CpusetCpus == "" {
198
-		hostConfig.CpusetCpus = job.Getenv("Cpuset")
199
-	}
179
+	return hc
180
+}
200 181
 
201
-	job.GetenvJson("LxcConf", &hostConfig.LxcConf)
202
-	job.GetenvJson("PortBindings", &hostConfig.PortBindings)
203
-	job.GetenvJson("Devices", &hostConfig.Devices)
204
-	job.GetenvJson("RestartPolicy", &hostConfig.RestartPolicy)
205
-	job.GetenvJson("Ulimits", &hostConfig.Ulimits)
206
-	job.GetenvJson("LogConfig", &hostConfig.LogConfig)
207
-	hostConfig.SecurityOpt = job.GetenvList("SecurityOpt")
208
-	if Binds := job.GetenvList("Binds"); Binds != nil {
209
-		hostConfig.Binds = Binds
210
-	}
211
-	if Links := job.GetenvList("Links"); Links != nil {
212
-		hostConfig.Links = Links
213
-	}
214
-	if Dns := job.GetenvList("Dns"); Dns != nil {
215
-		hostConfig.Dns = Dns
216
-	}
217
-	if DnsSearch := job.GetenvList("DnsSearch"); DnsSearch != nil {
218
-		hostConfig.DnsSearch = DnsSearch
219
-	}
220
-	if ExtraHosts := job.GetenvList("ExtraHosts"); ExtraHosts != nil {
221
-		hostConfig.ExtraHosts = ExtraHosts
222
-	}
223
-	if VolumesFrom := job.GetenvList("VolumesFrom"); VolumesFrom != nil {
224
-		hostConfig.VolumesFrom = VolumesFrom
225
-	}
226
-	if CapAdd := job.GetenvList("CapAdd"); CapAdd != nil {
227
-		hostConfig.CapAdd = CapAdd
228
-	}
229
-	if CapDrop := job.GetenvList("CapDrop"); CapDrop != nil {
230
-		hostConfig.CapDrop = CapDrop
182
+func DecodeHostConfig(src io.Reader) (*HostConfig, error) {
183
+	decoder := json.NewDecoder(src)
184
+
185
+	var w hostConfigWrapper
186
+	if err := decoder.Decode(&w); err != nil {
187
+		return nil, err
231 188
 	}
232 189
 
233
-	return hostConfig
190
+	hc := w.GetHostConfig()
191
+
192
+	return hc, nil
234 193
 }
... ...
@@ -11,15 +11,6 @@ func Merge(userConf, imageConf *Config) error {
11 11
 	if userConf.User == "" {
12 12
 		userConf.User = imageConf.User
13 13
 	}
14
-	if userConf.Memory == 0 {
15
-		userConf.Memory = imageConf.Memory
16
-	}
17
-	if userConf.MemorySwap == 0 {
18
-		userConf.MemorySwap = imageConf.MemorySwap
19
-	}
20
-	if userConf.CpuShares == 0 {
21
-		userConf.CpuShares = imageConf.CpuShares
22
-	}
23 14
 	if len(userConf.ExposedPorts) == 0 {
24 15
 		userConf.ExposedPorts = imageConf.ExposedPorts
25 16
 	} else if imageConf.ExposedPorts != nil {
... ...
@@ -94,8 +85,8 @@ func Merge(userConf, imageConf *Config) error {
94 94
 		userConf.Labels = imageConf.Labels
95 95
 	}
96 96
 
97
-	if len(userConf.Entrypoint) == 0 {
98
-		if len(userConf.Cmd) == 0 {
97
+	if userConf.Entrypoint.Len() == 0 {
98
+		if userConf.Cmd.Len() == 0 {
99 99
 			userConf.Cmd = imageConf.Cmd
100 100
 		}
101 101
 
... ...
@@ -186,21 +186,22 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
186 186
 
187 187
 	var (
188 188
 		parsedArgs = cmd.Args()
189
-		runCmd     []string
190
-		entrypoint []string
189
+		runCmd     *Command
190
+		entrypoint *Entrypoint
191 191
 		image      = cmd.Arg(0)
192 192
 	)
193 193
 	if len(parsedArgs) > 1 {
194
-		runCmd = parsedArgs[1:]
194
+		runCmd = NewCommand(parsedArgs[1:]...)
195 195
 	}
196 196
 	if *flEntrypoint != "" {
197
-		entrypoint = []string{*flEntrypoint}
197
+		entrypoint = NewEntrypoint(*flEntrypoint)
198 198
 	}
199 199
 
200
-	lxcConf, err := parseKeyValueOpts(flLxcOpts)
200
+	lc, err := parseKeyValueOpts(flLxcOpts)
201 201
 	if err != nil {
202 202
 		return nil, nil, cmd, err
203 203
 	}
204
+	lxcConf := NewLxcConfig(lc)
204 205
 
205 206
 	var (
206 207
 		domainname string
... ...
@@ -289,10 +290,6 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
289 289
 		Tty:             *flTty,
290 290
 		NetworkDisabled: !*flNetwork,
291 291
 		OpenStdin:       *flStdin,
292
-		Memory:          flMemory,      // FIXME: for backward compatibility
293
-		MemorySwap:      MemorySwap,    // FIXME: for backward compatibility
294
-		CpuShares:       *flCpuShares,  // FIXME: for backward compatibility
295
-		Cpuset:          *flCpusetCpus, // FIXME: for backward compatibility
296 292
 		AttachStdin:     attachStdin,
297 293
 		AttachStdout:    attachStdout,
298 294
 		AttachStderr:    attachStderr,