Enforce zero plugin refcount during disable.
When plugins have a positive refcount, they were not allowed to be
removed. However, plugins could still be disabled when volumes
referenced it and containers using them were running.
This change fixes that by enforcing plugin refcount during disable.
A "force" disable option is also added to ignore reference refcounting.
Signed-off-by: Anusha Ragunathan <anusha@docker.com>
(cherry picked from commit 8cb2229cd1
)
This commit is contained in:
parent
05426c80d9
commit
2039ea6adc
13 changed files with 69 additions and 31 deletions
|
@ -10,7 +10,7 @@ import (
|
||||||
|
|
||||||
// Backend for Plugin
|
// Backend for Plugin
|
||||||
type Backend interface {
|
type Backend interface {
|
||||||
Disable(name string) error
|
Disable(name string, config *enginetypes.PluginDisableConfig) error
|
||||||
Enable(name string, config *enginetypes.PluginEnableConfig) error
|
Enable(name string, config *enginetypes.PluginEnableConfig) error
|
||||||
List() ([]enginetypes.Plugin, error)
|
List() ([]enginetypes.Plugin, error)
|
||||||
Inspect(name string) (enginetypes.Plugin, error)
|
Inspect(name string) (enginetypes.Plugin, error)
|
||||||
|
|
|
@ -99,7 +99,16 @@ func (pr *pluginRouter) enablePlugin(ctx context.Context, w http.ResponseWriter,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pr *pluginRouter) disablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (pr *pluginRouter) disablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
return pr.backend.Disable(vars["name"])
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := vars["name"]
|
||||||
|
config := &types.PluginDisableConfig{
|
||||||
|
ForceDisable: httputils.BoolValue(r, "force"),
|
||||||
|
}
|
||||||
|
|
||||||
|
return pr.backend.Disable(name, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pr *pluginRouter) removePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (pr *pluginRouter) removePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
|
|
@ -340,6 +340,11 @@ type PluginEnableOptions struct {
|
||||||
Timeout int
|
Timeout int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PluginDisableOptions holds parameters to disable plugins.
|
||||||
|
type PluginDisableOptions struct {
|
||||||
|
Force bool
|
||||||
|
}
|
||||||
|
|
||||||
// PluginInstallOptions holds parameters to install a plugin.
|
// PluginInstallOptions holds parameters to install a plugin.
|
||||||
type PluginInstallOptions struct {
|
type PluginInstallOptions struct {
|
||||||
Disabled bool
|
Disabled bool
|
||||||
|
|
|
@ -53,14 +53,17 @@ type ExecConfig struct {
|
||||||
Cmd []string // Execution commands and args
|
Cmd []string // Execution commands and args
|
||||||
}
|
}
|
||||||
|
|
||||||
// PluginRmConfig holds arguments for the plugin remove
|
// PluginRmConfig holds arguments for plugin remove.
|
||||||
// operation. This struct is used to tell the backend what operations
|
|
||||||
// to perform.
|
|
||||||
type PluginRmConfig struct {
|
type PluginRmConfig struct {
|
||||||
ForceRemove bool
|
ForceRemove bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// PluginEnableConfig holds arguments for the plugin enable
|
// PluginEnableConfig holds arguments for plugin enable
|
||||||
type PluginEnableConfig struct {
|
type PluginEnableConfig struct {
|
||||||
Timeout int
|
Timeout int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PluginDisableConfig holds arguments for plugin disable.
|
||||||
|
type PluginDisableConfig struct {
|
||||||
|
ForceDisable bool
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package plugin
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/cli"
|
"github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/cli/command"
|
"github.com/docker/docker/cli/command"
|
||||||
"github.com/docker/docker/reference"
|
"github.com/docker/docker/reference"
|
||||||
|
@ -11,19 +12,23 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func newDisableCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func newDisableCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
|
var force bool
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "disable PLUGIN",
|
Use: "disable PLUGIN",
|
||||||
Short: "Disable a plugin",
|
Short: "Disable a plugin",
|
||||||
Args: cli.ExactArgs(1),
|
Args: cli.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runDisable(dockerCli, args[0])
|
return runDisable(dockerCli, args[0], force)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.BoolVarP(&force, "force", "f", false, "Force the disable of an active plugin")
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDisable(dockerCli *command.DockerCli, name string) error {
|
func runDisable(dockerCli *command.DockerCli, name string, force bool) error {
|
||||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
named, err := reference.ParseNamed(name) // FIXME: validate
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -35,7 +40,7 @@ func runDisable(dockerCli *command.DockerCli, name string) error {
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("invalid name: %s", named.String())
|
return fmt.Errorf("invalid name: %s", named.String())
|
||||||
}
|
}
|
||||||
if err := dockerCli.Client().PluginDisable(context.Background(), ref.String()); err != nil {
|
if err := dockerCli.Client().PluginDisable(context.Background(), ref.String(), types.PluginDisableOptions{Force: force}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintln(dockerCli.Out(), name)
|
fmt.Fprintln(dockerCli.Out(), name)
|
||||||
|
|
|
@ -110,7 +110,7 @@ type PluginAPIClient interface {
|
||||||
PluginList(ctx context.Context) (types.PluginsListResponse, error)
|
PluginList(ctx context.Context) (types.PluginsListResponse, error)
|
||||||
PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error
|
PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error
|
||||||
PluginEnable(ctx context.Context, name string, options types.PluginEnableOptions) error
|
PluginEnable(ctx context.Context, name string, options types.PluginEnableOptions) error
|
||||||
PluginDisable(ctx context.Context, name string) error
|
PluginDisable(ctx context.Context, name string, options types.PluginDisableOptions) error
|
||||||
PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) error
|
PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) error
|
||||||
PluginPush(ctx context.Context, name string, registryAuth string) error
|
PluginPush(ctx context.Context, name string, registryAuth string) error
|
||||||
PluginSet(ctx context.Context, name string, args []string) error
|
PluginSet(ctx context.Context, name string, args []string) error
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PluginDisable disables a plugin
|
// PluginDisable disables a plugin
|
||||||
func (cli *Client) PluginDisable(ctx context.Context, name string) error {
|
func (cli *Client) PluginDisable(ctx context.Context, name string, options types.PluginDisableOptions) error {
|
||||||
resp, err := cli.post(ctx, "/plugins/"+name+"/disable", nil, nil, nil)
|
query := url.Values{}
|
||||||
|
if options.Force {
|
||||||
|
query.Set("force", "1")
|
||||||
|
}
|
||||||
|
resp, err := cli.post(ctx, "/plugins/"+name+"/disable", query, nil, nil)
|
||||||
ensureReaderClosed(resp)
|
ensureReaderClosed(resp)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,7 +17,7 @@ func TestPluginDisableError(t *testing.T) {
|
||||||
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
|
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
|
||||||
}
|
}
|
||||||
|
|
||||||
err := client.PluginDisable(context.Background(), "plugin_name")
|
err := client.PluginDisable(context.Background(), "plugin_name", types.PluginDisableOptions{})
|
||||||
if err == nil || err.Error() != "Error response from daemon: Server error" {
|
if err == nil || err.Error() != "Error response from daemon: Server error" {
|
||||||
t.Fatalf("expected a Server Error, got %v", err)
|
t.Fatalf("expected a Server Error, got %v", err)
|
||||||
}
|
}
|
||||||
|
@ -40,7 +41,7 @@ func TestPluginDisable(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
err := client.PluginDisable(context.Background(), "plugin_name")
|
err := client.PluginDisable(context.Background(), "plugin_name", types.PluginDisableOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,11 +21,13 @@ Usage: docker plugin disable PLUGIN
|
||||||
Disable a plugin
|
Disable a plugin
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--help Print usage
|
-f, --force Force the disable of an active plugin
|
||||||
|
--help Print usage
|
||||||
```
|
```
|
||||||
|
|
||||||
Disables a plugin. The plugin must be installed before it can be disabled,
|
Disables a plugin. The plugin must be installed before it can be disabled,
|
||||||
see [`docker plugin install`](plugin_install.md).
|
see [`docker plugin install`](plugin_install.md). Without the `-f` option,
|
||||||
|
a plugin that has references (eg, volumes, networks) cannot be disabled.
|
||||||
|
|
||||||
|
|
||||||
The following example shows that the `no-remove` plugin is installed
|
The following example shows that the `no-remove` plugin is installed
|
||||||
|
|
|
@ -290,10 +290,17 @@ func (s *DockerDaemonSuite) TestPluginVolumeRemoveOnRestart(c *check.C) {
|
||||||
s.d.Restart("--live-restore=true")
|
s.d.Restart("--live-restore=true")
|
||||||
|
|
||||||
out, err = s.d.Cmd("plugin", "disable", pName)
|
out, err = s.d.Cmd("plugin", "disable", pName)
|
||||||
c.Assert(err, checker.IsNil, check.Commentf(out))
|
|
||||||
out, err = s.d.Cmd("plugin", "rm", pName)
|
|
||||||
c.Assert(err, checker.NotNil, check.Commentf(out))
|
c.Assert(err, checker.NotNil, check.Commentf(out))
|
||||||
c.Assert(out, checker.Contains, "in use")
|
c.Assert(out, checker.Contains, "in use")
|
||||||
|
|
||||||
|
out, err = s.d.Cmd("volume", "rm", "test")
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf(out))
|
||||||
|
|
||||||
|
out, err = s.d.Cmd("plugin", "disable", pName)
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf(out))
|
||||||
|
|
||||||
|
out, err = s.d.Cmd("plugin", "rm", pName)
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
func existsMountpointWithPrefix(mountpointPrefix string) (bool, error) {
|
func existsMountpointWithPrefix(mountpointPrefix string) (bool, error) {
|
||||||
|
|
|
@ -64,23 +64,18 @@ func (s *DockerSuite) TestPluginForceRemove(c *check.C) {
|
||||||
|
|
||||||
func (s *DockerSuite) TestPluginActive(c *check.C) {
|
func (s *DockerSuite) TestPluginActive(c *check.C) {
|
||||||
testRequires(c, DaemonIsLinux, IsAmd64, Network)
|
testRequires(c, DaemonIsLinux, IsAmd64, Network)
|
||||||
out, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", pNameWithTag)
|
_, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", pNameWithTag)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
out, _, err = dockerCmdWithError("volume", "create", "-d", pNameWithTag)
|
_, _, err = dockerCmdWithError("volume", "create", "-d", pNameWithTag, "--name", "testvol1")
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
vID := strings.TrimSpace(out)
|
out, _, err := dockerCmdWithError("plugin", "disable", pNameWithTag)
|
||||||
|
c.Assert(out, checker.Contains, "in use")
|
||||||
|
|
||||||
out, _, err = dockerCmdWithError("plugin", "remove", pNameWithTag)
|
_, _, err = dockerCmdWithError("volume", "rm", "testvol1")
|
||||||
c.Assert(out, checker.Contains, "is in use")
|
|
||||||
|
|
||||||
_, _, err = dockerCmdWithError("volume", "rm", vID)
|
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
out, _, err = dockerCmdWithError("plugin", "remove", pNameWithTag)
|
|
||||||
c.Assert(out, checker.Contains, "is enabled")
|
|
||||||
|
|
||||||
_, _, err = dockerCmdWithError("plugin", "disable", pNameWithTag)
|
_, _, err = dockerCmdWithError("plugin", "disable", pNameWithTag)
|
||||||
c.Assert(err, checker.IsNil)
|
c.Assert(err, checker.IsNil)
|
||||||
|
|
||||||
|
|
|
@ -31,8 +31,8 @@ var (
|
||||||
validPartialID = regexp.MustCompile(`^([a-f0-9]{1,64})$`)
|
validPartialID = regexp.MustCompile(`^([a-f0-9]{1,64})$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Disable deactivates a plugin, which implies that they cannot be used by containers.
|
// Disable deactivates a plugin. This means resources (volumes, networks) cant use them.
|
||||||
func (pm *Manager) Disable(name string) error {
|
func (pm *Manager) Disable(name string, config *types.PluginDisableConfig) error {
|
||||||
p, err := pm.pluginStore.GetByName(name)
|
p, err := pm.pluginStore.GetByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -41,6 +41,10 @@ func (pm *Manager) Disable(name string) error {
|
||||||
c := pm.cMap[p]
|
c := pm.cMap[p]
|
||||||
pm.mu.RUnlock()
|
pm.mu.RUnlock()
|
||||||
|
|
||||||
|
if !config.ForceDisable && p.GetRefCount() > 0 {
|
||||||
|
return fmt.Errorf("plugin %s is in use", p.Name())
|
||||||
|
}
|
||||||
|
|
||||||
if err := pm.disable(p, c); err != nil {
|
if err := pm.disable(p, c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
var errNotSupported = errors.New("plugins are not supported on this platform")
|
var errNotSupported = errors.New("plugins are not supported on this platform")
|
||||||
|
|
||||||
// Disable deactivates a plugin, which implies that they cannot be used by containers.
|
// Disable deactivates a plugin, which implies that they cannot be used by containers.
|
||||||
func (pm *Manager) Disable(name string) error {
|
func (pm *Manager) Disable(name string, config *types.PluginDisableConfig) error {
|
||||||
return errNotSupported
|
return errNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue