Merge pull request #27923 from vieux/plugin_set_gogogo

support env for docker plugin set
This commit is contained in:
Victor Vieux 2016-11-07 17:07:14 -08:00 committed by GitHub
commit 03da822ee9
15 changed files with 334 additions and 23 deletions

View file

@ -89,7 +89,11 @@ func (pr *pluginRouter) setPlugin(ctx context.Context, w http.ResponseWriter, r
if err := json.NewDecoder(r.Body).Decode(&args); err != nil {
return err
}
return pr.backend.Set(vars["name"], args)
if err := pr.backend.Set(vars["name"], args); err != nil {
return err
}
w.WriteHeader(http.StatusNoContent)
return nil
}
func (pr *pluginRouter) listPlugins(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {

View file

@ -4290,16 +4290,26 @@ Content-Type: application/json
- **200** - no error
- **404** - plugin not installed
<!-- TODO Document "docker plugin set" endpoint once implemented
### Configure a plugin
`POST /plugins/(plugin name)/set`
POST /plugins/(plugin name)/set`
**Example request**:
POST /plugins/tiborvass/no-remove/set
Content-Type: application/json
["DEBUG=1"]
**Example response**:
HTTP/1.1 204 No Content
**Status codes**:
- **500** - not implemented
-->
- **204** - no error
- **404** - plugin not installed
### Enable a plugin

View file

@ -59,3 +59,4 @@ tiborvass/no-remove latest A test plugin for Docker false
* [plugin inspect](plugin_inspect.md)
* [plugin install](plugin_install.md)
* [plugin rm](plugin_rm.md)
* [plugin set](plugin_set.md)

View file

@ -59,3 +59,4 @@ tiborvass/no-remove latest A test plugin for Docker true
* [plugin inspect](plugin_inspect.md)
* [plugin install](plugin_install.md)
* [plugin rm](plugin_rm.md)
* [plugin set](plugin_set.md)

View file

@ -159,3 +159,4 @@ $ docker plugin inspect -f '{{.Id}}' tiborvass/no-remove:latest
* [plugin disable](plugin_disable.md)
* [plugin install](plugin_install.md)
* [plugin rm](plugin_rm.md)
* [plugin set](plugin_set.md)

View file

@ -64,3 +64,4 @@ tiborvass/no-remove latest A test plugin for Docker true
* [plugin disable](plugin_disable.md)
* [plugin inspect](plugin_inspect.md)
* [plugin rm](plugin_rm.md)
* [plugin set](plugin_set.md)

View file

@ -48,3 +48,4 @@ tiborvass/no-remove latest A test plugin for Docker true
* [plugin inspect](plugin_inspect.md)
* [plugin install](plugin_install.md)
* [plugin rm](plugin_rm.md)
* [plugin set](plugin_set.md)

View file

@ -51,3 +51,4 @@ tiborvass/no-remove
* [plugin disable](plugin_disable.md)
* [plugin inspect](plugin_inspect.md)
* [plugin install](plugin_install.md)
* [plugin set](plugin_set.md)

View file

@ -0,0 +1,51 @@
---
title: "plugin set"
description: "the plugin set command description and usage"
keywords: "plugin, set"
advisory: "experimental"
---
<!-- This file is maintained within the docker/docker Github
repository at https://github.com/docker/docker/. Make all
pull requests against that repo. If you see this file in
another repository, consider it read-only there, as it will
periodically be overwritten by the definitive file. Pull
requests which include edits to this file in other repositories
will be rejected.
-->
# plugin set (experimental)
```markdown
Usage: docker plugin set PLUGIN key1=value1 [key2=value2...]
Change settings for a plugin
Options:
--help Print usage
```
Change settings for a plugin. The plugin must be disabled.
The following example installs change the env variable `DEBUG` of the
`no-remove` plugin.
```bash
$ docker plugin inspect -f {{.Config.Env}} tiborvass/no-remove
[DEBUG=0]
$ docker plugin set DEBUG=1 tiborvass/no-remove
$ docker plugin inspect -f {{.Config.Env}} tiborvass/no-remove
[DEBUG=1]
```
## Related information
* [plugin ls](plugin_ls.md)
* [plugin enable](plugin_enable.md)
* [plugin disable](plugin_disable.md)
* [plugin inspect](plugin_inspect.md)
* [plugin install](plugin_install.md)
* [plugin rm](plugin_rm.md)

View file

@ -117,6 +117,20 @@ func (s *DockerSuite) TestPluginInstallDisableVolumeLs(c *check.C) {
dockerCmd(c, "volume", "ls")
}
func (s *DockerSuite) TestPluginSet(c *check.C) {
testRequires(c, DaemonIsLinux, ExperimentalDaemon, Network)
out, _ := dockerCmd(c, "plugin", "install", "--grant-all-permissions", "--disable", pName)
c.Assert(strings.TrimSpace(out), checker.Contains, pName)
env, _ := dockerCmd(c, "plugin", "inspect", "-f", "{{.Config.Env}}", pName)
c.Assert(strings.TrimSpace(env), checker.Equals, "[DEBUG=0]")
dockerCmd(c, "plugin", "set", pName, "DEBUG=1")
env, _ = dockerCmd(c, "plugin", "inspect", "-f", "{{.Config.Env}}", pName)
c.Assert(strings.TrimSpace(env), checker.Equals, "[DEBUG=1]")
}
func (s *DockerSuite) TestPluginInstallImage(c *check.C) {
testRequires(c, DaemonIsLinux, ExperimentalDaemon)
out, _, err := dockerCmdWithError("plugin", "install", "redis")

View file

@ -85,8 +85,8 @@ func (pm *Manager) Pull(name string, metaHeader http.Header, authConfig *types.A
}
tag := distribution.GetTag(ref)
p := v2.NewPlugin(ref.Name(), pluginID, pm.runRoot, tag)
if err := p.InitPlugin(pm.libRoot); err != nil {
p := v2.NewPlugin(ref.Name(), pluginID, pm.runRoot, pm.libRoot, tag)
if err := p.InitPlugin(); err != nil {
return nil, err
}
pm.pluginStore.Add(p)

View file

@ -8,7 +8,7 @@ import (
)
func TestFilterByCapNeg(t *testing.T) {
p := v2.NewPlugin("test", "1234567890", "/run/docker", "latest")
p := v2.NewPlugin("test", "1234567890", "/run/docker", "/var/lib/docker/plugins", "latest")
iType := types.PluginInterfaceType{"volumedriver", "docker", "1.0"}
i := types.PluginManifestInterface{"plugins.sock", []types.PluginInterfaceType{iType}}
@ -21,7 +21,7 @@ func TestFilterByCapNeg(t *testing.T) {
}
func TestFilterByCapPos(t *testing.T) {
p := v2.NewPlugin("test", "1234567890", "/run/docker", "latest")
p := v2.NewPlugin("test", "1234567890", "/run/docker", "/var/lib/docker/plugins", "latest")
iType := types.PluginInterfaceType{"volumedriver", "docker", "1.0"}
i := types.PluginManifestInterface{"plugins.sock", []types.PluginInterfaceType{iType}}

View file

@ -2,7 +2,6 @@ package v2
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
@ -24,6 +23,7 @@ type Plugin struct {
RefCount int `json:"-"`
Restart bool `json:"-"`
ExitChan chan bool `json:"-"`
LibRoot string `json:"-"`
}
const defaultPluginRuntimeDestination = "/run/docker/plugins"
@ -42,10 +42,11 @@ func newPluginObj(name, id, tag string) types.Plugin {
}
// NewPlugin creates a plugin.
func NewPlugin(name, id, runRoot, tag string) *Plugin {
func NewPlugin(name, id, runRoot, libRoot, tag string) *Plugin {
return &Plugin{
PluginObj: newPluginObj(name, id, tag),
RuntimeSourcePath: filepath.Join(runRoot, id),
LibRoot: libRoot,
}
}
@ -86,8 +87,8 @@ func (p *Plugin) RemoveFromDisk() error {
}
// InitPlugin populates the plugin object from the plugin manifest file.
func (p *Plugin) InitPlugin(libRoot string) error {
dt, err := os.Open(filepath.Join(libRoot, p.PluginObj.ID, "manifest.json"))
func (p *Plugin) InitPlugin() error {
dt, err := os.Open(filepath.Join(p.LibRoot, p.PluginObj.ID, "manifest.json"))
if err != nil {
return err
}
@ -109,7 +110,11 @@ func (p *Plugin) InitPlugin(libRoot string) error {
}
copy(p.PluginObj.Config.Args, p.PluginObj.Manifest.Args.Value)
f, err := os.Create(filepath.Join(libRoot, p.PluginObj.ID, "plugin-config.json"))
return p.writeConfig()
}
func (p *Plugin) writeConfig() error {
f, err := os.Create(filepath.Join(p.LibRoot, p.PluginObj.ID, "plugin-config.json"))
if err != nil {
return err
}
@ -120,15 +125,43 @@ func (p *Plugin) InitPlugin(libRoot string) error {
// Set is used to pass arguments to the plugin.
func (p *Plugin) Set(args []string) error {
m := make(map[string]string, len(args))
for _, arg := range args {
i := strings.Index(arg, "=")
if i < 0 {
return fmt.Errorf("No equal sign '=' found in %s", arg)
}
m[arg[:i]] = arg[i+1:]
p.Lock()
defer p.Unlock()
if p.PluginObj.Enabled {
return fmt.Errorf("cannot set on an active plugin, disable plugin before setting")
}
return errors.New("not implemented")
sets, err := newSettables(args)
if err != nil {
return err
}
next:
for _, s := range sets {
// range over all the envs in the manifest
for _, env := range p.PluginObj.Manifest.Env {
// found the env in the manifest
if env.Name == s.name {
// is it settable ?
if ok, err := s.isSettable(allowedSettableFieldsEnv, env.Settable); err != nil {
return err
} else if !ok {
return fmt.Errorf("%q is not settable", s.prettyName())
}
// is it, so lets update the config in memory
updateConfigEnv(&p.PluginObj.Config.Env, &s)
continue next
}
}
//TODO: check devices, mount and args
return fmt.Errorf("setting %q not found in the plugin configuration", s.name)
}
// update the config on disk
return p.writeConfig()
}
// ComputePrivileges takes the manifest file and computes the list of access necessary

102
plugin/v2/settable.go Normal file
View file

@ -0,0 +1,102 @@
package v2
import (
"errors"
"fmt"
"strings"
)
type settable struct {
name string
field string
value string
}
var (
allowedSettableFieldsEnv = []string{"value"}
allowedSettableFieldsArgs = []string{"value"}
allowedSettableFieldsDevices = []string{"path"}
allowedSettableFieldsMounts = []string{"source"}
errMultipleFields = errors.New("multiple fields are settable, one must be specified")
errInvalidFormat = errors.New("invalid format, must be <name>[.<field>][=<value>]")
)
func newSettables(args []string) ([]settable, error) {
sets := make([]settable, 0, len(args))
for _, arg := range args {
set, err := newSettable(arg)
if err != nil {
return nil, err
}
sets = append(sets, set)
}
return sets, nil
}
func newSettable(arg string) (settable, error) {
var set settable
if i := strings.Index(arg, "="); i == 0 {
return set, errInvalidFormat
} else if i < 0 {
set.name = arg
} else {
set.name = arg[:i]
set.value = arg[i+1:]
}
if i := strings.LastIndex(set.name, "."); i > 0 {
set.field = set.name[i+1:]
set.name = arg[:i]
}
return set, nil
}
// prettyName return name.field if there is a field, otherwise name.
func (set *settable) prettyName() string {
if set.field != "" {
return fmt.Sprintf("%s.%s", set.name, set.field)
}
return set.name
}
func (set *settable) isSettable(allowedSettableFields []string, settable []string) (bool, error) {
if set.field == "" {
if len(settable) == 1 {
// if field is not specified and there only one settable, default to it.
set.field = settable[0]
} else if len(settable) > 1 {
return false, errMultipleFields
}
}
isAllowed := false
for _, allowedSettableField := range allowedSettableFields {
if set.field == allowedSettableField {
isAllowed = true
break
}
}
if isAllowed {
for _, settableField := range settable {
if set.field == settableField {
return true, nil
}
}
}
return false, nil
}
func updateConfigEnv(env *[]string, set *settable) {
for i, e := range *env {
if parts := strings.SplitN(e, "=", 2); parts[0] == set.name {
(*env)[i] = fmt.Sprintf("%s=%s", set.name, set.value)
return
}
}
*env = append(*env, fmt.Sprintf("%s=%s", set.name, set.value))
}

View file

@ -0,0 +1,91 @@
package v2
import (
"reflect"
"testing"
)
func TestNewSettable(t *testing.T) {
contexts := []struct {
arg string
name string
field string
value string
err error
}{
{"name=value", "name", "", "value", nil},
{"name", "name", "", "", nil},
{"name.field=value", "name", "field", "value", nil},
{"name.field", "name", "field", "", nil},
{"=value", "", "", "", errInvalidFormat},
{"=", "", "", "", errInvalidFormat},
}
for _, c := range contexts {
s, err := newSettable(c.arg)
if err != c.err {
t.Fatalf("expected error to be %v, got %v", c.err, err)
}
if s.name != c.name {
t.Fatalf("expected name to be %q, got %q", c.name, s.name)
}
if s.field != c.field {
t.Fatalf("expected field to be %q, got %q", c.field, s.field)
}
if s.value != c.value {
t.Fatalf("expected value to be %q, got %q", c.value, s.value)
}
}
}
func TestIsSettable(t *testing.T) {
contexts := []struct {
allowedSettableFields []string
set settable
settable []string
result bool
err error
}{
{allowedSettableFieldsEnv, settable{}, []string{}, false, nil},
{allowedSettableFieldsEnv, settable{field: "value"}, []string{}, false, nil},
{allowedSettableFieldsEnv, settable{}, []string{"value"}, true, nil},
{allowedSettableFieldsEnv, settable{field: "value"}, []string{"value"}, true, nil},
{allowedSettableFieldsEnv, settable{field: "foo"}, []string{"value"}, false, nil},
{allowedSettableFieldsEnv, settable{field: "foo"}, []string{"foo"}, false, nil},
{allowedSettableFieldsEnv, settable{}, []string{"value1", "value2"}, false, errMultipleFields},
}
for _, c := range contexts {
if res, err := c.set.isSettable(c.allowedSettableFields, c.settable); res != c.result {
t.Fatalf("expected result to be %t, got %t", c.result, res)
} else if err != c.err {
t.Fatalf("expected error to be %v, got %v", c.err, err)
}
}
}
func TestUpdateConfigEnv(t *testing.T) {
contexts := []struct {
env []string
set settable
newEnv []string
}{
{[]string{}, settable{name: "DEBUG", value: "1"}, []string{"DEBUG=1"}},
{[]string{"DEBUG=0"}, settable{name: "DEBUG", value: "1"}, []string{"DEBUG=1"}},
{[]string{"FOO=0"}, settable{name: "DEBUG", value: "1"}, []string{"FOO=0", "DEBUG=1"}},
{[]string{"FOO=0", "DEBUG=0"}, settable{name: "DEBUG", value: "1"}, []string{"FOO=0", "DEBUG=1"}},
{[]string{"FOO=0", "DEBUG=0", "BAR=1"}, settable{name: "DEBUG", value: "1"}, []string{"FOO=0", "DEBUG=1", "BAR=1"}},
}
for _, c := range contexts {
updateConfigEnv(&c.env, &c.set)
if !reflect.DeepEqual(c.env, c.newEnv) {
t.Fatalf("expected env to be %q, got %q", c.newEnv, c.env)
}
}
}