Signed-off-by: David Calavera <david.calavera@gmail.com>
David Calavera authored on 2015/04/11 09:05:21... | ... |
@@ -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 |
|
... | ... |
@@ -811,14 +812,14 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re |
811 | 811 |
var ( |
812 | 812 |
warnings []string |
813 | 813 |
name = r.Form.Get("name") |
814 |
- env = new(engine.Env) |
|
815 | 814 |
) |
816 | 815 |
|
817 |
- if err := env.Decode(r.Body); err != nil { |
|
816 |
+ config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body) |
|
817 |
+ if err != nil { |
|
818 | 818 |
return err |
819 | 819 |
} |
820 | 820 |
|
821 |
- containerId, warnings, err := getDaemon(eng).ContainerCreate(name, env) |
|
821 |
+ containerId, warnings, err := getDaemon(eng).ContainerCreate(name, config, hostConfig) |
|
822 | 822 |
if err != nil { |
823 | 823 |
return err |
824 | 824 |
} |
... | ... |
@@ -917,10 +918,6 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res |
917 | 917 |
if vars == nil { |
918 | 918 |
return fmt.Errorf("Missing parameter") |
919 | 919 |
} |
920 |
- var ( |
|
921 |
- name = vars["name"] |
|
922 |
- env = new(engine.Env) |
|
923 |
- ) |
|
924 | 920 |
|
925 | 921 |
// If contentLength is -1, we can assumed chunked encoding |
926 | 922 |
// or more technically that the length is unknown |
... | ... |
@@ -928,17 +925,21 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res |
928 | 928 |
// net/http otherwise seems to swallow any headers related to chunked encoding |
929 | 929 |
// including r.TransferEncoding |
930 | 930 |
// allow a nil body for backwards compatibility |
931 |
+ var hostConfig *runconfig.HostConfig |
|
931 | 932 |
if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) { |
932 | 933 |
if err := checkForJson(r); err != nil { |
933 | 934 |
return err |
934 | 935 |
} |
935 | 936 |
|
936 |
- if err := env.Decode(r.Body); err != nil { |
|
937 |
+ c, err := runconfig.DecodeHostConfig(r.Body) |
|
938 |
+ if err != nil { |
|
937 | 939 |
return err |
938 | 940 |
} |
941 |
+ |
|
942 |
+ hostConfig = c |
|
939 | 943 |
} |
940 | 944 |
|
941 |
- if err := getDaemon(eng).ContainerStart(name, env); err != nil { |
|
945 |
+ if err := getDaemon(eng).ContainerStart(vars["name"], hostConfig); err != nil { |
|
942 | 946 |
if err.Error() == "Container already started" { |
943 | 947 |
w.WriteHeader(http.StatusNotModified) |
944 | 948 |
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 { |
... | ... |
@@ -559,12 +559,13 @@ func (b *Builder) create() (*daemon.Container, error) { |
559 | 559 |
b.TmpContainers[c.ID] = struct{}{} |
560 | 560 |
fmt.Fprintf(b.OutStream, " ---> Running in %s\n", stringid.TruncateID(c.ID)) |
561 | 561 |
|
562 |
- if len(config.Cmd) > 0 { |
|
562 |
+ if config.Cmd.Len() > 0 { |
|
563 | 563 |
// override the entry point that may have been picked up from the base image |
564 |
- c.Path = config.Cmd[0] |
|
565 |
- c.Args = config.Cmd[1:] |
|
564 |
+ s := config.Cmd.Slice() |
|
565 |
+ c.Path = s[0] |
|
566 |
+ c.Args = s[1:] |
|
566 | 567 |
} else { |
567 |
- config.Cmd = []string{} |
|
568 |
+ config.Cmd = runconfig.NewCommand() |
|
568 | 569 |
} |
569 | 570 |
|
570 | 571 |
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,13 +11,10 @@ import ( |
12 | 12 |
"github.com/docker/libcontainer/label" |
13 | 13 |
) |
14 | 14 |
|
15 |
-func (daemon *Daemon) ContainerCreate(name string, env *engine.Env) (string, []string, error) { |
|
15 |
+func (daemon *Daemon) ContainerCreate(name string, config *runconfig.Config, hostConfig *runconfig.HostConfig) (string, []string, error) { |
|
16 | 16 |
var warnings []string |
17 | 17 |
|
18 |
- config := runconfig.ContainerConfigFromJob(env) |
|
19 |
- hostConfig := runconfig.ContainerHostConfigFromJob(env) |
|
20 |
- |
|
21 |
- if len(hostConfig.LxcConf) > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") { |
|
18 |
+ if hostConfig.LxcConf.Len() > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") { |
|
22 | 19 |
return "", warnings, fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name()) |
23 | 20 |
} |
24 | 21 |
if hostConfig.Memory != 0 && hostConfig.Memory < 4194304 { |
... | ... |
@@ -119,12 +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 |
- "container_stats": daemon.ContainerStats, |
|
123 |
- "export": daemon.ContainerExport, |
|
124 | 122 |
"info": daemon.CmdInfo, |
125 | 123 |
"restart": daemon.ContainerRestart, |
126 |
- "stop": daemon.ContainerStop, |
|
127 |
- "wait": daemon.ContainerWait, |
|
128 | 124 |
"execCreate": daemon.ContainerExecCreate, |
129 | 125 |
"execStart": daemon.ContainerExecStart, |
130 | 126 |
} { |
... | ... |
@@ -485,7 +481,7 @@ func (daemon *Daemon) mergeAndVerifyConfig(config *runconfig.Config, img *image. |
485 | 485 |
return nil, err |
486 | 486 |
} |
487 | 487 |
} |
488 |
- if len(config.Entrypoint) == 0 && len(config.Cmd) == 0 { |
|
488 |
+ if config.Entrypoint.Len() == 0 && config.Cmd.Len() == 0 { |
|
489 | 489 |
return nil, fmt.Errorf("No command specified") |
490 | 490 |
} |
491 | 491 |
return warnings, nil |
... | ... |
@@ -577,17 +573,20 @@ func (daemon *Daemon) generateHostname(id string, config *runconfig.Config) { |
577 | 577 |
} |
578 | 578 |
} |
579 | 579 |
|
580 |
-func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint, configCmd []string) (string, []string) { |
|
580 |
+func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint *runconfig.Entrypoint, configCmd *runconfig.Command) (string, []string) { |
|
581 | 581 |
var ( |
582 | 582 |
entrypoint string |
583 | 583 |
args []string |
584 | 584 |
) |
585 |
- if len(configEntrypoint) != 0 { |
|
586 |
- entrypoint = configEntrypoint[0] |
|
587 |
- args = append(configEntrypoint[1:], configCmd...) |
|
585 |
+ |
|
586 |
+ cmdSlice := configCmd.Slice() |
|
587 |
+ if configEntrypoint.Len() != 0 { |
|
588 |
+ eSlice := configEntrypoint.Slice() |
|
589 |
+ entrypoint = eSlice[0] |
|
590 |
+ args = append(eSlice[1:], cmdSlice...) |
|
588 | 591 |
} else { |
589 |
- entrypoint = configCmd[0] |
|
590 |
- args = configCmd[1:] |
|
592 |
+ entrypoint = cmdSlice[0] |
|
593 |
+ args = cmdSlice[1:] |
|
591 | 594 |
} |
592 | 595 |
return entrypoint, args |
593 | 596 |
} |
... | ... |
@@ -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,11 +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(name string, env *engine.Env) error { |
|
9 |
+func (daemon *Daemon) ContainerStart(name string, hostConfig *runconfig.HostConfig) error { |
|
11 | 10 |
container, err := daemon.Get(name) |
12 | 11 |
if err != nil { |
13 | 12 |
return err |
... | ... |
@@ -21,15 +20,14 @@ func (daemon *Daemon) ContainerStart(name string, env *engine.Env) error { |
21 | 21 |
return fmt.Errorf("Container already started") |
22 | 22 |
} |
23 | 23 |
|
24 |
- // If no environment was set, then no hostconfig was passed. |
|
25 | 24 |
// This is kept for backward compatibility - hostconfig should be passed when |
26 | 25 |
// creating a container, not during start. |
27 |
- if len(env.Map()) > 0 { |
|
28 |
- hostConfig := runconfig.ContainerHostConfigFromJob(env) |
|
26 |
+ if hostConfig != nil { |
|
29 | 27 |
if err := daemon.setHostConfig(container, hostConfig); err != nil { |
30 | 28 |
return err |
31 | 29 |
} |
32 | 30 |
} |
31 |
+ |
|
33 | 32 |
if err := container.Start(); err != nil { |
34 | 33 |
container.LogEvent("die") |
35 | 34 |
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 |
"") |
... | ... |
@@ -451,13 +452,14 @@ func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daem |
451 | 451 |
p = nat.Port(fmt.Sprintf("%s/%s", strPort, proto)) |
452 | 452 |
ep[p] = struct{}{} |
453 | 453 |
|
454 |
- env := new(engine.Env) |
|
455 |
- env.Set("Image", unitTestImageID) |
|
456 |
- env.SetList("Cmd", []string{"sh", "-c", cmd}) |
|
457 |
- env.SetList("PortSpecs", []string{fmt.Sprintf("%s/%s", strPort, proto)}) |
|
458 |
- env.SetJson("ExposedPorts", ep) |
|
454 |
+ c := &runconfig.Config{ |
|
455 |
+ Image: unitTestImageID, |
|
456 |
+ Cmd: runconfig.NewCommand("sh", "-c", cmd), |
|
457 |
+ PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)}, |
|
458 |
+ ExposedPorts: ep, |
|
459 |
+ } |
|
459 | 460 |
|
460 |
- id, _, err = daemon.ContainerCreate(unitTestImageID, env) |
|
461 |
+ id, _, err = daemon.ContainerCreate(unitTestImageID, c, &runconfig.HostConfig{}) |
|
461 | 462 |
// FIXME: this relies on the undocumented behavior of daemon.Create |
462 | 463 |
// which will return a nil error AND container if the exposed ports |
463 | 464 |
// are invalid. That behavior should be fixed! |
... | ... |
@@ -468,16 +470,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daem |
468 | 468 |
|
469 | 469 |
} |
470 | 470 |
|
471 |
- portBindings := make(map[nat.Port][]nat.PortBinding) |
|
472 |
- portBindings[p] = []nat.PortBinding{ |
|
473 |
- {}, |
|
474 |
- } |
|
475 |
- |
|
476 |
- env := new(engine.Env) |
|
477 |
- if err := env.SetJson("PortsBindings", portBindings); err != nil { |
|
478 |
- t.Fatal(err) |
|
479 |
- } |
|
480 |
- if err := daemon.ContainerStart(id, env); err != nil { |
|
471 |
+ if err := daemon.ContainerStart(id, &runconfig.HostConfig{}); err != nil { |
|
481 | 472 |
t.Fatal(err) |
482 | 473 |
} |
483 | 474 |
|
... | ... |
@@ -728,12 +721,7 @@ func TestContainerNameValidation(t *testing.T) { |
728 | 728 |
t.Fatal(err) |
729 | 729 |
} |
730 | 730 |
|
731 |
- env := new(engine.Env) |
|
732 |
- if err := env.Import(config); err != nil { |
|
733 |
- t.Fatal(err) |
|
734 |
- } |
|
735 |
- |
|
736 |
- containerId, _, err := daemon.ContainerCreate(test.Name, env) |
|
731 |
+ containerId, _, err := daemon.ContainerCreate(test.Name, config, &runconfig.HostConfig{}) |
|
737 | 732 |
if err != nil { |
738 | 733 |
if !test.Valid { |
739 | 734 |
continue |
... | ... |
@@ -872,7 +860,7 @@ func TestDestroyWithInitLayer(t *testing.T) { |
872 | 872 |
|
873 | 873 |
container, _, err := daemon.Create(&runconfig.Config{ |
874 | 874 |
Image: GetTestImage(daemon).ID, |
875 |
- Cmd: []string{"ls", "-al"}, |
|
875 |
+ Cmd: runconfig.NewCommand("ls", "-al"), |
|
876 | 876 |
}, |
877 | 877 |
&runconfig.HostConfig{}, |
878 | 878 |
"") |
... | ... |
@@ -44,11 +44,7 @@ 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 |
- env := new(engine.Env) |
|
48 |
- if err := env.Import(config); err != nil { |
|
49 |
- f.Fatal(err) |
|
50 |
- } |
|
51 |
- containerId, _, err := getDaemon(eng).ContainerCreate(name, env) |
|
47 |
+ containerId, _, err := getDaemon(eng).ContainerCreate(name, config, &runconfig.HostConfig{}) |
|
52 | 48 |
if err != nil { |
53 | 49 |
f.Fatal(err) |
54 | 50 |
} |
... | ... |
@@ -60,8 +56,7 @@ func createTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler |
60 | 60 |
} |
61 | 61 |
|
62 | 62 |
func startContainer(eng *engine.Engine, id string, t Fataler) { |
63 |
- env := new(engine.Env) |
|
64 |
- if err := getDaemon(eng).ContainerStart(id, env); err != nil { |
|
63 |
+ if err := getDaemon(eng).ContainerStart(id, &runconfig.HostConfig{}); err != nil { |
|
65 | 64 |
t.Fatal(err) |
66 | 65 |
} |
67 | 66 |
} |
... | ... |
@@ -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(env *engine.Env) *Config { |
|
40 |
- config := &Config{ |
|
41 |
- Hostname: env.Get("Hostname"), |
|
42 |
- Domainname: env.Get("Domainname"), |
|
43 |
- User: env.Get("User"), |
|
44 |
- Memory: env.GetInt64("Memory"), |
|
45 |
- MemorySwap: env.GetInt64("MemorySwap"), |
|
46 |
- CpuShares: env.GetInt64("CpuShares"), |
|
47 |
- Cpuset: env.Get("Cpuset"), |
|
48 |
- AttachStdin: env.GetBool("AttachStdin"), |
|
49 |
- AttachStdout: env.GetBool("AttachStdout"), |
|
50 |
- AttachStderr: env.GetBool("AttachStderr"), |
|
51 |
- Tty: env.GetBool("Tty"), |
|
52 |
- OpenStdin: env.GetBool("OpenStdin"), |
|
53 |
- StdinOnce: env.GetBool("StdinOnce"), |
|
54 |
- Image: env.Get("Image"), |
|
55 |
- WorkingDir: env.Get("WorkingDir"), |
|
56 |
- NetworkDisabled: env.GetBool("NetworkDisabled"), |
|
57 |
- MacAddress: env.Get("MacAddress"), |
|
58 |
- } |
|
59 |
- env.GetJson("ExposedPorts", &config.ExposedPorts) |
|
60 |
- env.GetJson("Volumes", &config.Volumes) |
|
61 |
- if PortSpecs := env.GetList("PortSpecs"); PortSpecs != nil { |
|
62 |
- config.PortSpecs = PortSpecs |
|
63 |
- } |
|
64 |
- if Env := env.GetList("Env"); Env != nil { |
|
65 |
- config.Env = Env |
|
66 |
- } |
|
67 |
- if Cmd := env.GetList("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 |
- env.GetJson("Labels", &config.Labels) |
|
49 |
+ return c.hostConfigWrapper.GetHostConfig() |
|
50 |
+} |
|
72 | 51 |
|
73 |
- if Entrypoint := env.GetList("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) |
... | ... |
@@ -138,96 +188,55 @@ type HostConfig struct { |
138 | 138 |
CgroupParent string // Parent cgroup. |
139 | 139 |
} |
140 | 140 |
|
141 |
-// This is used by the create command when you want to set both the |
|
142 |
-// Config and the HostConfig in the same call |
|
143 |
-type ConfigAndHostConfig struct { |
|
144 |
- Config |
|
145 |
- HostConfig HostConfig |
|
141 |
+func MergeConfigs(config *Config, hostConfig *HostConfig) *ContainerConfigWrapper { |
|
142 |
+ return &ContainerConfigWrapper{ |
|
143 |
+ config, |
|
144 |
+ &hostConfigWrapper{InnerHostConfig: hostConfig}, |
|
145 |
+ } |
|
146 | 146 |
} |
147 | 147 |
|
148 |
-func MergeConfigs(config *Config, hostConfig *HostConfig) *ConfigAndHostConfig { |
|
149 |
- return &ConfigAndHostConfig{ |
|
150 |
- *config, |
|
151 |
- *hostConfig, |
|
152 |
- } |
|
148 |
+type hostConfigWrapper struct { |
|
149 |
+ InnerHostConfig *HostConfig `json:"HostConfig,omitempty"` |
|
150 |
+ Cpuset string `json:",omitempty"` // Deprecated. Exported for backwards compatibility. |
|
151 |
+ |
|
152 |
+ *HostConfig // Deprecated. Exported to read attrubutes from json that are not in the inner host config structure. |
|
153 | 153 |
} |
154 | 154 |
|
155 |
-func ContainerHostConfigFromJob(env *engine.Env) *HostConfig { |
|
156 |
- if env.Exists("HostConfig") { |
|
157 |
- hostConfig := HostConfig{} |
|
158 |
- env.GetJson("HostConfig", &hostConfig) |
|
155 |
+func (w hostConfigWrapper) GetHostConfig() *HostConfig { |
|
156 |
+ hc := w.HostConfig |
|
159 | 157 |
|
160 |
- // FIXME: These are for backward compatibility, if people use these |
|
161 |
- // options with `HostConfig`, we should still make them workable. |
|
162 |
- if env.Exists("Memory") && hostConfig.Memory == 0 { |
|
163 |
- hostConfig.Memory = env.GetInt64("Memory") |
|
164 |
- } |
|
165 |
- if env.Exists("MemorySwap") && hostConfig.MemorySwap == 0 { |
|
166 |
- hostConfig.MemorySwap = env.GetInt64("MemorySwap") |
|
158 |
+ if hc == nil && w.InnerHostConfig != nil { |
|
159 |
+ hc = w.InnerHostConfig |
|
160 |
+ } else if w.InnerHostConfig != nil { |
|
161 |
+ if hc.Memory != 0 && w.InnerHostConfig.Memory == 0 { |
|
162 |
+ w.InnerHostConfig.Memory = hc.Memory |
|
167 | 163 |
} |
168 |
- if env.Exists("CpuShares") && hostConfig.CpuShares == 0 { |
|
169 |
- hostConfig.CpuShares = env.GetInt64("CpuShares") |
|
164 |
+ if hc.MemorySwap != 0 && w.InnerHostConfig.MemorySwap == 0 { |
|
165 |
+ w.InnerHostConfig.MemorySwap = hc.MemorySwap |
|
170 | 166 |
} |
171 |
- if env.Exists("Cpuset") && hostConfig.CpusetCpus == "" { |
|
172 |
- hostConfig.CpusetCpus = env.Get("Cpuset") |
|
167 |
+ if hc.CpuShares != 0 && w.InnerHostConfig.CpuShares == 0 { |
|
168 |
+ w.InnerHostConfig.CpuShares = hc.CpuShares |
|
173 | 169 |
} |
174 | 170 |
|
175 |
- return &hostConfig |
|
171 |
+ hc = w.InnerHostConfig |
|
176 | 172 |
} |
177 | 173 |
|
178 |
- hostConfig := &HostConfig{ |
|
179 |
- ContainerIDFile: env.Get("ContainerIDFile"), |
|
180 |
- Memory: env.GetInt64("Memory"), |
|
181 |
- MemorySwap: env.GetInt64("MemorySwap"), |
|
182 |
- CpuShares: env.GetInt64("CpuShares"), |
|
183 |
- CpusetCpus: env.Get("CpusetCpus"), |
|
184 |
- Privileged: env.GetBool("Privileged"), |
|
185 |
- PublishAllPorts: env.GetBool("PublishAllPorts"), |
|
186 |
- NetworkMode: NetworkMode(env.Get("NetworkMode")), |
|
187 |
- IpcMode: IpcMode(env.Get("IpcMode")), |
|
188 |
- PidMode: PidMode(env.Get("PidMode")), |
|
189 |
- ReadonlyRootfs: env.GetBool("ReadonlyRootfs"), |
|
190 |
- CgroupParent: env.Get("CgroupParent"), |
|
174 |
+ if hc != nil && w.Cpuset != "" && hc.CpusetCpus == "" { |
|
175 |
+ hc.CpusetCpus = w.Cpuset |
|
191 | 176 |
} |
192 | 177 |
|
193 |
- // FIXME: This is for backward compatibility, if people use `Cpuset` |
|
194 |
- // in json, make it workable, we will only pass hostConfig.CpusetCpus |
|
195 |
- // to execDriver. |
|
196 |
- if env.Exists("Cpuset") && hostConfig.CpusetCpus == "" { |
|
197 |
- hostConfig.CpusetCpus = env.Get("Cpuset") |
|
198 |
- } |
|
178 |
+ return hc |
|
179 |
+} |
|
199 | 180 |
|
200 |
- env.GetJson("LxcConf", &hostConfig.LxcConf) |
|
201 |
- env.GetJson("PortBindings", &hostConfig.PortBindings) |
|
202 |
- env.GetJson("Devices", &hostConfig.Devices) |
|
203 |
- env.GetJson("RestartPolicy", &hostConfig.RestartPolicy) |
|
204 |
- env.GetJson("Ulimits", &hostConfig.Ulimits) |
|
205 |
- env.GetJson("LogConfig", &hostConfig.LogConfig) |
|
206 |
- hostConfig.SecurityOpt = env.GetList("SecurityOpt") |
|
207 |
- if Binds := env.GetList("Binds"); Binds != nil { |
|
208 |
- hostConfig.Binds = Binds |
|
209 |
- } |
|
210 |
- if Links := env.GetList("Links"); Links != nil { |
|
211 |
- hostConfig.Links = Links |
|
212 |
- } |
|
213 |
- if Dns := env.GetList("Dns"); Dns != nil { |
|
214 |
- hostConfig.Dns = Dns |
|
215 |
- } |
|
216 |
- if DnsSearch := env.GetList("DnsSearch"); DnsSearch != nil { |
|
217 |
- hostConfig.DnsSearch = DnsSearch |
|
218 |
- } |
|
219 |
- if ExtraHosts := env.GetList("ExtraHosts"); ExtraHosts != nil { |
|
220 |
- hostConfig.ExtraHosts = ExtraHosts |
|
221 |
- } |
|
222 |
- if VolumesFrom := env.GetList("VolumesFrom"); VolumesFrom != nil { |
|
223 |
- hostConfig.VolumesFrom = VolumesFrom |
|
224 |
- } |
|
225 |
- if CapAdd := env.GetList("CapAdd"); CapAdd != nil { |
|
226 |
- hostConfig.CapAdd = CapAdd |
|
227 |
- } |
|
228 |
- if CapDrop := env.GetList("CapDrop"); CapDrop != nil { |
|
229 |
- hostConfig.CapDrop = CapDrop |
|
181 |
+func DecodeHostConfig(src io.Reader) (*HostConfig, error) { |
|
182 |
+ decoder := json.NewDecoder(src) |
|
183 |
+ |
|
184 |
+ var w hostConfigWrapper |
|
185 |
+ if err := decoder.Decode(&w); err != nil { |
|
186 |
+ return nil, err |
|
230 | 187 |
} |
231 | 188 |
|
232 |
- return hostConfig |
|
189 |
+ hc := w.GetHostConfig() |
|
190 |
+ |
|
191 |
+ return hc, nil |
|
233 | 192 |
} |
... | ... |
@@ -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 |
|
... | ... |
@@ -185,21 +185,22 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe |
185 | 185 |
|
186 | 186 |
var ( |
187 | 187 |
parsedArgs = cmd.Args() |
188 |
- runCmd []string |
|
189 |
- entrypoint []string |
|
188 |
+ runCmd *Command |
|
189 |
+ entrypoint *Entrypoint |
|
190 | 190 |
image = cmd.Arg(0) |
191 | 191 |
) |
192 | 192 |
if len(parsedArgs) > 1 { |
193 |
- runCmd = parsedArgs[1:] |
|
193 |
+ runCmd = NewCommand(parsedArgs[1:]...) |
|
194 | 194 |
} |
195 | 195 |
if *flEntrypoint != "" { |
196 |
- entrypoint = []string{*flEntrypoint} |
|
196 |
+ entrypoint = NewEntrypoint(*flEntrypoint) |
|
197 | 197 |
} |
198 | 198 |
|
199 |
- lxcConf, err := parseKeyValueOpts(flLxcOpts) |
|
199 |
+ lc, err := parseKeyValueOpts(flLxcOpts) |
|
200 | 200 |
if err != nil { |
201 | 201 |
return nil, nil, cmd, err |
202 | 202 |
} |
203 |
+ lxcConf := NewLxcConfig(lc) |
|
203 | 204 |
|
204 | 205 |
var ( |
205 | 206 |
domainname string |
... | ... |
@@ -288,10 +289,6 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe |
288 | 288 |
Tty: *flTty, |
289 | 289 |
NetworkDisabled: !*flNetwork, |
290 | 290 |
OpenStdin: *flStdin, |
291 |
- Memory: flMemory, // FIXME: for backward compatibility |
|
292 |
- MemorySwap: MemorySwap, // FIXME: for backward compatibility |
|
293 |
- CpuShares: *flCpuShares, // FIXME: for backward compatibility |
|
294 |
- Cpuset: *flCpusetCpus, // FIXME: for backward compatibility |
|
295 | 291 |
AttachStdin: attachStdin, |
296 | 292 |
AttachStdout: attachStdout, |
297 | 293 |
AttachStderr: attachStderr, |