Browse code

Add --opt arguments for drivers

In order to handle special configuration for different drivers we
make the Config field a map to string array. This lets
us use it for lxc, by using the "lxc" key for those, and we can
later extend it easily for other backend-specific options.

Docker-DCO-1.1-Signed-off-by: Alexander Larsson <alexl@redhat.com> (github: alexlarsson)

Alexander Larsson authored on 2014/03/14 01:03:09
Showing 11 changed files
... ...
@@ -3,21 +3,18 @@ package runconfig
3 3
 import (
4 4
 	"github.com/dotcloud/docker/engine"
5 5
 	"github.com/dotcloud/docker/nat"
6
+	"github.com/dotcloud/docker/utils"
6 7
 )
7 8
 
8 9
 type HostConfig struct {
9 10
 	Binds           []string
10 11
 	ContainerIDFile string
11
-	LxcConf         []KeyValuePair
12
+	LxcConf         []utils.KeyValuePair
12 13
 	Privileged      bool
13 14
 	PortBindings    nat.PortMap
14 15
 	Links           []string
15 16
 	PublishAllPorts bool
16
-}
17
-
18
-type KeyValuePair struct {
19
-	Key   string
20
-	Value string
17
+	DriverOptions   map[string][]string
21 18
 }
22 19
 
23 20
 func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
... ...
@@ -28,6 +25,7 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
28 28
 	}
29 29
 	job.GetenvJson("LxcConf", &hostConfig.LxcConf)
30 30
 	job.GetenvJson("PortBindings", &hostConfig.PortBindings)
31
+	job.GetenvJson("DriverOptions", &hostConfig.DriverOptions)
31 32
 	if Binds := job.GetenvList("Binds"); Binds != nil {
32 33
 		hostConfig.Binds = Binds
33 34
 	}
... ...
@@ -51,6 +51,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
51 51
 		flDnsSearch   = opts.NewListOpts(opts.ValidateDomain)
52 52
 		flVolumesFrom opts.ListOpts
53 53
 		flLxcOpts     opts.ListOpts
54
+		flDriverOpts  opts.ListOpts
54 55
 
55 56
 		flAutoRemove      = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)")
56 57
 		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
83 83
 	cmd.Var(&flDns, []string{"#dns", "-dns"}, "Set custom dns servers")
84 84
 	cmd.Var(&flDnsSearch, []string{"-dns-search"}, "Set custom dns search domains")
85 85
 	cmd.Var(&flVolumesFrom, []string{"#volumes-from", "-volumes-from"}, "Mount volumes from the specified container(s)")
86
-	cmd.Var(&flLxcOpts, []string{"#lxc-conf", "-lxc-conf"}, "(lxc exec-driver only) Add custom lxc options --lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
86
+	cmd.Var(&flLxcOpts, []string{"#lxc-conf", "#-lxc-conf"}, "(lxc exec-driver only) Add custom lxc options --lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
87
+	cmd.Var(&flDriverOpts, []string{"o", "-opt"}, "Add custom driver options")
87 88
 
88 89
 	if err := cmd.Parse(args); err != nil {
89 90
 		return nil, nil, cmd, err
... ...
@@ -166,7 +168,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
166 166
 		mountLabel = mLabel
167 167
 	}
168 168
 
169
-	lxcConf, err := parseLxcConfOpts(flLxcOpts)
169
+	lxcConf, err := parseKeyValueOpts(flLxcOpts)
170 170
 	if err != nil {
171 171
 		return nil, nil, cmd, err
172 172
 	}
... ...
@@ -226,6 +228,11 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
226 226
 		},
227 227
 	}
228 228
 
229
+	driverOptions, err := parseDriverOpts(flDriverOpts)
230
+	if err != nil {
231
+		return nil, nil, cmd, err
232
+	}
233
+
229 234
 	hostConfig := &HostConfig{
230 235
 		Binds:           binds,
231 236
 		ContainerIDFile: *flContainerIDFile,
... ...
@@ -234,6 +241,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
234 234
 		PortBindings:    portBindings,
235 235
 		Links:           flLinks.GetAll(),
236 236
 		PublishAllPorts: *flPublishAll,
237
+		DriverOptions:   driverOptions,
237 238
 	}
238 239
 
239 240
 	if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit {
... ...
@@ -248,22 +256,31 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
248 248
 	return config, hostConfig, cmd, nil
249 249
 }
250 250
 
251
-func parseLxcConfOpts(opts opts.ListOpts) ([]KeyValuePair, error) {
252
-	out := make([]KeyValuePair, opts.Len())
253
-	for i, o := range opts.GetAll() {
254
-		k, v, err := parseLxcOpt(o)
255
-		if err != nil {
256
-			return nil, err
251
+// options will come in the format of name.key=value or name.option
252
+func parseDriverOpts(opts opts.ListOpts) (map[string][]string, error) {
253
+	out := make(map[string][]string, len(opts.GetAll()))
254
+	for _, o := range opts.GetAll() {
255
+		parts := strings.SplitN(o, ".", 2)
256
+		if len(parts) < 2 {
257
+			return nil, fmt.Errorf("invalid opt format %s", o)
257 258
 		}
258
-		out[i] = KeyValuePair{Key: k, Value: v}
259
+		values, exists := out[parts[0]]
260
+		if !exists {
261
+			values = []string{}
262
+		}
263
+		out[parts[0]] = append(values, parts[1])
259 264
 	}
260 265
 	return out, nil
261 266
 }
262 267
 
263
-func parseLxcOpt(opt string) (string, string, error) {
264
-	parts := strings.SplitN(opt, "=", 2)
265
-	if len(parts) != 2 {
266
-		return "", "", fmt.Errorf("Unable to parse lxc conf option: %s", opt)
268
+func parseKeyValueOpts(opts opts.ListOpts) ([]utils.KeyValuePair, error) {
269
+	out := make([]utils.KeyValuePair, opts.Len())
270
+	for i, o := range opts.GetAll() {
271
+		k, v, err := utils.ParseKeyValueOpt(o)
272
+		if err != nil {
273
+			return nil, err
274
+		}
275
+		out[i] = utils.KeyValuePair{Key: k, Value: v}
267 276
 	}
268
-	return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
277
+	return out, nil
269 278
 }
... ...
@@ -1,6 +1,7 @@
1 1
 package runconfig
2 2
 
3 3
 import (
4
+	"github.com/dotcloud/docker/utils"
4 5
 	"testing"
5 6
 )
6 7
 
... ...
@@ -8,7 +9,7 @@ func TestParseLxcConfOpt(t *testing.T) {
8 8
 	opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "}
9 9
 
10 10
 	for _, o := range opts {
11
-		k, v, err := parseLxcOpt(o)
11
+		k, v, err := utils.ParseKeyValueOpt(o)
12 12
 		if err != nil {
13 13
 			t.FailNow()
14 14
 		}
... ...
@@ -361,9 +361,13 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
361 361
 func populateCommand(c *Container) {
362 362
 	var (
363 363
 		en           *execdriver.Network
364
-		driverConfig []string
364
+		driverConfig = c.hostConfig.DriverOptions
365 365
 	)
366 366
 
367
+	if driverConfig == nil {
368
+		driverConfig = make(map[string][]string)
369
+	}
370
+
367 371
 	en = &execdriver.Network{
368 372
 		Mtu:       c.runtime.config.Mtu,
369 373
 		Interface: nil,
... ...
@@ -379,11 +383,9 @@ func populateCommand(c *Container) {
379 379
 		}
380 380
 	}
381 381
 
382
-	if lxcConf := c.hostConfig.LxcConf; lxcConf != nil {
383
-		for _, pair := range lxcConf {
384
-			driverConfig = append(driverConfig, fmt.Sprintf("%s = %s", pair.Key, pair.Value))
385
-		}
386
-	}
382
+	// TODO: this can be removed after lxc-conf is fully deprecated
383
+	mergeLxcConfIntoOptions(c.hostConfig, driverConfig)
384
+
387 385
 	resources := &execdriver.Resources{
388 386
 		Memory:     c.Config.Memory,
389 387
 		MemorySwap: c.Config.MemorySwap,
... ...
@@ -116,21 +116,21 @@ type Mount struct {
116 116
 type Command struct {
117 117
 	exec.Cmd `json:"-"`
118 118
 
119
-	ID         string     `json:"id"`
120
-	Privileged bool       `json:"privileged"`
121
-	User       string     `json:"user"`
122
-	Rootfs     string     `json:"rootfs"`   // root fs of the container
123
-	InitPath   string     `json:"initpath"` // dockerinit
124
-	Entrypoint string     `json:"entrypoint"`
125
-	Arguments  []string   `json:"arguments"`
126
-	WorkingDir string     `json:"working_dir"`
127
-	ConfigPath string     `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver
128
-	Context    Context    `json:"context"`     // generic context for specific options (apparmor, selinux)
129
-	Tty        bool       `json:"tty"`
130
-	Network    *Network   `json:"network"`
131
-	Config     []string   `json:"config"` //  generic values that specific drivers can consume
132
-	Resources  *Resources `json:"resources"`
133
-	Mounts     []Mount    `json:"mounts"`
119
+	ID         string              `json:"id"`
120
+	Privileged bool                `json:"privileged"`
121
+	User       string              `json:"user"`
122
+	Rootfs     string              `json:"rootfs"`   // root fs of the container
123
+	InitPath   string              `json:"initpath"` // dockerinit
124
+	Entrypoint string              `json:"entrypoint"`
125
+	Arguments  []string            `json:"arguments"`
126
+	WorkingDir string              `json:"working_dir"`
127
+	ConfigPath string              `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver
128
+	Context    Context             `json:"context"`     // generic context for specific options (apparmor, selinux)
129
+	Tty        bool                `json:"tty"`
130
+	Network    *Network            `json:"network"`
131
+	Config     map[string][]string `json:"config"` //  generic values that specific drivers can consume
132
+	Resources  *Resources          `json:"resources"`
133
+	Mounts     []Mount             `json:"mounts"`
134 134
 
135 135
 	Terminal     Terminal `json:"-"`             // standard or tty terminal
136 136
 	Console      string   `json:"-"`             // dev/console path
... ...
@@ -123,8 +123,8 @@ lxc.cgroup.cpu.shares = {{.Resources.CpuShares}}
123 123
 {{end}}
124 124
 {{end}}
125 125
 
126
-{{if .Config}}
127
-{{range $value := .Config}}
126
+{{if .Config.lxc}}
127
+{{range $value := .Config.lxc}}
128 128
 {{$value}}
129 129
 {{end}}
130 130
 {{end}}
... ...
@@ -75,9 +75,11 @@ func TestCustomLxcConfig(t *testing.T) {
75 75
 	command := &execdriver.Command{
76 76
 		ID:         "1",
77 77
 		Privileged: false,
78
-		Config: []string{
79
-			"lxc.utsname = docker",
80
-			"lxc.cgroup.cpuset.cpus = 0,1",
78
+		Config: map[string][]string{
79
+			"lxc": {
80
+				"lxc.utsname = docker",
81
+				"lxc.cgroup.cpuset.cpus = 0,1",
82
+			},
81 83
 		},
82 84
 		Network: &execdriver.Network{
83 85
 			Mtu:       1500,
... ...
@@ -58,6 +58,7 @@ func createContainer(c *execdriver.Command) *libcontainer.Container {
58 58
 		container.Cgroups.Memory = c.Resources.Memory
59 59
 		container.Cgroups.MemorySwap = c.Resources.MemorySwap
60 60
 	}
61
+
61 62
 	// check to see if we are running in ramdisk to disable pivot root
62 63
 	container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != ""
63 64
 
... ...
@@ -184,10 +184,9 @@ func (d *driver) removeContainerRoot(id string) error {
184 184
 func (d *driver) validateCommand(c *execdriver.Command) error {
185 185
 	// we need to check the Config of the command to make sure that we
186 186
 	// do not have any of the lxc-conf variables
187
-	for _, conf := range c.Config {
188
-		if strings.Contains(conf, "lxc") {
189
-			return fmt.Errorf("%s is not supported by the native driver", conf)
190
-		}
187
+	lxc := c.Config["lxc"]
188
+	if lxc != nil && len(lxc) > 0 {
189
+		return fmt.Errorf("lxc config options are not supported by the native driver")
191 190
 	}
192 191
 	return nil
193 192
 }
... ...
@@ -1,9 +1,11 @@
1 1
 package runtime
2 2
 
3 3
 import (
4
+	"fmt"
4 5
 	"github.com/dotcloud/docker/nat"
5 6
 	"github.com/dotcloud/docker/pkg/namesgenerator"
6 7
 	"github.com/dotcloud/docker/runconfig"
8
+	"strings"
7 9
 )
8 10
 
9 11
 func migratePortMappings(config *runconfig.Config, hostConfig *runconfig.HostConfig) error {
... ...
@@ -30,6 +32,24 @@ func migratePortMappings(config *runconfig.Config, hostConfig *runconfig.HostCon
30 30
 	return nil
31 31
 }
32 32
 
33
+func mergeLxcConfIntoOptions(hostConfig *runconfig.HostConfig, driverConfig map[string][]string) {
34
+	if hostConfig == nil {
35
+		return
36
+	}
37
+
38
+	// merge in the lxc conf options into the generic config map
39
+	if lxcConf := hostConfig.LxcConf; lxcConf != nil {
40
+		lxc := driverConfig["lxc"]
41
+		for _, pair := range lxcConf {
42
+			// because lxc conf gets the driver name lxc.XXXX we need to trim it off
43
+			// and let the lxc driver add it back later if needed
44
+			parts := strings.SplitN(pair.Key, ".", 2)
45
+			lxc = append(lxc, fmt.Sprintf("%s=%s", parts[1], pair.Value))
46
+		}
47
+		driverConfig["lxc"] = lxc
48
+	}
49
+}
50
+
33 51
 type checker struct {
34 52
 	runtime *Runtime
35 53
 }
... ...
@@ -25,6 +25,11 @@ import (
25 25
 	"time"
26 26
 )
27 27
 
28
+type KeyValuePair struct {
29
+	Key   string
30
+	Value string
31
+}
32
+
28 33
 // A common interface to access the Fatal method of
29 34
 // both testing.B and testing.T.
30 35
 type Fataler interface {
... ...
@@ -1071,3 +1076,11 @@ func ReadSymlinkedDirectory(path string) (string, error) {
1071 1071
 	}
1072 1072
 	return realPath, nil
1073 1073
 }
1074
+
1075
+func ParseKeyValueOpt(opt string) (string, string, error) {
1076
+	parts := strings.SplitN(opt, "=", 2)
1077
+	if len(parts) != 2 {
1078
+		return "", "", fmt.Errorf("Unable to parse key/value option: %s", opt)
1079
+	}
1080
+	return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
1081
+}