diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 6c8618ee81..9a92258644 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -3,21 +3,18 @@ package runconfig import ( "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/nat" + "github.com/dotcloud/docker/utils" ) type HostConfig struct { Binds []string ContainerIDFile string - LxcConf []KeyValuePair + LxcConf []utils.KeyValuePair Privileged bool PortBindings nat.PortMap Links []string PublishAllPorts bool -} - -type KeyValuePair struct { - Key string - Value string + DriverOptions map[string][]string } func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { @@ -28,6 +25,7 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { } job.GetenvJson("LxcConf", &hostConfig.LxcConf) job.GetenvJson("PortBindings", &hostConfig.PortBindings) + job.GetenvJson("DriverOptions", &hostConfig.DriverOptions) if Binds := job.GetenvList("Binds"); Binds != nil { hostConfig.Binds = Binds } diff --git a/runconfig/parse.go b/runconfig/parse.go index 23c66cd611..43aecdb753 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -51,6 +51,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf flDnsSearch = opts.NewListOpts(opts.ValidateDomain) flVolumesFrom opts.ListOpts flLxcOpts opts.ListOpts + flDriverOpts opts.ListOpts flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)") flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: Run container in the background, print new container id") @@ -83,7 +84,8 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf cmd.Var(&flDns, []string{"#dns", "-dns"}, "Set custom dns servers") cmd.Var(&flDnsSearch, []string{"-dns-search"}, "Set custom dns search domains") cmd.Var(&flVolumesFrom, []string{"#volumes-from", "-volumes-from"}, "Mount volumes from the specified container(s)") - cmd.Var(&flLxcOpts, []string{"#lxc-conf", "-lxc-conf"}, "(lxc exec-driver only) Add custom lxc options --lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") + cmd.Var(&flLxcOpts, []string{"#lxc-conf", "#-lxc-conf"}, "(lxc exec-driver only) Add custom lxc options --lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") + cmd.Var(&flDriverOpts, []string{"o", "-opt"}, "Add custom driver options") if err := cmd.Parse(args); err != nil { return nil, nil, cmd, err @@ -166,7 +168,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf mountLabel = mLabel } - lxcConf, err := parseLxcConfOpts(flLxcOpts) + lxcConf, err := parseKeyValueOpts(flLxcOpts) if err != nil { return nil, nil, cmd, err } @@ -226,6 +228,11 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf }, } + driverOptions, err := parseDriverOpts(flDriverOpts) + if err != nil { + return nil, nil, cmd, err + } + hostConfig := &HostConfig{ Binds: binds, ContainerIDFile: *flContainerIDFile, @@ -234,6 +241,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf PortBindings: portBindings, Links: flLinks.GetAll(), PublishAllPorts: *flPublishAll, + DriverOptions: driverOptions, } if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit { @@ -248,22 +256,31 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf return config, hostConfig, cmd, nil } -func parseLxcConfOpts(opts opts.ListOpts) ([]KeyValuePair, error) { - out := make([]KeyValuePair, opts.Len()) - for i, o := range opts.GetAll() { - k, v, err := parseLxcOpt(o) - if err != nil { - return nil, err +// options will come in the format of name.key=value or name.option +func parseDriverOpts(opts opts.ListOpts) (map[string][]string, error) { + out := make(map[string][]string, len(opts.GetAll())) + for _, o := range opts.GetAll() { + parts := strings.SplitN(o, ".", 2) + if len(parts) < 2 { + return nil, fmt.Errorf("invalid opt format %s", o) } - out[i] = KeyValuePair{Key: k, Value: v} + values, exists := out[parts[0]] + if !exists { + values = []string{} + } + out[parts[0]] = append(values, parts[1]) } return out, nil } -func parseLxcOpt(opt string) (string, string, error) { - parts := strings.SplitN(opt, "=", 2) - if len(parts) != 2 { - return "", "", fmt.Errorf("Unable to parse lxc conf option: %s", opt) +func parseKeyValueOpts(opts opts.ListOpts) ([]utils.KeyValuePair, error) { + out := make([]utils.KeyValuePair, opts.Len()) + for i, o := range opts.GetAll() { + k, v, err := utils.ParseKeyValueOpt(o) + if err != nil { + return nil, err + } + out[i] = utils.KeyValuePair{Key: k, Value: v} } - return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil + return out, nil } diff --git a/runconfig/parse_test.go b/runconfig/parse_test.go index 2b89e88ec3..fd28c4593e 100644 --- a/runconfig/parse_test.go +++ b/runconfig/parse_test.go @@ -1,6 +1,7 @@ package runconfig import ( + "github.com/dotcloud/docker/utils" "testing" ) @@ -8,7 +9,7 @@ func TestParseLxcConfOpt(t *testing.T) { opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "} for _, o := range opts { - k, v, err := parseLxcOpt(o) + k, v, err := utils.ParseKeyValueOpt(o) if err != nil { t.FailNow() } diff --git a/runtime/container.go b/runtime/container.go index 4cf307d823..656e9ae587 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -361,9 +361,13 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s func populateCommand(c *Container) { var ( en *execdriver.Network - driverConfig []string + driverConfig = c.hostConfig.DriverOptions ) + if driverConfig == nil { + driverConfig = make(map[string][]string) + } + en = &execdriver.Network{ Mtu: c.runtime.config.Mtu, Interface: nil, @@ -379,11 +383,9 @@ func populateCommand(c *Container) { } } - if lxcConf := c.hostConfig.LxcConf; lxcConf != nil { - for _, pair := range lxcConf { - driverConfig = append(driverConfig, fmt.Sprintf("%s = %s", pair.Key, pair.Value)) - } - } + // TODO: this can be removed after lxc-conf is fully deprecated + mergeLxcConfIntoOptions(c.hostConfig, driverConfig) + resources := &execdriver.Resources{ Memory: c.Config.Memory, MemorySwap: c.Config.MemorySwap, diff --git a/runtime/execdriver/driver.go b/runtime/execdriver/driver.go index dca889a82d..096ea0790d 100644 --- a/runtime/execdriver/driver.go +++ b/runtime/execdriver/driver.go @@ -116,21 +116,21 @@ type Mount struct { type Command struct { exec.Cmd `json:"-"` - ID string `json:"id"` - Privileged bool `json:"privileged"` - User string `json:"user"` - Rootfs string `json:"rootfs"` // root fs of the container - InitPath string `json:"initpath"` // dockerinit - Entrypoint string `json:"entrypoint"` - Arguments []string `json:"arguments"` - WorkingDir string `json:"working_dir"` - ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver - Context Context `json:"context"` // generic context for specific options (apparmor, selinux) - Tty bool `json:"tty"` - Network *Network `json:"network"` - Config []string `json:"config"` // generic values that specific drivers can consume - Resources *Resources `json:"resources"` - Mounts []Mount `json:"mounts"` + ID string `json:"id"` + Privileged bool `json:"privileged"` + User string `json:"user"` + Rootfs string `json:"rootfs"` // root fs of the container + InitPath string `json:"initpath"` // dockerinit + Entrypoint string `json:"entrypoint"` + Arguments []string `json:"arguments"` + WorkingDir string `json:"working_dir"` + ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver + Context Context `json:"context"` // generic context for specific options (apparmor, selinux) + Tty bool `json:"tty"` + Network *Network `json:"network"` + Config map[string][]string `json:"config"` // generic values that specific drivers can consume + Resources *Resources `json:"resources"` + Mounts []Mount `json:"mounts"` Terminal Terminal `json:"-"` // standard or tty terminal Console string `json:"-"` // dev/console path diff --git a/runtime/execdriver/lxc/lxc_template.go b/runtime/execdriver/lxc/lxc_template.go index 608fb22436..f325ffcaef 100644 --- a/runtime/execdriver/lxc/lxc_template.go +++ b/runtime/execdriver/lxc/lxc_template.go @@ -123,8 +123,8 @@ lxc.cgroup.cpu.shares = {{.Resources.CpuShares}} {{end}} {{end}} -{{if .Config}} -{{range $value := .Config}} +{{if .Config.lxc}} +{{range $value := .Config.lxc}} {{$value}} {{end}} {{end}} diff --git a/runtime/execdriver/lxc/lxc_template_unit_test.go b/runtime/execdriver/lxc/lxc_template_unit_test.go index e613adf7a9..7f473a0502 100644 --- a/runtime/execdriver/lxc/lxc_template_unit_test.go +++ b/runtime/execdriver/lxc/lxc_template_unit_test.go @@ -75,9 +75,11 @@ func TestCustomLxcConfig(t *testing.T) { command := &execdriver.Command{ ID: "1", Privileged: false, - Config: []string{ - "lxc.utsname = docker", - "lxc.cgroup.cpuset.cpus = 0,1", + Config: map[string][]string{ + "lxc": { + "lxc.utsname = docker", + "lxc.cgroup.cpuset.cpus = 0,1", + }, }, Network: &execdriver.Network{ Mtu: 1500, diff --git a/runtime/execdriver/native/default_template.go b/runtime/execdriver/native/default_template.go index 7e1e9ed86e..e11f2de1cf 100644 --- a/runtime/execdriver/native/default_template.go +++ b/runtime/execdriver/native/default_template.go @@ -58,6 +58,7 @@ func createContainer(c *execdriver.Command) *libcontainer.Container { container.Cgroups.Memory = c.Resources.Memory container.Cgroups.MemorySwap = c.Resources.MemorySwap } + // check to see if we are running in ramdisk to disable pivot root container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != "" diff --git a/runtime/execdriver/native/driver.go b/runtime/execdriver/native/driver.go index bf7e8ccdec..db974cbf04 100644 --- a/runtime/execdriver/native/driver.go +++ b/runtime/execdriver/native/driver.go @@ -184,10 +184,9 @@ func (d *driver) removeContainerRoot(id string) error { func (d *driver) validateCommand(c *execdriver.Command) error { // we need to check the Config of the command to make sure that we // do not have any of the lxc-conf variables - for _, conf := range c.Config { - if strings.Contains(conf, "lxc") { - return fmt.Errorf("%s is not supported by the native driver", conf) - } + lxc := c.Config["lxc"] + if lxc != nil && len(lxc) > 0 { + return fmt.Errorf("lxc config options are not supported by the native driver") } return nil } diff --git a/runtime/utils.go b/runtime/utils.go index b343b5b10e..b983e67d41 100644 --- a/runtime/utils.go +++ b/runtime/utils.go @@ -1,9 +1,11 @@ package runtime import ( + "fmt" "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/pkg/namesgenerator" "github.com/dotcloud/docker/runconfig" + "strings" ) func migratePortMappings(config *runconfig.Config, hostConfig *runconfig.HostConfig) error { @@ -30,6 +32,24 @@ func migratePortMappings(config *runconfig.Config, hostConfig *runconfig.HostCon return nil } +func mergeLxcConfIntoOptions(hostConfig *runconfig.HostConfig, driverConfig map[string][]string) { + if hostConfig == nil { + return + } + + // merge in the lxc conf options into the generic config map + if lxcConf := hostConfig.LxcConf; lxcConf != nil { + lxc := driverConfig["lxc"] + for _, pair := range lxcConf { + // 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 + parts := strings.SplitN(pair.Key, ".", 2) + lxc = append(lxc, fmt.Sprintf("%s=%s", parts[1], pair.Value)) + } + driverConfig["lxc"] = lxc + } +} + type checker struct { runtime *Runtime } diff --git a/utils/utils.go b/utils/utils.go index 2702555973..1fe2e87b4f 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -25,6 +25,11 @@ import ( "time" ) +type KeyValuePair struct { + Key string + Value string +} + // A common interface to access the Fatal method of // both testing.B and testing.T. type Fataler interface { @@ -1071,3 +1076,11 @@ func ReadSymlinkedDirectory(path string) (string, error) { } return realPath, nil } + +func ParseKeyValueOpt(opt string) (string, string, error) { + parts := strings.SplitN(opt, "=", 2) + if len(parts) != 2 { + return "", "", fmt.Errorf("Unable to parse key/value option: %s", opt) + } + return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil +}