Signed-off-by: Victor Vieux <vieux@docker.com>
| ... | ... |
@@ -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 |
} |
| ... | ... |
@@ -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 |