Merge pull request #12253 from calavera/remove_job_from_start_and_create

Remove engine.Job from Start and Create
This commit is contained in:
Brian Goff 2015-04-15 21:49:25 -04:00
commit de923f59b3
23 changed files with 534 additions and 334 deletions

View File

@ -33,6 +33,7 @@ import (
"github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/version" "github.com/docker/docker/pkg/version"
"github.com/docker/docker/registry" "github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/utils" "github.com/docker/docker/utils"
) )
@ -809,30 +810,23 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re
return err return err
} }
var ( var (
job = eng.Job("create", r.Form.Get("name")) warnings []string
outWarnings []string name = r.Form.Get("name")
stdoutBuffer = bytes.NewBuffer(nil)
warnings = bytes.NewBuffer(nil)
) )
if err := job.DecodeEnv(r.Body); err != nil { config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
if err != nil {
return err return err
} }
// Read container ID from the first line of stdout
job.Stdout.Add(stdoutBuffer) containerId, warnings, err := getDaemon(eng).ContainerCreate(name, config, hostConfig)
// Read warnings from stderr if err != nil {
job.Stderr.Add(warnings)
if err := job.Run(); err != nil {
return err return err
} }
// Parse warnings from stderr
scanner := bufio.NewScanner(warnings)
for scanner.Scan() {
outWarnings = append(outWarnings, scanner.Text())
}
return writeJSON(w, http.StatusCreated, &types.ContainerCreateResponse{ return writeJSON(w, http.StatusCreated, &types.ContainerCreateResponse{
ID: engine.Tail(stdoutBuffer, 1), ID: containerId,
Warnings: outWarnings, Warnings: warnings,
}) })
} }
@ -924,10 +918,6 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res
if vars == nil { if vars == nil {
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
var (
name = vars["name"]
job = eng.Job("start", name)
)
// If contentLength is -1, we can assumed chunked encoding // If contentLength is -1, we can assumed chunked encoding
// or more technically that the length is unknown // or more technically that the length is unknown
@ -935,17 +925,21 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res
// net/http otherwise seems to swallow any headers related to chunked encoding // net/http otherwise seems to swallow any headers related to chunked encoding
// including r.TransferEncoding // including r.TransferEncoding
// allow a nil body for backwards compatibility // allow a nil body for backwards compatibility
var hostConfig *runconfig.HostConfig
if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) { if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) {
if err := checkForJson(r); err != nil { if err := checkForJson(r); err != nil {
return err return err
} }
if err := job.DecodeEnv(r.Body); err != nil { c, err := runconfig.DecodeHostConfig(r.Body)
if err != nil {
return err return err
} }
hostConfig = c
} }
if err := job.Run(); err != nil { if err := getDaemon(eng).ContainerStart(vars["name"], hostConfig); err != nil {
if err.Error() == "Container already started" { if err.Error() == "Container already started" {
w.WriteHeader(http.StatusNotModified) w.WriteHeader(http.StatusNotModified)
return nil return nil

View File

@ -262,7 +262,7 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
b.Config.Cmd = config.Cmd b.Config.Cmd = config.Cmd
runconfig.Merge(b.Config, config) runconfig.Merge(b.Config, config)
defer func(cmd []string) { b.Config.Cmd = cmd }(cmd) defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd)
logrus.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd) logrus.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd)
@ -301,13 +301,15 @@ func run(b *Builder, args []string, attributes map[string]bool, original string)
// Argument handling is the same as RUN. // Argument handling is the same as RUN.
// //
func cmd(b *Builder, args []string, attributes map[string]bool, original string) error { func cmd(b *Builder, args []string, attributes map[string]bool, original string) error {
b.Config.Cmd = handleJsonArgs(args, attributes) cmdSlice := handleJsonArgs(args, attributes)
if !attributes["json"] { if !attributes["json"] {
b.Config.Cmd = append([]string{"/bin/sh", "-c"}, b.Config.Cmd...) cmdSlice = append([]string{"/bin/sh", "-c"}, cmdSlice...)
} }
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %q", b.Config.Cmd)); err != nil { b.Config.Cmd = runconfig.NewCommand(cmdSlice...)
if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil {
return err return err
} }
@ -332,13 +334,13 @@ func entrypoint(b *Builder, args []string, attributes map[string]bool, original
switch { switch {
case attributes["json"]: case attributes["json"]:
// ENTRYPOINT ["echo", "hi"] // ENTRYPOINT ["echo", "hi"]
b.Config.Entrypoint = parsed b.Config.Entrypoint = runconfig.NewEntrypoint(parsed...)
case len(parsed) == 0: case len(parsed) == 0:
// ENTRYPOINT [] // ENTRYPOINT []
b.Config.Entrypoint = nil b.Config.Entrypoint = nil
default: default:
// ENTRYPOINT echo hi // ENTRYPOINT echo hi
b.Config.Entrypoint = []string{"/bin/sh", "-c", parsed[0]} b.Config.Entrypoint = runconfig.NewEntrypoint("/bin/sh", "-c", parsed[0])
} }
// when setting the entrypoint if a CMD was not explicitly set then // when setting the entrypoint if a CMD was not explicitly set then

View File

@ -61,7 +61,7 @@ func (b *Builder) readContext(context io.Reader) error {
return nil return nil
} }
func (b *Builder) commit(id string, autoCmd []string, comment string) error { func (b *Builder) commit(id string, autoCmd *runconfig.Command, comment string) error {
if b.disableCommit { if b.disableCommit {
return nil return nil
} }
@ -71,8 +71,8 @@ func (b *Builder) commit(id string, autoCmd []string, comment string) error {
b.Config.Image = b.image b.Config.Image = b.image
if id == "" { if id == "" {
cmd := b.Config.Cmd cmd := b.Config.Cmd
b.Config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment} b.Config.Cmd = runconfig.NewCommand("/bin/sh", "-c", "#(nop) "+comment)
defer func(cmd []string) { b.Config.Cmd = cmd }(cmd) defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd)
hit, err := b.probeCache() hit, err := b.probeCache()
if err != nil { if err != nil {
@ -182,8 +182,8 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowDecomp
} }
cmd := b.Config.Cmd cmd := b.Config.Cmd
b.Config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest)} b.Config.Cmd = runconfig.NewCommand("/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest))
defer func(cmd []string) { b.Config.Cmd = cmd }(cmd) defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd)
hit, err := b.probeCache() hit, err := b.probeCache()
if err != nil { if err != nil {
@ -560,12 +560,13 @@ func (b *Builder) create() (*daemon.Container, error) {
b.TmpContainers[c.ID] = struct{}{} b.TmpContainers[c.ID] = struct{}{}
fmt.Fprintf(b.OutStream, " ---> Running in %s\n", stringid.TruncateID(c.ID)) fmt.Fprintf(b.OutStream, " ---> Running in %s\n", stringid.TruncateID(c.ID))
if len(config.Cmd) > 0 { if config.Cmd.Len() > 0 {
// override the entry point that may have been picked up from the base image // override the entry point that may have been picked up from the base image
c.Path = config.Cmd[0] s := config.Cmd.Slice()
c.Args = config.Cmd[1:] c.Path = s[0]
c.Args = s[1:]
} else { } else {
config.Cmd = []string{} config.Cmd = runconfig.NewCommand()
} }
return c, nil return c, nil

View File

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/docker/docker/engine"
"github.com/docker/docker/graph" "github.com/docker/docker/graph"
"github.com/docker/docker/image" "github.com/docker/docker/image"
"github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/parsers"
@ -12,36 +11,28 @@ import (
"github.com/docker/libcontainer/label" "github.com/docker/libcontainer/label"
) )
func (daemon *Daemon) ContainerCreate(job *engine.Job) error { func (daemon *Daemon) ContainerCreate(name string, config *runconfig.Config, hostConfig *runconfig.HostConfig) (string, []string, error) {
var name string var warnings []string
if len(job.Args) == 1 {
name = job.Args[0]
} else if len(job.Args) > 1 {
return fmt.Errorf("Usage: %s", job.Name)
}
config := runconfig.ContainerConfigFromJob(job) if hostConfig.LxcConf.Len() > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") {
hostConfig := runconfig.ContainerHostConfigFromJob(job) return "", warnings, fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name())
if len(hostConfig.LxcConf) > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") {
return fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name())
} }
if hostConfig.Memory != 0 && hostConfig.Memory < 4194304 { if hostConfig.Memory != 0 && hostConfig.Memory < 4194304 {
return fmt.Errorf("Minimum memory limit allowed is 4MB") return "", warnings, fmt.Errorf("Minimum memory limit allowed is 4MB")
} }
if hostConfig.Memory > 0 && !daemon.SystemConfig().MemoryLimit { if hostConfig.Memory > 0 && !daemon.SystemConfig().MemoryLimit {
job.Errorf("Your kernel does not support memory limit capabilities. Limitation discarded.\n") warnings = append(warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.\n")
hostConfig.Memory = 0 hostConfig.Memory = 0
} }
if hostConfig.Memory > 0 && hostConfig.MemorySwap != -1 && !daemon.SystemConfig().SwapLimit { if hostConfig.Memory > 0 && hostConfig.MemorySwap != -1 && !daemon.SystemConfig().SwapLimit {
job.Errorf("Your kernel does not support swap limit capabilities. Limitation discarded.\n") warnings = append(warnings, "Your kernel does not support swap limit capabilities. Limitation discarded.\n")
hostConfig.MemorySwap = -1 hostConfig.MemorySwap = -1
} }
if hostConfig.Memory > 0 && hostConfig.MemorySwap > 0 && hostConfig.MemorySwap < hostConfig.Memory { if hostConfig.Memory > 0 && hostConfig.MemorySwap > 0 && hostConfig.MemorySwap < hostConfig.Memory {
return fmt.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.\n") return "", warnings, fmt.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.\n")
} }
if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 { if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 {
return fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.\n") return "", warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.\n")
} }
container, buildWarnings, err := daemon.Create(config, hostConfig, name) container, buildWarnings, err := daemon.Create(config, hostConfig, name)
@ -51,22 +42,18 @@ func (daemon *Daemon) ContainerCreate(job *engine.Job) error {
if tag == "" { if tag == "" {
tag = graph.DEFAULTTAG tag = graph.DEFAULTTAG
} }
return fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag) return "", warnings, fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag)
} }
return err return "", warnings, err
} }
if !container.Config.NetworkDisabled && daemon.SystemConfig().IPv4ForwardingDisabled { if !container.Config.NetworkDisabled && daemon.SystemConfig().IPv4ForwardingDisabled {
job.Errorf("IPv4 forwarding is disabled.\n") warnings = append(warnings, "IPv4 forwarding is disabled.\n")
} }
container.LogEvent("create") container.LogEvent("create")
warnings = append(warnings, buildWarnings...)
job.Printf("%s\n", container.ID) return container.ID, warnings, nil
for _, warning := range buildWarnings {
job.Errorf("%s\n", warning)
}
return nil
} }
// Create creates a new container from the given configuration with a given name. // Create creates a new container from the given configuration with a given name.

View File

@ -119,10 +119,8 @@ type Daemon struct {
func (daemon *Daemon) Install(eng *engine.Engine) error { func (daemon *Daemon) Install(eng *engine.Engine) error {
for name, method := range map[string]engine.Handler{ for name, method := range map[string]engine.Handler{
"container_inspect": daemon.ContainerInspect, "container_inspect": daemon.ContainerInspect,
"create": daemon.ContainerCreate,
"info": daemon.CmdInfo, "info": daemon.CmdInfo,
"restart": daemon.ContainerRestart, "restart": daemon.ContainerRestart,
"start": daemon.ContainerStart,
"execCreate": daemon.ContainerExecCreate, "execCreate": daemon.ContainerExecCreate,
"execStart": daemon.ContainerExecStart, "execStart": daemon.ContainerExecStart,
} { } {
@ -483,7 +481,7 @@ func (daemon *Daemon) mergeAndVerifyConfig(config *runconfig.Config, img *image.
return nil, err return nil, err
} }
} }
if len(config.Entrypoint) == 0 && len(config.Cmd) == 0 { if config.Entrypoint.Len() == 0 && config.Cmd.Len() == 0 {
return nil, fmt.Errorf("No command specified") return nil, fmt.Errorf("No command specified")
} }
return warnings, nil return warnings, nil
@ -575,17 +573,20 @@ func (daemon *Daemon) generateHostname(id string, config *runconfig.Config) {
} }
} }
func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint, configCmd []string) (string, []string) { func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint *runconfig.Entrypoint, configCmd *runconfig.Command) (string, []string) {
var ( var (
entrypoint string entrypoint string
args []string args []string
) )
if len(configEntrypoint) != 0 {
entrypoint = configEntrypoint[0] cmdSlice := configCmd.Slice()
args = append(configEntrypoint[1:], configCmd...) if configEntrypoint.Len() != 0 {
eSlice := configEntrypoint.Slice()
entrypoint = eSlice[0]
args = append(eSlice[1:], cmdSlice...)
} else { } else {
entrypoint = configCmd[0] entrypoint = cmdSlice[0]
args = configCmd[1:] args = cmdSlice[1:]
} }
return entrypoint, args return entrypoint, args
} }

View File

@ -132,7 +132,8 @@ func (d *Daemon) ContainerExecCreate(job *engine.Job) error {
return err return err
} }
entrypoint, args := d.getEntrypointAndArgs(nil, config.Cmd) cmd := runconfig.NewCommand(config.Cmd...)
entrypoint, args := d.getEntrypointAndArgs(runconfig.NewEntrypoint(), cmd)
processConfig := execdriver.ProcessConfig{ processConfig := execdriver.ProcessConfig{
Tty: config.Tty, Tty: config.Tty,

View File

@ -3,18 +3,10 @@ package daemon
import ( import (
"fmt" "fmt"
"github.com/docker/docker/engine"
"github.com/docker/docker/runconfig" "github.com/docker/docker/runconfig"
) )
func (daemon *Daemon) ContainerStart(job *engine.Job) error { func (daemon *Daemon) ContainerStart(name string, hostConfig *runconfig.HostConfig) error {
if len(job.Args) < 1 {
return fmt.Errorf("Usage: %s container_id", job.Name)
}
var (
name = job.Args[0]
)
container, err := daemon.Get(name) container, err := daemon.Get(name)
if err != nil { if err != nil {
return err return err
@ -28,15 +20,14 @@ func (daemon *Daemon) ContainerStart(job *engine.Job) error {
return fmt.Errorf("Container already started") return fmt.Errorf("Container already started")
} }
// If no environment was set, then no hostconfig was passed.
// This is kept for backward compatibility - hostconfig should be passed when // This is kept for backward compatibility - hostconfig should be passed when
// creating a container, not during start. // creating a container, not during start.
if len(job.Environ()) > 0 { if hostConfig != nil {
hostConfig := runconfig.ContainerHostConfigFromJob(job)
if err := daemon.setHostConfig(container, hostConfig); err != nil { if err := daemon.setHostConfig(container, hostConfig); err != nil {
return err return err
} }
} }
if err := container.Start(); err != nil { if err := container.Start(); err != nil {
container.LogEvent("die") container.LogEvent("die")
return fmt.Errorf("Cannot start container %s: %s", name, err) return fmt.Errorf("Cannot start container %s: %s", name, err)

View File

@ -42,7 +42,8 @@ func mergeLxcConfIntoOptions(hostConfig *runconfig.HostConfig) ([]string, error)
// merge in the lxc conf options into the generic config map // merge in the lxc conf options into the generic config map
if lxcConf := hostConfig.LxcConf; lxcConf != nil { if lxcConf := hostConfig.LxcConf; lxcConf != nil {
for _, pair := range lxcConf { lxSlice := lxcConf.Slice()
for _, pair := range lxSlice {
// because lxc conf gets the driver name lxc.XXXX we need to trim it off // because lxc conf gets the driver name lxc.XXXX we need to trim it off
// and let the lxc driver add it back later if needed // and let the lxc driver add it back later if needed
if !strings.Contains(pair.Key, ".") { if !strings.Contains(pair.Key, ".") {

View File

@ -7,10 +7,11 @@ import (
) )
func TestMergeLxcConfig(t *testing.T) { func TestMergeLxcConfig(t *testing.T) {
kv := []runconfig.KeyValuePair{
{"lxc.cgroups.cpuset", "1,2"},
}
hostConfig := &runconfig.HostConfig{ hostConfig := &runconfig.HostConfig{
LxcConf: []runconfig.KeyValuePair{ LxcConf: runconfig.NewLxcConfig(kv),
{Key: "lxc.cgroups.cpuset", Value: "1,2"},
},
} }
out, err := mergeLxcConfIntoOptions(hostConfig) out, err := mergeLxcConfIntoOptions(hostConfig)

View File

@ -31,7 +31,7 @@ func (s *TagStore) History(name string) ([]*types.ImageHistory, error) {
history = append(history, &types.ImageHistory{ history = append(history, &types.ImageHistory{
ID: img.ID, ID: img.ID,
Created: img.Created.Unix(), Created: img.Created.Unix(),
CreatedBy: strings.Join(img.ContainerConfig.Cmd, " "), CreatedBy: strings.Join(img.ContainerConfig.Cmd.Slice(), " "),
Tags: lookupMap[img.ID], Tags: lookupMap[img.ID],
Size: img.Size, Size: img.Size,
Comment: img.Comment, Comment: img.Comment,

View File

@ -91,7 +91,7 @@ func TestGetContainersTop(t *testing.T) {
containerID := createTestContainer(eng, containerID := createTestContainer(eng,
&runconfig.Config{ &runconfig.Config{
Image: unitTestImageID, Image: unitTestImageID,
Cmd: []string{"/bin/sh", "-c", "cat"}, Cmd: runconfig.NewCommand("/bin/sh", "-c", "cat"),
OpenStdin: true, OpenStdin: true,
}, },
t, t,
@ -168,7 +168,7 @@ func TestPostCommit(t *testing.T) {
containerID := createTestContainer(eng, containerID := createTestContainer(eng,
&runconfig.Config{ &runconfig.Config{
Image: unitTestImageID, Image: unitTestImageID,
Cmd: []string{"touch", "/test"}, Cmd: runconfig.NewCommand("touch", "/test"),
}, },
t, t,
) )
@ -201,9 +201,8 @@ func TestPostContainersCreate(t *testing.T) {
defer mkDaemonFromEngine(eng, t).Nuke() defer mkDaemonFromEngine(eng, t).Nuke()
configJSON, err := json.Marshal(&runconfig.Config{ configJSON, err := json.Marshal(&runconfig.Config{
Image: unitTestImageID, Image: unitTestImageID,
Memory: 33554432, Cmd: runconfig.NewCommand("touch", "/test"),
Cmd: []string{"touch", "/test"},
}) })
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -242,9 +241,8 @@ func TestPostJsonVerify(t *testing.T) {
defer mkDaemonFromEngine(eng, t).Nuke() defer mkDaemonFromEngine(eng, t).Nuke()
configJSON, err := json.Marshal(&runconfig.Config{ configJSON, err := json.Marshal(&runconfig.Config{
Image: unitTestImageID, Image: unitTestImageID,
Memory: 33554432, Cmd: runconfig.NewCommand("touch", "/test"),
Cmd: []string{"touch", "/test"},
}) })
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -330,8 +328,8 @@ func TestPostCreateNull(t *testing.T) {
containerAssertExists(eng, containerID, t) containerAssertExists(eng, containerID, t)
c, _ := daemon.Get(containerID) c, _ := daemon.Get(containerID)
if c.Config.Cpuset != "" { if c.HostConfig().CpusetCpus != "" {
t.Fatalf("Cpuset should have been empty - instead its:" + c.Config.Cpuset) t.Fatalf("Cpuset should have been empty - instead its:" + c.HostConfig().CpusetCpus)
} }
} }
@ -342,7 +340,7 @@ func TestPostContainersKill(t *testing.T) {
containerID := createTestContainer(eng, containerID := createTestContainer(eng,
&runconfig.Config{ &runconfig.Config{
Image: unitTestImageID, Image: unitTestImageID,
Cmd: []string{"/bin/cat"}, Cmd: runconfig.NewCommand("/bin/cat"),
OpenStdin: true, OpenStdin: true,
}, },
t, t,
@ -379,7 +377,7 @@ func TestPostContainersRestart(t *testing.T) {
containerID := createTestContainer(eng, containerID := createTestContainer(eng,
&runconfig.Config{ &runconfig.Config{
Image: unitTestImageID, Image: unitTestImageID,
Cmd: []string{"/bin/top"}, Cmd: runconfig.NewCommand("/bin/top"),
OpenStdin: true, OpenStdin: true,
}, },
t, t,
@ -423,7 +421,7 @@ func TestPostContainersStart(t *testing.T) {
eng, eng,
&runconfig.Config{ &runconfig.Config{
Image: unitTestImageID, Image: unitTestImageID,
Cmd: []string{"/bin/cat"}, Cmd: runconfig.NewCommand("/bin/cat"),
OpenStdin: true, OpenStdin: true,
}, },
t, t,
@ -473,7 +471,7 @@ func TestPostContainersStop(t *testing.T) {
containerID := createTestContainer(eng, containerID := createTestContainer(eng,
&runconfig.Config{ &runconfig.Config{
Image: unitTestImageID, Image: unitTestImageID,
Cmd: []string{"/bin/top"}, Cmd: runconfig.NewCommand("/bin/top"),
OpenStdin: true, OpenStdin: true,
}, },
t, t,
@ -525,7 +523,7 @@ func TestPostContainersWait(t *testing.T) {
containerID := createTestContainer(eng, containerID := createTestContainer(eng,
&runconfig.Config{ &runconfig.Config{
Image: unitTestImageID, Image: unitTestImageID,
Cmd: []string{"/bin/sleep", "1"}, Cmd: runconfig.NewCommand("/bin/sleep", "1"),
OpenStdin: true, OpenStdin: true,
}, },
t, t,
@ -561,7 +559,7 @@ func TestPostContainersAttach(t *testing.T) {
containerID := createTestContainer(eng, containerID := createTestContainer(eng,
&runconfig.Config{ &runconfig.Config{
Image: unitTestImageID, Image: unitTestImageID,
Cmd: []string{"/bin/cat"}, Cmd: runconfig.NewCommand("/bin/cat"),
OpenStdin: true, OpenStdin: true,
}, },
t, t,
@ -637,7 +635,7 @@ func TestPostContainersAttachStderr(t *testing.T) {
containerID := createTestContainer(eng, containerID := createTestContainer(eng,
&runconfig.Config{ &runconfig.Config{
Image: unitTestImageID, Image: unitTestImageID,
Cmd: []string{"/bin/sh", "-c", "/bin/cat >&2"}, Cmd: runconfig.NewCommand("/bin/sh", "-c", "/bin/cat >&2"),
OpenStdin: true, OpenStdin: true,
}, },
t, t,
@ -818,7 +816,7 @@ func TestPostContainersCopy(t *testing.T) {
containerID := createTestContainer(eng, containerID := createTestContainer(eng,
&runconfig.Config{ &runconfig.Config{
Image: unitTestImageID, Image: unitTestImageID,
Cmd: []string{"touch", "/test.txt"}, Cmd: runconfig.NewCommand("touch", "/test.txt"),
}, },
t, t,
) )

View File

@ -14,7 +14,7 @@ func TestRestartStdin(t *testing.T) {
defer nuke(daemon) defer nuke(daemon)
container, _, err := daemon.Create(&runconfig.Config{ container, _, err := daemon.Create(&runconfig.Config{
Image: GetTestImage(daemon).ID, Image: GetTestImage(daemon).ID,
Cmd: []string{"cat"}, Cmd: runconfig.NewCommand("cat"),
OpenStdin: true, OpenStdin: true,
}, },
@ -79,7 +79,7 @@ func TestStdin(t *testing.T) {
defer nuke(daemon) defer nuke(daemon)
container, _, err := daemon.Create(&runconfig.Config{ container, _, err := daemon.Create(&runconfig.Config{
Image: GetTestImage(daemon).ID, Image: GetTestImage(daemon).ID,
Cmd: []string{"cat"}, Cmd: runconfig.NewCommand("cat"),
OpenStdin: true, OpenStdin: true,
}, },
@ -119,7 +119,7 @@ func TestTty(t *testing.T) {
defer nuke(daemon) defer nuke(daemon)
container, _, err := daemon.Create(&runconfig.Config{ container, _, err := daemon.Create(&runconfig.Config{
Image: GetTestImage(daemon).ID, Image: GetTestImage(daemon).ID,
Cmd: []string{"cat"}, Cmd: runconfig.NewCommand("cat"),
OpenStdin: true, OpenStdin: true,
}, },
@ -160,7 +160,7 @@ func BenchmarkRunSequential(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
container, _, err := daemon.Create(&runconfig.Config{ container, _, err := daemon.Create(&runconfig.Config{
Image: GetTestImage(daemon).ID, Image: GetTestImage(daemon).ID,
Cmd: []string{"echo", "-n", "foo"}, Cmd: runconfig.NewCommand("echo", "-n", "foo"),
}, },
&runconfig.HostConfig{}, &runconfig.HostConfig{},
"", "",
@ -194,7 +194,7 @@ func BenchmarkRunParallel(b *testing.B) {
go func(i int, complete chan error) { go func(i int, complete chan error) {
container, _, err := daemon.Create(&runconfig.Config{ container, _, err := daemon.Create(&runconfig.Config{
Image: GetTestImage(daemon).ID, Image: GetTestImage(daemon).ID,
Cmd: []string{"echo", "-n", "foo"}, Cmd: runconfig.NewCommand("echo", "-n", "foo"),
}, },
&runconfig.HostConfig{}, &runconfig.HostConfig{},
"", "",

View File

@ -255,7 +255,7 @@ func TestDaemonCreate(t *testing.T) {
container, _, err := daemon.Create(&runconfig.Config{ container, _, err := daemon.Create(&runconfig.Config{
Image: GetTestImage(daemon).ID, Image: GetTestImage(daemon).ID,
Cmd: []string{"ls", "-al"}, Cmd: runconfig.NewCommand("ls", "-al"),
}, },
&runconfig.HostConfig{}, &runconfig.HostConfig{},
"", "",
@ -296,15 +296,16 @@ func TestDaemonCreate(t *testing.T) {
} }
// Test that conflict error displays correct details // Test that conflict error displays correct details
cmd := runconfig.NewCommand("ls", "-al")
testContainer, _, _ := daemon.Create( testContainer, _, _ := daemon.Create(
&runconfig.Config{ &runconfig.Config{
Image: GetTestImage(daemon).ID, Image: GetTestImage(daemon).ID,
Cmd: []string{"ls", "-al"}, Cmd: cmd,
}, },
&runconfig.HostConfig{}, &runconfig.HostConfig{},
"conflictname", "conflictname",
) )
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)) { 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)) {
t.Fatalf("Name conflict error doesn't include the correct short id. Message was: %v", err) t.Fatalf("Name conflict error doesn't include the correct short id. Message was: %v", err)
} }
@ -316,7 +317,7 @@ func TestDaemonCreate(t *testing.T) {
if _, _, err := daemon.Create( if _, _, err := daemon.Create(
&runconfig.Config{ &runconfig.Config{
Image: GetTestImage(daemon).ID, Image: GetTestImage(daemon).ID,
Cmd: []string{}, Cmd: runconfig.NewCommand(),
}, },
&runconfig.HostConfig{}, &runconfig.HostConfig{},
"", "",
@ -326,7 +327,7 @@ func TestDaemonCreate(t *testing.T) {
config := &runconfig.Config{ config := &runconfig.Config{
Image: GetTestImage(daemon).ID, Image: GetTestImage(daemon).ID,
Cmd: []string{"/bin/ls"}, Cmd: runconfig.NewCommand("/bin/ls"),
PortSpecs: []string{"80"}, PortSpecs: []string{"80"},
} }
container, _, err = daemon.Create(config, &runconfig.HostConfig{}, "") container, _, err = daemon.Create(config, &runconfig.HostConfig{}, "")
@ -339,7 +340,7 @@ func TestDaemonCreate(t *testing.T) {
// test expose 80:8000 // test expose 80:8000
container, warnings, err := daemon.Create(&runconfig.Config{ container, warnings, err := daemon.Create(&runconfig.Config{
Image: GetTestImage(daemon).ID, Image: GetTestImage(daemon).ID,
Cmd: []string{"ls", "-al"}, Cmd: runconfig.NewCommand("ls", "-al"),
PortSpecs: []string{"80:8000"}, PortSpecs: []string{"80:8000"},
}, },
&runconfig.HostConfig{}, &runconfig.HostConfig{},
@ -359,7 +360,7 @@ func TestDestroy(t *testing.T) {
container, _, err := daemon.Create(&runconfig.Config{ container, _, err := daemon.Create(&runconfig.Config{
Image: GetTestImage(daemon).ID, Image: GetTestImage(daemon).ID,
Cmd: []string{"ls", "-al"}, Cmd: runconfig.NewCommand("ls", "-al"),
}, },
&runconfig.HostConfig{}, &runconfig.HostConfig{},
"") "")
@ -422,14 +423,13 @@ func TestGet(t *testing.T) {
func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daemon.Container, string) { func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daemon.Container, string) {
var ( var (
err error err error
id string id string
outputBuffer = bytes.NewBuffer(nil) strPort string
strPort string eng = NewTestEngine(t)
eng = NewTestEngine(t) daemon = mkDaemonFromEngine(eng, t)
daemon = mkDaemonFromEngine(eng, t) port = 5554
port = 5554 p nat.Port
p nat.Port
) )
defer func() { defer func() {
if err != nil { if err != nil {
@ -452,16 +452,14 @@ func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daem
p = nat.Port(fmt.Sprintf("%s/%s", strPort, proto)) p = nat.Port(fmt.Sprintf("%s/%s", strPort, proto))
ep[p] = struct{}{} ep[p] = struct{}{}
jobCreate := eng.Job("create") c := &runconfig.Config{
jobCreate.Setenv("Image", unitTestImageID) Image: unitTestImageID,
jobCreate.SetenvList("Cmd", []string{"sh", "-c", cmd}) Cmd: runconfig.NewCommand("sh", "-c", cmd),
jobCreate.SetenvList("PortSpecs", []string{fmt.Sprintf("%s/%s", strPort, proto)}) PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)},
jobCreate.SetenvJson("ExposedPorts", ep) ExposedPorts: ep,
jobCreate.Stdout.Add(outputBuffer)
if err := jobCreate.Run(); err != nil {
t.Fatal(err)
} }
id = engine.Tail(outputBuffer, 1)
id, _, err = daemon.ContainerCreate(unitTestImageID, c, &runconfig.HostConfig{})
// FIXME: this relies on the undocumented behavior of daemon.Create // FIXME: this relies on the undocumented behavior of daemon.Create
// which will return a nil error AND container if the exposed ports // which will return a nil error AND container if the exposed ports
// are invalid. That behavior should be fixed! // are invalid. That behavior should be fixed!
@ -472,15 +470,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daem
} }
jobStart := eng.Job("start", id) if err := daemon.ContainerStart(id, &runconfig.HostConfig{}); err != nil {
portBindings := make(map[nat.Port][]nat.PortBinding)
portBindings[p] = []nat.PortBinding{
{},
}
if err := jobStart.SetenvJson("PortsBindings", portBindings); err != nil {
t.Fatal(err)
}
if err := jobStart.Run(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -731,20 +721,15 @@ func TestContainerNameValidation(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
var outputBuffer = bytes.NewBuffer(nil) containerId, _, err := daemon.ContainerCreate(test.Name, config, &runconfig.HostConfig{})
job := eng.Job("create", test.Name) if err != nil {
if err := job.ImportEnv(config); err != nil {
t.Fatal(err)
}
job.Stdout.Add(outputBuffer)
if err := job.Run(); err != nil {
if !test.Valid { if !test.Valid {
continue continue
} }
t.Fatal(err) t.Fatal(err)
} }
container, err := daemon.Get(engine.Tail(outputBuffer, 1)) container, err := daemon.Get(containerId)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -759,7 +744,6 @@ func TestContainerNameValidation(t *testing.T) {
t.Fatalf("Container /%s has ID %s instead of %s", test.Name, c.ID, container.ID) t.Fatalf("Container /%s has ID %s instead of %s", test.Name, c.ID, container.ID)
} }
} }
} }
func TestLinkChildContainer(t *testing.T) { func TestLinkChildContainer(t *testing.T) {
@ -876,7 +860,7 @@ func TestDestroyWithInitLayer(t *testing.T) {
container, _, err := daemon.Create(&runconfig.Config{ container, _, err := daemon.Create(&runconfig.Config{
Image: GetTestImage(daemon).ID, Image: GetTestImage(daemon).ID,
Cmd: []string{"ls", "-al"}, Cmd: runconfig.NewCommand("ls", "-al"),
}, },
&runconfig.HostConfig{}, &runconfig.HostConfig{},
"") "")

View File

@ -44,16 +44,11 @@ func mkDaemon(f Fataler) *daemon.Daemon {
} }
func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler, name string) (shortId string) { func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler, name string) (shortId string) {
job := eng.Job("create", name) containerId, _, err := getDaemon(eng).ContainerCreate(name, config, &runconfig.HostConfig{})
if err := job.ImportEnv(config); err != nil { if err != nil {
f.Fatal(err) f.Fatal(err)
} }
var outputBuffer = bytes.NewBuffer(nil) return containerId
job.Stdout.Add(outputBuffer)
if err := job.Run(); err != nil {
f.Fatal(err)
}
return engine.Tail(outputBuffer, 1)
} }
func createTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler) (shortId string) { 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
} }
func startContainer(eng *engine.Engine, id string, t Fataler) { func startContainer(eng *engine.Engine, id string, t Fataler) {
job := eng.Job("start", id) if err := getDaemon(eng).ContainerStart(id, &runconfig.HostConfig{}); err != nil {
if err := job.Run(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }

View File

@ -10,25 +10,25 @@ func Compare(a, b *Config) bool {
if a.AttachStdout != b.AttachStdout || if a.AttachStdout != b.AttachStdout ||
a.AttachStderr != b.AttachStderr || a.AttachStderr != b.AttachStderr ||
a.User != b.User || a.User != b.User ||
a.Memory != b.Memory ||
a.MemorySwap != b.MemorySwap ||
a.CpuShares != b.CpuShares ||
a.OpenStdin != b.OpenStdin || a.OpenStdin != b.OpenStdin ||
a.Tty != b.Tty { a.Tty != b.Tty {
return false return false
} }
if len(a.Cmd) != len(b.Cmd) ||
if a.Cmd.Len() != b.Cmd.Len() ||
len(a.Env) != len(b.Env) || len(a.Env) != len(b.Env) ||
len(a.Labels) != len(b.Labels) || len(a.Labels) != len(b.Labels) ||
len(a.PortSpecs) != len(b.PortSpecs) || len(a.PortSpecs) != len(b.PortSpecs) ||
len(a.ExposedPorts) != len(b.ExposedPorts) || len(a.ExposedPorts) != len(b.ExposedPorts) ||
len(a.Entrypoint) != len(b.Entrypoint) || a.Entrypoint.Len() != b.Entrypoint.Len() ||
len(a.Volumes) != len(b.Volumes) { len(a.Volumes) != len(b.Volumes) {
return false return false
} }
for i := 0; i < len(a.Cmd); i++ { aCmd := a.Cmd.Slice()
if a.Cmd[i] != b.Cmd[i] { bCmd := b.Cmd.Slice()
for i := 0; i < len(aCmd); i++ {
if aCmd[i] != bCmd[i] {
return false return false
} }
} }
@ -52,8 +52,11 @@ func Compare(a, b *Config) bool {
return false return false
} }
} }
for i := 0; i < len(a.Entrypoint); i++ {
if a.Entrypoint[i] != b.Entrypoint[i] { aEntrypoint := a.Entrypoint.Slice()
bEntrypoint := b.Entrypoint.Slice()
for i := 0; i < len(aEntrypoint); i++ {
if aEntrypoint[i] != bEntrypoint[i] {
return false return false
} }
} }

View File

@ -1,10 +1,103 @@
package runconfig package runconfig
import ( import (
"github.com/docker/docker/engine" "encoding/json"
"io"
"github.com/docker/docker/nat" "github.com/docker/docker/nat"
) )
// Entrypoint encapsulates the container entrypoint.
// It might be represented as a string or an array of strings.
// We need to override the json decoder to accept both options.
// The JSON decoder will fail if the api sends an string and
// we try to decode it into an array of string.
type Entrypoint struct {
parts []string
}
func (e *Entrypoint) MarshalJSON() ([]byte, error) {
if e == nil {
return []byte{}, nil
}
return json.Marshal(e.Slice())
}
// UnmarshalJSON decoded the entrypoint whether it's a string or an array of strings.
func (e *Entrypoint) UnmarshalJSON(b []byte) error {
if len(b) == 0 {
return nil
}
p := make([]string, 0, 1)
if err := json.Unmarshal(b, &p); err != nil {
p = append(p, string(b))
}
e.parts = p
return nil
}
func (e *Entrypoint) Len() int {
if e == nil {
return 0
}
return len(e.parts)
}
func (e *Entrypoint) Slice() []string {
if e == nil {
return nil
}
return e.parts
}
func NewEntrypoint(parts ...string) *Entrypoint {
return &Entrypoint{parts}
}
type Command struct {
parts []string
}
func (e *Command) MarshalJSON() ([]byte, error) {
if e == nil {
return []byte{}, nil
}
return json.Marshal(e.Slice())
}
// UnmarshalJSON decoded the entrypoint whether it's a string or an array of strings.
func (e *Command) UnmarshalJSON(b []byte) error {
if len(b) == 0 {
return nil
}
p := make([]string, 0, 1)
if err := json.Unmarshal(b, &p); err != nil {
p = append(p, string(b))
}
e.parts = p
return nil
}
func (e *Command) Len() int {
if e == nil {
return 0
}
return len(e.parts)
}
func (e *Command) Slice() []string {
if e == nil {
return nil
}
return e.parts
}
func NewCommand(parts ...string) *Command {
return &Command{parts}
}
// Note: the Config structure should hold only portable information about the container. // Note: the Config structure should hold only portable information about the container.
// Here, "portable" means "independent from the host we are running on". // Here, "portable" means "independent from the host we are running on".
// Non-portable information *should* appear in HostConfig. // Non-portable information *should* appear in HostConfig.
@ -12,10 +105,6 @@ type Config struct {
Hostname string Hostname string
Domainname string Domainname string
User string User string
Memory int64 // FIXME: we keep it for backward compatibility, it has been moved to hostConfig.
MemorySwap int64 // FIXME: it has been moved to hostConfig.
CpuShares int64 // FIXME: it has been moved to hostConfig.
Cpuset string // FIXME: it has been moved to hostConfig and renamed to CpusetCpus.
AttachStdin bool AttachStdin bool
AttachStdout bool AttachStdout bool
AttachStderr bool AttachStderr bool
@ -25,53 +114,37 @@ type Config struct {
OpenStdin bool // Open stdin OpenStdin bool // Open stdin
StdinOnce bool // If true, close stdin after the 1 attached client disconnects. StdinOnce bool // If true, close stdin after the 1 attached client disconnects.
Env []string Env []string
Cmd []string Cmd *Command
Image string // Name of the image as it was passed by the operator (eg. could be symbolic) Image string // Name of the image as it was passed by the operator (eg. could be symbolic)
Volumes map[string]struct{} Volumes map[string]struct{}
WorkingDir string WorkingDir string
Entrypoint []string Entrypoint *Entrypoint
NetworkDisabled bool NetworkDisabled bool
MacAddress string MacAddress string
OnBuild []string OnBuild []string
Labels map[string]string Labels map[string]string
} }
func ContainerConfigFromJob(job *engine.Job) *Config { type ContainerConfigWrapper struct {
config := &Config{ *Config
Hostname: job.Getenv("Hostname"), *hostConfigWrapper
Domainname: job.Getenv("Domainname"), }
User: job.Getenv("User"),
Memory: job.GetenvInt64("Memory"), func (c ContainerConfigWrapper) HostConfig() *HostConfig {
MemorySwap: job.GetenvInt64("MemorySwap"), if c.hostConfigWrapper == nil {
CpuShares: job.GetenvInt64("CpuShares"), return new(HostConfig)
Cpuset: job.Getenv("Cpuset"), }
AttachStdin: job.GetenvBool("AttachStdin"),
AttachStdout: job.GetenvBool("AttachStdout"), return c.hostConfigWrapper.GetHostConfig()
AttachStderr: job.GetenvBool("AttachStderr"), }
Tty: job.GetenvBool("Tty"),
OpenStdin: job.GetenvBool("OpenStdin"), func DecodeContainerConfig(src io.Reader) (*Config, *HostConfig, error) {
StdinOnce: job.GetenvBool("StdinOnce"), decoder := json.NewDecoder(src)
Image: job.Getenv("Image"),
WorkingDir: job.Getenv("WorkingDir"), var w ContainerConfigWrapper
NetworkDisabled: job.GetenvBool("NetworkDisabled"), if err := decoder.Decode(&w); err != nil {
MacAddress: job.Getenv("MacAddress"), return nil, nil, err
} }
job.GetenvJson("ExposedPorts", &config.ExposedPorts)
job.GetenvJson("Volumes", &config.Volumes) return w.Config, w.HostConfig(), nil
if PortSpecs := job.GetenvList("PortSpecs"); PortSpecs != nil {
config.PortSpecs = PortSpecs
}
if Env := job.GetenvList("Env"); Env != nil {
config.Env = Env
}
if Cmd := job.GetenvList("Cmd"); Cmd != nil {
config.Cmd = Cmd
}
job.GetenvJson("Labels", &config.Labels)
if Entrypoint := job.GetenvList("Entrypoint"); Entrypoint != nil {
config.Entrypoint = Entrypoint
}
return config
} }

View File

@ -1,7 +1,9 @@
package runconfig package runconfig
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil"
"strings" "strings"
"testing" "testing"
@ -260,5 +262,39 @@ func TestMerge(t *testing.T) {
t.Fatalf("Expected %q or %q or %q or %q, found %s", 0, 1111, 2222, 3333, portSpecs) t.Fatalf("Expected %q or %q or %q or %q, found %s", 0, 1111, 2222, 3333, portSpecs)
} }
} }
}
func TestDecodeContainerConfig(t *testing.T) {
fixtures := []struct {
file string
entrypoint *Entrypoint
}{
{"fixtures/container_config_1_14.json", NewEntrypoint()},
{"fixtures/container_config_1_17.json", NewEntrypoint("bash")},
{"fixtures/container_config_1_19.json", NewEntrypoint("bash")},
}
for _, f := range fixtures {
b, err := ioutil.ReadFile(f.file)
if err != nil {
t.Fatal(err)
}
c, h, err := DecodeContainerConfig(bytes.NewReader(b))
if err != nil {
t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err))
}
if c.Image != "ubuntu" {
t.Fatalf("Expected ubuntu image, found %s\n", c.Image)
}
if c.Entrypoint.Len() != f.entrypoint.Len() {
t.Fatalf("Expected %v, found %v\n", f.entrypoint, c.Entrypoint)
}
if h.Memory != 1000 {
t.Fatalf("Expected memory to be 1000, found %d\n", h.Memory)
}
}
} }

View File

@ -0,0 +1,30 @@
{
"Hostname":"",
"Domainname": "",
"User":"",
"Memory": 1000,
"MemorySwap":0,
"CpuShares": 512,
"Cpuset": "0,1",
"AttachStdin":false,
"AttachStdout":true,
"AttachStderr":true,
"PortSpecs":null,
"Tty":false,
"OpenStdin":false,
"StdinOnce":false,
"Env":null,
"Cmd":[
"bash"
],
"Image":"ubuntu",
"Volumes":{
"/tmp": {}
},
"WorkingDir":"",
"NetworkDisabled": false,
"ExposedPorts":{
"22/tcp": {}
},
"RestartPolicy": { "Name": "always" }
}

View File

@ -0,0 +1,49 @@
{
"Hostname": "",
"Domainname": "",
"User": "",
"Memory": 1000,
"MemorySwap": 0,
"CpuShares": 512,
"Cpuset": "0,1",
"AttachStdin": false,
"AttachStdout": true,
"AttachStderr": true,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": null,
"Cmd": [
"date"
],
"Entrypoint": "bash",
"Image": "ubuntu",
"Volumes": {
"/tmp": {}
},
"WorkingDir": "",
"NetworkDisabled": false,
"MacAddress": "12:34:56:78:9a:bc",
"ExposedPorts": {
"22/tcp": {}
},
"SecurityOpt": [""],
"HostConfig": {
"Binds": ["/tmp:/tmp"],
"Links": ["redis3:redis"],
"LxcConf": {"lxc.utsname":"docker"},
"PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
"PublishAllPorts": false,
"Privileged": false,
"ReadonlyRootfs": false,
"Dns": ["8.8.8.8"],
"DnsSearch": [""],
"ExtraHosts": null,
"VolumesFrom": ["parent", "other:ro"],
"CapAdd": ["NET_ADMIN"],
"CapDrop": ["MKNOD"],
"RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
"NetworkMode": "bridge",
"Devices": []
}
}

View File

@ -0,0 +1,57 @@
{
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": true,
"AttachStderr": true,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": null,
"Cmd": [
"date"
],
"Entrypoint": "bash",
"Image": "ubuntu",
"Labels": {
"com.example.vendor": "Acme",
"com.example.license": "GPL",
"com.example.version": "1.0"
},
"Volumes": {
"/tmp": {}
},
"WorkingDir": "",
"NetworkDisabled": false,
"MacAddress": "12:34:56:78:9a:bc",
"ExposedPorts": {
"22/tcp": {}
},
"HostConfig": {
"Binds": ["/tmp:/tmp"],
"Links": ["redis3:redis"],
"LxcConf": {"lxc.utsname":"docker"},
"Memory": 1000,
"MemorySwap": 0,
"CpuShares": 512,
"CpusetCpus": "0,1",
"PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
"PublishAllPorts": false,
"Privileged": false,
"ReadonlyRootfs": false,
"Dns": ["8.8.8.8"],
"DnsSearch": [""],
"ExtraHosts": null,
"VolumesFrom": ["parent", "other:ro"],
"CapAdd": ["NET_ADMIN"],
"CapDrop": ["MKNOD"],
"RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
"NetworkMode": "bridge",
"Devices": [],
"Ulimits": [{}],
"LogConfig": { "Type": "json-file", "Config": {} },
"SecurityOpt": [""],
"CgroupParent": ""
}
}

View File

@ -1,9 +1,10 @@
package runconfig package runconfig
import ( import (
"encoding/json"
"io"
"strings" "strings"
"github.com/docker/docker/engine"
"github.com/docker/docker/nat" "github.com/docker/docker/nat"
"github.com/docker/docker/pkg/ulimit" "github.com/docker/docker/pkg/ulimit"
) )
@ -108,10 +109,59 @@ type LogConfig struct {
Config map[string]string Config map[string]string
} }
type LxcConfig struct {
values []KeyValuePair
}
func (c *LxcConfig) MarshalJSON() ([]byte, error) {
if c == nil {
return []byte{}, nil
}
return json.Marshal(c.Slice())
}
func (c *LxcConfig) UnmarshalJSON(b []byte) error {
if len(b) == 0 {
return nil
}
var kv []KeyValuePair
if err := json.Unmarshal(b, &kv); err != nil {
var h map[string]string
if err := json.Unmarshal(b, &h); err != nil {
return err
}
for k, v := range h {
kv = append(kv, KeyValuePair{k, v})
}
}
c.values = kv
return nil
}
func (c *LxcConfig) Len() int {
if c == nil {
return 0
}
return len(c.values)
}
func (c *LxcConfig) Slice() []KeyValuePair {
if c == nil {
return nil
}
return c.values
}
func NewLxcConfig(values []KeyValuePair) *LxcConfig {
return &LxcConfig{values}
}
type HostConfig struct { type HostConfig struct {
Binds []string Binds []string
ContainerIDFile string ContainerIDFile string
LxcConf []KeyValuePair LxcConf *LxcConfig
Memory int64 // Memory limit (in bytes) Memory int64 // Memory limit (in bytes)
MemorySwap int64 // Total memory usage (memory + swap); set `-1` to disable swap MemorySwap int64 // Total memory usage (memory + swap); set `-1` to disable swap
CpuShares int64 // CPU shares (relative weight vs. other containers) CpuShares int64 // CPU shares (relative weight vs. other containers)
@ -139,96 +189,55 @@ type HostConfig struct {
CgroupParent string // Parent cgroup. CgroupParent string // Parent cgroup.
} }
// This is used by the create command when you want to set both the func MergeConfigs(config *Config, hostConfig *HostConfig) *ContainerConfigWrapper {
// Config and the HostConfig in the same call return &ContainerConfigWrapper{
type ConfigAndHostConfig struct { config,
Config &hostConfigWrapper{InnerHostConfig: hostConfig},
HostConfig HostConfig
}
func MergeConfigs(config *Config, hostConfig *HostConfig) *ConfigAndHostConfig {
return &ConfigAndHostConfig{
*config,
*hostConfig,
} }
} }
func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { type hostConfigWrapper struct {
if job.EnvExists("HostConfig") { InnerHostConfig *HostConfig `json:"HostConfig,omitempty"`
hostConfig := HostConfig{} Cpuset string `json:",omitempty"` // Deprecated. Exported for backwards compatibility.
job.GetenvJson("HostConfig", &hostConfig)
// FIXME: These are for backward compatibility, if people use these *HostConfig // Deprecated. Exported to read attrubutes from json that are not in the inner host config structure.
// options with `HostConfig`, we should still make them workable. }
if job.EnvExists("Memory") && hostConfig.Memory == 0 {
hostConfig.Memory = job.GetenvInt64("Memory") func (w hostConfigWrapper) GetHostConfig() *HostConfig {
} hc := w.HostConfig
if job.EnvExists("MemorySwap") && hostConfig.MemorySwap == 0 {
hostConfig.MemorySwap = job.GetenvInt64("MemorySwap") if hc == nil && w.InnerHostConfig != nil {
} hc = w.InnerHostConfig
if job.EnvExists("CpuShares") && hostConfig.CpuShares == 0 { } else if w.InnerHostConfig != nil {
hostConfig.CpuShares = job.GetenvInt64("CpuShares") if hc.Memory != 0 && w.InnerHostConfig.Memory == 0 {
} w.InnerHostConfig.Memory = hc.Memory
if job.EnvExists("Cpuset") && hostConfig.CpusetCpus == "" { }
hostConfig.CpusetCpus = job.Getenv("Cpuset") if hc.MemorySwap != 0 && w.InnerHostConfig.MemorySwap == 0 {
} w.InnerHostConfig.MemorySwap = hc.MemorySwap
}
return &hostConfig if hc.CpuShares != 0 && w.InnerHostConfig.CpuShares == 0 {
} w.InnerHostConfig.CpuShares = hc.CpuShares
}
hostConfig := &HostConfig{
ContainerIDFile: job.Getenv("ContainerIDFile"), hc = w.InnerHostConfig
Memory: job.GetenvInt64("Memory"), }
MemorySwap: job.GetenvInt64("MemorySwap"),
CpuShares: job.GetenvInt64("CpuShares"), if hc != nil && w.Cpuset != "" && hc.CpusetCpus == "" {
CpusetCpus: job.Getenv("CpusetCpus"), hc.CpusetCpus = w.Cpuset
Privileged: job.GetenvBool("Privileged"), }
PublishAllPorts: job.GetenvBool("PublishAllPorts"),
NetworkMode: NetworkMode(job.Getenv("NetworkMode")), return hc
IpcMode: IpcMode(job.Getenv("IpcMode")), }
PidMode: PidMode(job.Getenv("PidMode")),
ReadonlyRootfs: job.GetenvBool("ReadonlyRootfs"), func DecodeHostConfig(src io.Reader) (*HostConfig, error) {
CgroupParent: job.Getenv("CgroupParent"), decoder := json.NewDecoder(src)
}
var w hostConfigWrapper
// FIXME: This is for backward compatibility, if people use `Cpuset` if err := decoder.Decode(&w); err != nil {
// in json, make it workable, we will only pass hostConfig.CpusetCpus return nil, err
// to execDriver. }
if job.EnvExists("Cpuset") && hostConfig.CpusetCpus == "" {
hostConfig.CpusetCpus = job.Getenv("Cpuset") hc := w.GetHostConfig()
}
return hc, nil
job.GetenvJson("LxcConf", &hostConfig.LxcConf)
job.GetenvJson("PortBindings", &hostConfig.PortBindings)
job.GetenvJson("Devices", &hostConfig.Devices)
job.GetenvJson("RestartPolicy", &hostConfig.RestartPolicy)
job.GetenvJson("Ulimits", &hostConfig.Ulimits)
job.GetenvJson("LogConfig", &hostConfig.LogConfig)
hostConfig.SecurityOpt = job.GetenvList("SecurityOpt")
if Binds := job.GetenvList("Binds"); Binds != nil {
hostConfig.Binds = Binds
}
if Links := job.GetenvList("Links"); Links != nil {
hostConfig.Links = Links
}
if Dns := job.GetenvList("Dns"); Dns != nil {
hostConfig.Dns = Dns
}
if DnsSearch := job.GetenvList("DnsSearch"); DnsSearch != nil {
hostConfig.DnsSearch = DnsSearch
}
if ExtraHosts := job.GetenvList("ExtraHosts"); ExtraHosts != nil {
hostConfig.ExtraHosts = ExtraHosts
}
if VolumesFrom := job.GetenvList("VolumesFrom"); VolumesFrom != nil {
hostConfig.VolumesFrom = VolumesFrom
}
if CapAdd := job.GetenvList("CapAdd"); CapAdd != nil {
hostConfig.CapAdd = CapAdd
}
if CapDrop := job.GetenvList("CapDrop"); CapDrop != nil {
hostConfig.CapDrop = CapDrop
}
return hostConfig
} }

View File

@ -11,15 +11,6 @@ func Merge(userConf, imageConf *Config) error {
if userConf.User == "" { if userConf.User == "" {
userConf.User = imageConf.User userConf.User = imageConf.User
} }
if userConf.Memory == 0 {
userConf.Memory = imageConf.Memory
}
if userConf.MemorySwap == 0 {
userConf.MemorySwap = imageConf.MemorySwap
}
if userConf.CpuShares == 0 {
userConf.CpuShares = imageConf.CpuShares
}
if len(userConf.ExposedPorts) == 0 { if len(userConf.ExposedPorts) == 0 {
userConf.ExposedPorts = imageConf.ExposedPorts userConf.ExposedPorts = imageConf.ExposedPorts
} else if imageConf.ExposedPorts != nil { } else if imageConf.ExposedPorts != nil {
@ -94,8 +85,8 @@ func Merge(userConf, imageConf *Config) error {
userConf.Labels = imageConf.Labels userConf.Labels = imageConf.Labels
} }
if len(userConf.Entrypoint) == 0 { if userConf.Entrypoint.Len() == 0 {
if len(userConf.Cmd) == 0 { if userConf.Cmd.Len() == 0 {
userConf.Cmd = imageConf.Cmd userConf.Cmd = imageConf.Cmd
} }

View File

@ -186,21 +186,22 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
var ( var (
parsedArgs = cmd.Args() parsedArgs = cmd.Args()
runCmd []string runCmd *Command
entrypoint []string entrypoint *Entrypoint
image = cmd.Arg(0) image = cmd.Arg(0)
) )
if len(parsedArgs) > 1 { if len(parsedArgs) > 1 {
runCmd = parsedArgs[1:] runCmd = NewCommand(parsedArgs[1:]...)
} }
if *flEntrypoint != "" { if *flEntrypoint != "" {
entrypoint = []string{*flEntrypoint} entrypoint = NewEntrypoint(*flEntrypoint)
} }
lxcConf, err := parseKeyValueOpts(flLxcOpts) lc, err := parseKeyValueOpts(flLxcOpts)
if err != nil { if err != nil {
return nil, nil, cmd, err return nil, nil, cmd, err
} }
lxcConf := NewLxcConfig(lc)
var ( var (
domainname string domainname string
@ -289,10 +290,6 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
Tty: *flTty, Tty: *flTty,
NetworkDisabled: !*flNetwork, NetworkDisabled: !*flNetwork,
OpenStdin: *flStdin, OpenStdin: *flStdin,
Memory: flMemory, // FIXME: for backward compatibility
MemorySwap: MemorySwap, // FIXME: for backward compatibility
CpuShares: *flCpuShares, // FIXME: for backward compatibility
Cpuset: *flCpusetCpus, // FIXME: for backward compatibility
AttachStdin: attachStdin, AttachStdin: attachStdin,
AttachStdout: attachStdout, AttachStdout: attachStdout,
AttachStderr: attachStderr, AttachStderr: attachStderr,