Merge pull request #27923 from vieux/plugin_set_gogogo
support env for docker plugin set
This commit is contained in:
commit
03da822ee9
15 changed files with 334 additions and 23 deletions
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
51
docs/reference/commandline/plugin_set.md
Normal file
51
docs/reference/commandline/plugin_set.md
Normal 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)
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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
102
plugin/v2/settable.go
Normal 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))
|
||||
}
|
91
plugin/v2/settable_test.go
Normal file
91
plugin/v2/settable_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue