Browse code

support env for docker plugin set

Signed-off-by: Victor Vieux <vieux@docker.com>

Victor Vieux authored on 2016/11/01 09:07:05
Showing 15 changed files
... ...
@@ -89,7 +89,11 @@ func (pr *pluginRouter) setPlugin(ctx context.Context, w http.ResponseWriter, r
89 89
 	if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
90 90
 		return err
91 91
 	}
92
-	return pr.backend.Set(vars["name"], args)
92
+	if err := pr.backend.Set(vars["name"], args); err != nil {
93
+		return err
94
+	}
95
+	w.WriteHeader(http.StatusNoContent)
96
+	return nil
93 97
 }
94 98
 
95 99
 func (pr *pluginRouter) listPlugins(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
... ...
@@ -4290,16 +4290,26 @@ Content-Type: application/json
4290 4290
 -   **200** - no error
4291 4291
 -   **404** - plugin not installed
4292 4292
 
4293
-<!-- TODO Document "docker plugin set" endpoint once implemented
4294 4293
 ### Configure a plugin
4295 4294
 
4296
-`POST /plugins/(plugin name)/set`
4295
+POST /plugins/(plugin name)/set`
4297 4296
 
4298
-**Status codes**:
4297
+**Example request**:
4299 4298
 
4300
--   **500** - not implemented
4301 4299
 
4300
+    POST /plugins/tiborvass/no-remove/set
4301
+    Content-Type: application/json
4302
+
4303
+    ["DEBUG=1"]
4304
+
4305
+**Example response**:
4306
+
4307
+    HTTP/1.1 204 No Content
4308
+
4309
+**Status codes**:
4310
+
4311
+-   **204** - no error
4312
+-   **404** - plugin not installed
4302 4313
 
4303 4314
 ### Enable a plugin
4304 4315
 
... ...
@@ -59,3 +59,4 @@ tiborvass/no-remove   latest              A test plugin for Docker   false
59 59
 * [plugin inspect](plugin_inspect.md)
60 60
 * [plugin install](plugin_install.md)
61 61
 * [plugin rm](plugin_rm.md)
62
+* [plugin set](plugin_set.md)
... ...
@@ -59,3 +59,4 @@ tiborvass/no-remove   latest              A test plugin for Docker   true
59 59
 * [plugin inspect](plugin_inspect.md)
60 60
 * [plugin install](plugin_install.md)
61 61
 * [plugin rm](plugin_rm.md)
62
+* [plugin set](plugin_set.md)
... ...
@@ -159,3 +159,4 @@ $ docker plugin inspect -f '{{.Id}}' tiborvass/no-remove:latest
159 159
 * [plugin disable](plugin_disable.md)
160 160
 * [plugin install](plugin_install.md)
161 161
 * [plugin rm](plugin_rm.md)
162
+* [plugin set](plugin_set.md)
... ...
@@ -64,3 +64,4 @@ tiborvass/no-remove   latest              A test plugin for Docker   true
64 64
 * [plugin disable](plugin_disable.md)
65 65
 * [plugin inspect](plugin_inspect.md)
66 66
 * [plugin rm](plugin_rm.md)
67
+* [plugin set](plugin_set.md)
... ...
@@ -48,3 +48,4 @@ tiborvass/no-remove   latest              A test plugin for Docker   true
48 48
 * [plugin inspect](plugin_inspect.md)
49 49
 * [plugin install](plugin_install.md)
50 50
 * [plugin rm](plugin_rm.md)
51
+* [plugin set](plugin_set.md)
... ...
@@ -51,3 +51,4 @@ tiborvass/no-remove
51 51
 * [plugin disable](plugin_disable.md)
52 52
 * [plugin inspect](plugin_inspect.md)
53 53
 * [plugin install](plugin_install.md)
54
+* [plugin set](plugin_set.md)
54 55
new file mode 100644
... ...
@@ -0,0 +1,51 @@
0
+---
1
+title: "plugin set"
2
+description: "the plugin set command description and usage"
3
+keywords: "plugin, set"
4
+advisory: "experimental"
5
+---
6
+
7
+<!-- This file is maintained within the docker/docker Github
8
+     repository at https://github.com/docker/docker/. Make all
9
+     pull requests against that repo. If you see this file in
10
+     another repository, consider it read-only there, as it will
11
+     periodically be overwritten by the definitive file. Pull
12
+     requests which include edits to this file in other repositories
13
+     will be rejected.
14
+-->
15
+
16
+# plugin set (experimental)
17
+
18
+```markdown
19
+Usage:  docker plugin set PLUGIN key1=value1 [key2=value2...]
20
+
21
+Change settings for a plugin
22
+
23
+Options:
24
+      --help                    Print usage
25
+```
26
+
27
+Change settings for a plugin. The plugin must be disabled.
28
+
29
+
30
+The following example installs change the env variable `DEBUG` of the
31
+`no-remove` plugin.
32
+
33
+```bash
34
+$ docker plugin inspect -f {{.Config.Env}} tiborvass/no-remove
35
+[DEBUG=0]
36
+
37
+$ docker plugin set DEBUG=1 tiborvass/no-remove
38
+
39
+$ docker plugin inspect -f {{.Config.Env}} tiborvass/no-remove
40
+[DEBUG=1]
41
+```
42
+
43
+## Related information
44
+
45
+* [plugin ls](plugin_ls.md)
46
+* [plugin enable](plugin_enable.md)
47
+* [plugin disable](plugin_disable.md)
48
+* [plugin inspect](plugin_inspect.md)
49
+* [plugin install](plugin_install.md)
50
+* [plugin rm](plugin_rm.md)
... ...
@@ -117,6 +117,20 @@ func (s *DockerSuite) TestPluginInstallDisableVolumeLs(c *check.C) {
117 117
 	dockerCmd(c, "volume", "ls")
118 118
 }
119 119
 
120
+func (s *DockerSuite) TestPluginSet(c *check.C) {
121
+	testRequires(c, DaemonIsLinux, ExperimentalDaemon, Network)
122
+	out, _ := dockerCmd(c, "plugin", "install", "--grant-all-permissions", "--disable", pName)
123
+	c.Assert(strings.TrimSpace(out), checker.Contains, pName)
124
+
125
+	env, _ := dockerCmd(c, "plugin", "inspect", "-f", "{{.Config.Env}}", pName)
126
+	c.Assert(strings.TrimSpace(env), checker.Equals, "[DEBUG=0]")
127
+
128
+	dockerCmd(c, "plugin", "set", pName, "DEBUG=1")
129
+
130
+	env, _ = dockerCmd(c, "plugin", "inspect", "-f", "{{.Config.Env}}", pName)
131
+	c.Assert(strings.TrimSpace(env), checker.Equals, "[DEBUG=1]")
132
+}
133
+
120 134
 func (s *DockerSuite) TestPluginInstallImage(c *check.C) {
121 135
 	testRequires(c, DaemonIsLinux, ExperimentalDaemon)
122 136
 	out, _, err := dockerCmdWithError("plugin", "install", "redis")
... ...
@@ -85,8 +85,8 @@ func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.A
85 85
 	}
86 86
 
87 87
 	tag := distribution.GetTag(ref)
88
-	p := v2.NewPlugin(ref.Name(), pluginID, pm.runRoot, tag)
89
-	if err := p.InitPlugin(pm.libRoot); err != nil {
88
+	p := v2.NewPlugin(ref.Name(), pluginID, pm.runRoot, pm.libRoot, tag)
89
+	if err := p.InitPlugin(); err != nil {
90 90
 		return nil, err
91 91
 	}
92 92
 	pm.pluginStore.Add(p)
... ...
@@ -8,7 +8,7 @@ import (
8 8
 )
9 9
 
10 10
 func TestFilterByCapNeg(t *testing.T) {
11
-	p := v2.NewPlugin("test", "1234567890", "/run/docker", "latest")
11
+	p := v2.NewPlugin("test", "1234567890", "/run/docker", "/var/lib/docker/plugins", "latest")
12 12
 
13 13
 	iType := types.PluginInterfaceType{"volumedriver", "docker", "1.0"}
14 14
 	i := types.PluginManifestInterface{"plugins.sock", []types.PluginInterfaceType{iType}}
... ...
@@ -21,7 +21,7 @@ func TestFilterByCapNeg(t *testing.T) {
21 21
 }
22 22
 
23 23
 func TestFilterByCapPos(t *testing.T) {
24
-	p := v2.NewPlugin("test", "1234567890", "/run/docker", "latest")
24
+	p := v2.NewPlugin("test", "1234567890", "/run/docker", "/var/lib/docker/plugins", "latest")
25 25
 
26 26
 	iType := types.PluginInterfaceType{"volumedriver", "docker", "1.0"}
27 27
 	i := types.PluginManifestInterface{"plugins.sock", []types.PluginInterfaceType{iType}}
... ...
@@ -2,7 +2,6 @@ package v2
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
-	"errors"
6 5
 	"fmt"
7 6
 	"os"
8 7
 	"path/filepath"
... ...
@@ -24,6 +23,7 @@ type Plugin struct {
24 24
 	RefCount          int             `json:"-"`
25 25
 	Restart           bool            `json:"-"`
26 26
 	ExitChan          chan bool       `json:"-"`
27
+	LibRoot           string          `json:"-"`
27 28
 }
28 29
 
29 30
 const defaultPluginRuntimeDestination = "/run/docker/plugins"
... ...
@@ -42,10 +42,11 @@ func newPluginObj(name, id, tag string) types.Plugin {
42 42
 }
43 43
 
44 44
 // NewPlugin creates a plugin.
45
-func NewPlugin(name, id, runRoot, tag string) *Plugin {
45
+func NewPlugin(name, id, runRoot, libRoot, tag string) *Plugin {
46 46
 	return &Plugin{
47 47
 		PluginObj:         newPluginObj(name, id, tag),
48 48
 		RuntimeSourcePath: filepath.Join(runRoot, id),
49
+		LibRoot:           libRoot,
49 50
 	}
50 51
 }
51 52
 
... ...
@@ -86,8 +87,8 @@ func (p *Plugin) RemoveFromDisk() error {
86 86
 }
87 87
 
88 88
 // InitPlugin populates the plugin object from the plugin manifest file.
89
-func (p *Plugin) InitPlugin(libRoot string) error {
90
-	dt, err := os.Open(filepath.Join(libRoot, p.PluginObj.ID, "manifest.json"))
89
+func (p *Plugin) InitPlugin() error {
90
+	dt, err := os.Open(filepath.Join(p.LibRoot, p.PluginObj.ID, "manifest.json"))
91 91
 	if err != nil {
92 92
 		return err
93 93
 	}
... ...
@@ -109,7 +110,11 @@ func (p *Plugin) InitPlugin(libRoot string) error {
109 109
 	}
110 110
 	copy(p.PluginObj.Config.Args, p.PluginObj.Manifest.Args.Value)
111 111
 
112
-	f, err := os.Create(filepath.Join(libRoot, p.PluginObj.ID, "plugin-config.json"))
112
+	return p.writeConfig()
113
+}
114
+
115
+func (p *Plugin) writeConfig() error {
116
+	f, err := os.Create(filepath.Join(p.LibRoot, p.PluginObj.ID, "plugin-config.json"))
113 117
 	if err != nil {
114 118
 		return err
115 119
 	}
... ...
@@ -120,15 +125,43 @@ func (p *Plugin) InitPlugin(libRoot string) error {
120 120
 
121 121
 // Set is used to pass arguments to the plugin.
122 122
 func (p *Plugin) Set(args []string) error {
123
-	m := make(map[string]string, len(args))
124
-	for _, arg := range args {
125
-		i := strings.Index(arg, "=")
126
-		if i < 0 {
127
-			return fmt.Errorf("No equal sign '=' found in %s", arg)
123
+	p.Lock()
124
+	defer p.Unlock()
125
+
126
+	if p.PluginObj.Enabled {
127
+		return fmt.Errorf("cannot set on an active plugin, disable plugin before setting")
128
+	}
129
+
130
+	sets, err := newSettables(args)
131
+	if err != nil {
132
+		return err
133
+	}
134
+
135
+next:
136
+	for _, s := range sets {
137
+		// range over all the envs in the manifest
138
+		for _, env := range p.PluginObj.Manifest.Env {
139
+			// found the env in the manifest
140
+			if env.Name == s.name {
141
+				// is it settable ?
142
+				if ok, err := s.isSettable(allowedSettableFieldsEnv, env.Settable); err != nil {
143
+					return err
144
+				} else if !ok {
145
+					return fmt.Errorf("%q is not settable", s.prettyName())
146
+				}
147
+				// is it, so lets update the config in memory
148
+				updateConfigEnv(&p.PluginObj.Config.Env, &s)
149
+				continue next
150
+			}
128 151
 		}
129
-		m[arg[:i]] = arg[i+1:]
152
+
153
+		//TODO: check devices, mount and args
154
+
155
+		return fmt.Errorf("setting %q not found in the plugin configuration", s.name)
130 156
 	}
131
-	return errors.New("not implemented")
157
+
158
+	// update the config on disk
159
+	return p.writeConfig()
132 160
 }
133 161
 
134 162
 // ComputePrivileges takes the manifest file and computes the list of access necessary
135 163
new file mode 100644
... ...
@@ -0,0 +1,102 @@
0
+package v2
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"strings"
6
+)
7
+
8
+type settable struct {
9
+	name  string
10
+	field string
11
+	value string
12
+}
13
+
14
+var (
15
+	allowedSettableFieldsEnv     = []string{"value"}
16
+	allowedSettableFieldsArgs    = []string{"value"}
17
+	allowedSettableFieldsDevices = []string{"path"}
18
+	allowedSettableFieldsMounts  = []string{"source"}
19
+
20
+	errMultipleFields = errors.New("multiple fields are settable, one must be specified")
21
+	errInvalidFormat  = errors.New("invalid format, must be <name>[.<field>][=<value>]")
22
+)
23
+
24
+func newSettables(args []string) ([]settable, error) {
25
+	sets := make([]settable, 0, len(args))
26
+	for _, arg := range args {
27
+		set, err := newSettable(arg)
28
+		if err != nil {
29
+			return nil, err
30
+		}
31
+		sets = append(sets, set)
32
+	}
33
+	return sets, nil
34
+}
35
+
36
+func newSettable(arg string) (settable, error) {
37
+	var set settable
38
+	if i := strings.Index(arg, "="); i == 0 {
39
+		return set, errInvalidFormat
40
+	} else if i < 0 {
41
+		set.name = arg
42
+	} else {
43
+		set.name = arg[:i]
44
+		set.value = arg[i+1:]
45
+	}
46
+
47
+	if i := strings.LastIndex(set.name, "."); i > 0 {
48
+		set.field = set.name[i+1:]
49
+		set.name = arg[:i]
50
+	}
51
+
52
+	return set, nil
53
+}
54
+
55
+// prettyName return name.field if there is a field, otherwise name.
56
+func (set *settable) prettyName() string {
57
+	if set.field != "" {
58
+		return fmt.Sprintf("%s.%s", set.name, set.field)
59
+	}
60
+	return set.name
61
+}
62
+
63
+func (set *settable) isSettable(allowedSettableFields []string, settable []string) (bool, error) {
64
+	if set.field == "" {
65
+		if len(settable) == 1 {
66
+			// if field is not specified and there only one settable, default to it.
67
+			set.field = settable[0]
68
+		} else if len(settable) > 1 {
69
+			return false, errMultipleFields
70
+		}
71
+	}
72
+
73
+	isAllowed := false
74
+	for _, allowedSettableField := range allowedSettableFields {
75
+		if set.field == allowedSettableField {
76
+			isAllowed = true
77
+			break
78
+		}
79
+	}
80
+
81
+	if isAllowed {
82
+		for _, settableField := range settable {
83
+			if set.field == settableField {
84
+				return true, nil
85
+			}
86
+		}
87
+	}
88
+
89
+	return false, nil
90
+}
91
+
92
+func updateConfigEnv(env *[]string, set *settable) {
93
+	for i, e := range *env {
94
+		if parts := strings.SplitN(e, "=", 2); parts[0] == set.name {
95
+			(*env)[i] = fmt.Sprintf("%s=%s", set.name, set.value)
96
+			return
97
+		}
98
+	}
99
+
100
+	*env = append(*env, fmt.Sprintf("%s=%s", set.name, set.value))
101
+}
0 102
new file mode 100644
... ...
@@ -0,0 +1,91 @@
0
+package v2
1
+
2
+import (
3
+	"reflect"
4
+	"testing"
5
+)
6
+
7
+func TestNewSettable(t *testing.T) {
8
+	contexts := []struct {
9
+		arg   string
10
+		name  string
11
+		field string
12
+		value string
13
+		err   error
14
+	}{
15
+		{"name=value", "name", "", "value", nil},
16
+		{"name", "name", "", "", nil},
17
+		{"name.field=value", "name", "field", "value", nil},
18
+		{"name.field", "name", "field", "", nil},
19
+		{"=value", "", "", "", errInvalidFormat},
20
+		{"=", "", "", "", errInvalidFormat},
21
+	}
22
+
23
+	for _, c := range contexts {
24
+		s, err := newSettable(c.arg)
25
+		if err != c.err {
26
+			t.Fatalf("expected error to be %v, got %v", c.err, err)
27
+		}
28
+
29
+		if s.name != c.name {
30
+			t.Fatalf("expected name to be %q, got %q", c.name, s.name)
31
+		}
32
+
33
+		if s.field != c.field {
34
+			t.Fatalf("expected field to be %q, got %q", c.field, s.field)
35
+		}
36
+
37
+		if s.value != c.value {
38
+			t.Fatalf("expected value to be %q, got %q", c.value, s.value)
39
+		}
40
+
41
+	}
42
+}
43
+
44
+func TestIsSettable(t *testing.T) {
45
+	contexts := []struct {
46
+		allowedSettableFields []string
47
+		set                   settable
48
+		settable              []string
49
+		result                bool
50
+		err                   error
51
+	}{
52
+		{allowedSettableFieldsEnv, settable{}, []string{}, false, nil},
53
+		{allowedSettableFieldsEnv, settable{field: "value"}, []string{}, false, nil},
54
+		{allowedSettableFieldsEnv, settable{}, []string{"value"}, true, nil},
55
+		{allowedSettableFieldsEnv, settable{field: "value"}, []string{"value"}, true, nil},
56
+		{allowedSettableFieldsEnv, settable{field: "foo"}, []string{"value"}, false, nil},
57
+		{allowedSettableFieldsEnv, settable{field: "foo"}, []string{"foo"}, false, nil},
58
+		{allowedSettableFieldsEnv, settable{}, []string{"value1", "value2"}, false, errMultipleFields},
59
+	}
60
+
61
+	for _, c := range contexts {
62
+		if res, err := c.set.isSettable(c.allowedSettableFields, c.settable); res != c.result {
63
+			t.Fatalf("expected result to be %t, got %t", c.result, res)
64
+		} else if err != c.err {
65
+			t.Fatalf("expected error to be %v, got %v", c.err, err)
66
+		}
67
+	}
68
+}
69
+
70
+func TestUpdateConfigEnv(t *testing.T) {
71
+	contexts := []struct {
72
+		env    []string
73
+		set    settable
74
+		newEnv []string
75
+	}{
76
+		{[]string{}, settable{name: "DEBUG", value: "1"}, []string{"DEBUG=1"}},
77
+		{[]string{"DEBUG=0"}, settable{name: "DEBUG", value: "1"}, []string{"DEBUG=1"}},
78
+		{[]string{"FOO=0"}, settable{name: "DEBUG", value: "1"}, []string{"FOO=0", "DEBUG=1"}},
79
+		{[]string{"FOO=0", "DEBUG=0"}, settable{name: "DEBUG", value: "1"}, []string{"FOO=0", "DEBUG=1"}},
80
+		{[]string{"FOO=0", "DEBUG=0", "BAR=1"}, settable{name: "DEBUG", value: "1"}, []string{"FOO=0", "DEBUG=1", "BAR=1"}},
81
+	}
82
+
83
+	for _, c := range contexts {
84
+		updateConfigEnv(&c.env, &c.set)
85
+
86
+		if !reflect.DeepEqual(c.env, c.newEnv) {
87
+			t.Fatalf("expected env to be %q, got %q", c.newEnv, c.env)
88
+		}
89
+	}
90
+}