Browse code

Add --force to docker plugin remove

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

Victor Vieux authored on 2016/07/23 00:24:54
Showing 13 changed files
... ...
@@ -8,27 +8,41 @@ import (
8 8
 	"github.com/docker/docker/api/client"
9 9
 	"github.com/docker/docker/cli"
10 10
 	"github.com/docker/docker/reference"
11
+	"github.com/docker/engine-api/types"
11 12
 	"github.com/spf13/cobra"
12 13
 	"golang.org/x/net/context"
13 14
 )
14 15
 
16
+type rmOptions struct {
17
+	force bool
18
+
19
+	plugins []string
20
+}
21
+
15 22
 func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
23
+	var opts rmOptions
24
+
16 25
 	cmd := &cobra.Command{
17
-		Use:     "rm PLUGIN",
18
-		Short:   "Remove a plugin",
26
+		Use:     "rm [OPTIONS] PLUGIN [PLUGIN...]",
27
+		Short:   "Remove one or more plugins",
19 28
 		Aliases: []string{"remove"},
20 29
 		Args:    cli.ExactArgs(1),
21 30
 		RunE: func(cmd *cobra.Command, args []string) error {
22
-			return runRemove(dockerCli, args)
31
+			opts.plugins = args
32
+			return runRemove(dockerCli, &opts)
23 33
 		},
24 34
 	}
25 35
 
36
+	flags := cmd.Flags()
37
+	flags.BoolVarP(&opts.force, "force", "f", false, "Force the removal of an active plugin")
26 38
 	return cmd
27 39
 }
28 40
 
29
-func runRemove(dockerCli *client.DockerCli, names []string) error {
41
+func runRemove(dockerCli *client.DockerCli, opts *rmOptions) error {
42
+	ctx := context.Background()
43
+
30 44
 	var errs cli.Errors
31
-	for _, name := range names {
45
+	for _, name := range opts.plugins {
32 46
 		named, err := reference.ParseNamed(name) // FIXME: validate
33 47
 		if err != nil {
34 48
 			return err
... ...
@@ -41,7 +55,7 @@ func runRemove(dockerCli *client.DockerCli, names []string) error {
41 41
 			return fmt.Errorf("invalid name: %s", named.String())
42 42
 		}
43 43
 		// TODO: pass names to api instead of making multiple api calls
44
-		if err := dockerCli.Client().PluginRemove(context.Background(), ref.String()); err != nil {
44
+		if err := dockerCli.Client().PluginRemove(ctx, ref.String(), types.PluginRemoveOptions{Force: opts.force}); err != nil {
45 45
 			errs = append(errs, err)
46 46
 			continue
47 47
 		}
... ...
@@ -14,7 +14,7 @@ type Backend interface {
14 14
 	Enable(name string) error
15 15
 	List() ([]enginetypes.Plugin, error)
16 16
 	Inspect(name string) (enginetypes.Plugin, error)
17
-	Remove(name string) error
17
+	Remove(name string, config *enginetypes.PluginRmConfig) error
18 18
 	Set(name string, args []string) error
19 19
 	Pull(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) (enginetypes.PluginPrivileges, error)
20 20
 	Push(name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) error
... ...
@@ -51,7 +51,15 @@ func (pr *pluginRouter) disablePlugin(ctx context.Context, w http.ResponseWriter
51 51
 }
52 52
 
53 53
 func (pr *pluginRouter) removePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
54
-	return pr.backend.Remove(vars["name"])
54
+	if err := httputils.ParseForm(r); err != nil {
55
+		return err
56
+	}
57
+
58
+	name := vars["name"]
59
+	config := &types.PluginRmConfig{
60
+		ForceRemove: httputils.BoolValue(r, "force"),
61
+	}
62
+	return pr.backend.Remove(name, config)
55 63
 }
56 64
 
57 65
 func (pr *pluginRouter) pushPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
... ...
@@ -12,7 +12,7 @@ parent = "smn_cli"
12 12
 # plugin rm (experimental)
13 13
 
14 14
 ```markdown
15
-Usage:  docker plugin rm PLUGIN
15
+Usage:  docker plugin rm [OPTIONS] PLUGIN [PLUGIN...]
16 16
 
17 17
 Remove a plugin
18 18
 
... ...
@@ -20,12 +20,14 @@ Aliases:
20 20
   rm, remove
21 21
 
22 22
 Options:
23
-      --help   Print usage
23
+      -f, --force  Force the removal of an active plugin
24
+          --help   Print usage
24 25
 ```
25 26
 
26 27
 Removes a plugin. You cannot remove a plugin if it is active, you must disable
27 28
 a plugin using the [`docker plugin disable`](plugin_disable.md) before removing
28
-it.
29
+it (or use --force, use of force is not recommended, since it can affect
30
+functioning of running containers using the plugin).
29 31
 
30 32
 The following example disables and removes the `no-remove:latest` plugin;
31 33
 
... ...
@@ -61,7 +61,7 @@ clone git golang.org/x/sys eb2c74142fd19a79b3f237334c7384d5167b1b46 https://gith
61 61
 clone git github.com/docker/go-units 651fc226e7441360384da338d0fd37f2440ffbe3
62 62
 clone git github.com/docker/go-connections fa2850ff103453a9ad190da0df0af134f0314b3d
63 63
 
64
-clone git github.com/docker/engine-api 228c7390a733320d48697cb41ae8cde4942cd3e5
64
+clone git github.com/docker/engine-api 603ec41824c63d1e6498a22271987fa1f268c0c0
65 65
 clone git github.com/RackSec/srslog 259aed10dfa74ea2961eddd1d9847619f6e98837
66 66
 clone git github.com/imdario/mergo 0.2.1
67 67
 
... ...
@@ -57,6 +57,19 @@ func (s *DockerSuite) TestPluginBasicOps(c *check.C) {
57 57
 	}
58 58
 }
59 59
 
60
+func (s *DockerSuite) TestPluginForceRemove(c *check.C) {
61
+	testRequires(c, DaemonIsLinux, ExperimentalDaemon)
62
+	out, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", pNameWithTag)
63
+	c.Assert(err, checker.IsNil)
64
+
65
+	out, _, err = dockerCmdWithError("plugin", "remove", pNameWithTag)
66
+	c.Assert(out, checker.Contains, "is active")
67
+
68
+	out, _, err = dockerCmdWithError("plugin", "remove", "--force", pNameWithTag)
69
+	c.Assert(err, checker.IsNil)
70
+	c.Assert(out, checker.Contains, pNameWithTag)
71
+}
72
+
60 73
 func (s *DockerSuite) TestPluginInstallDisable(c *check.C) {
61 74
 	testRequires(c, DaemonIsLinux, ExperimentalDaemon)
62 75
 	out, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", "--disable", pName)
... ...
@@ -131,12 +131,12 @@ func (pm *Manager) Push(name string, metaHeader http.Header, authConfig *types.A
131 131
 }
132 132
 
133 133
 // Remove deletes plugin's root directory.
134
-func (pm *Manager) Remove(name string) error {
134
+func (pm *Manager) Remove(name string, config *types.PluginRmConfig) error {
135 135
 	p, err := pm.get(name)
136 136
 	if err != nil {
137 137
 		return err
138 138
 	}
139
-	if err := pm.remove(p); err != nil {
139
+	if err := pm.remove(p, config.ForceRemove); err != nil {
140 140
 		return err
141 141
 	}
142 142
 	pm.pluginEventLogger(p.PluginObj.ID, name, "remove")
... ...
@@ -310,7 +310,7 @@ func (pm *Manager) init() error {
310 310
 		go func(p *plugin) {
311 311
 			defer group.Done()
312 312
 			if err := pm.restorePlugin(p); err != nil {
313
-				logrus.Errorf("Error restoring plugin '%s': %s", p.Name(), err)
313
+				logrus.Errorf("failed to restore plugin '%s': %s", p.Name(), err)
314 314
 				return
315 315
 			}
316 316
 
... ...
@@ -322,7 +322,7 @@ func (pm *Manager) init() error {
322 322
 			if requiresManualRestore {
323 323
 				// if liveRestore is not enabled, the plugin will be stopped now so we should enable it
324 324
 				if err := pm.enable(p); err != nil {
325
-					logrus.Errorf("Error enabling plugin '%s': %s", p.Name(), err)
325
+					logrus.Errorf("failed to enable plugin '%s': %s", p.Name(), err)
326 326
 				}
327 327
 			}
328 328
 		}(p)
... ...
@@ -363,9 +363,14 @@ func (pm *Manager) initPlugin(p *plugin) error {
363 363
 	return err
364 364
 }
365 365
 
366
-func (pm *Manager) remove(p *plugin) error {
366
+func (pm *Manager) remove(p *plugin, force bool) error {
367 367
 	if p.PluginObj.Active {
368
-		return fmt.Errorf("plugin %s is active", p.Name())
368
+		if !force {
369
+			return fmt.Errorf("plugin %s is active", p.Name())
370
+		}
371
+		if err := pm.disable(p); err != nil {
372
+			logrus.Errorf("failed to disable plugin '%s': %s", p.Name(), err)
373
+		}
369 374
 	}
370 375
 	pm.Lock() // fixme: lock single record
371 376
 	defer pm.Unlock()
... ...
@@ -380,7 +385,7 @@ func (pm *Manager) set(p *plugin, args []string) error {
380 380
 	for _, arg := range args {
381 381
 		i := strings.Index(arg, "=")
382 382
 		if i < 0 {
383
-			return fmt.Errorf("No equal sign '=' found in %s", arg)
383
+			return fmt.Errorf("no equal sign '=' found in %s", arg)
384 384
 		}
385 385
 		m[arg[:i]] = arg[i+1:]
386 386
 	}
... ...
@@ -393,7 +398,7 @@ func (pm *Manager) save() error {
393 393
 
394 394
 	jsonData, err := json.Marshal(pm.plugins)
395 395
 	if err != nil {
396
-		logrus.Debugf("Error in json.Marshal: %v", err)
396
+		logrus.Debugf("failure in json.Marshal: %v", err)
397 397
 		return err
398 398
 	}
399 399
 	ioutils.AtomicWriteFile(filePath, jsonData, 0600)
... ...
@@ -24,7 +24,7 @@ type CheckpointAPIClient interface {
24 24
 // PluginAPIClient defines API client methods for the plugins
25 25
 type PluginAPIClient interface {
26 26
 	PluginList(ctx context.Context) (types.PluginsListResponse, error)
27
-	PluginRemove(ctx context.Context, name string) error
27
+	PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error
28 28
 	PluginEnable(ctx context.Context, name string) error
29 29
 	PluginDisable(ctx context.Context, name string) error
30 30
 	PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) error
... ...
@@ -3,12 +3,20 @@
3 3
 package client
4 4
 
5 5
 import (
6
+	"net/url"
7
+
8
+	"github.com/docker/engine-api/types"
6 9
 	"golang.org/x/net/context"
7 10
 )
8 11
 
9 12
 // PluginRemove removes a plugin
10
-func (cli *Client) PluginRemove(ctx context.Context, name string) error {
11
-	resp, err := cli.delete(ctx, "/plugins/"+name, nil, nil)
13
+func (cli *Client) PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error {
14
+	query := url.Values{}
15
+	if options.Force {
16
+		query.Set("force", "1")
17
+	}
18
+
19
+	resp, err := cli.delete(ctx, "/plugins/"+name, query, nil)
12 20
 	ensureReaderClosed(resp)
13 21
 	return err
14 22
 }
... ...
@@ -289,3 +289,8 @@ type ServiceListOptions struct {
289 289
 type TaskListOptions struct {
290 290
 	Filter filters.Args
291 291
 }
292
+
293
+// PluginRemoveOptions holds parameters to remove plugins.
294
+type PluginRemoveOptions struct {
295
+	Force bool
296
+}
... ...
@@ -51,3 +51,10 @@ type ExecConfig struct {
51 51
 	DetachKeys   string   // Escape keys for detach
52 52
 	Cmd          []string // Execution commands and args
53 53
 }
54
+
55
+// PluginRmConfig holds arguments for the plugin remove
56
+// operation. This struct is used to tell the backend what operations
57
+// to perform.
58
+type PluginRmConfig struct {
59
+	ForceRemove bool
60
+}
... ...
@@ -36,7 +36,7 @@ type HealthConfig struct {
36 36
 type Config struct {
37 37
 	Hostname        string                // Hostname
38 38
 	Domainname      string                // Domainname
39
-	User            string                // User that will run the command(s) inside the container
39
+	User            string                // User that will run the command(s) inside the container, also support user:group
40 40
 	AttachStdin     bool                  // Attach the standard input, makes possible user interaction
41 41
 	AttachStdout    bool                  // Attach the standard output
42 42
 	AttachStderr    bool                  // Attach the standard error