diff --git a/integration-cli/docker_cli_plugins_test.go b/integration-cli/docker_cli_plugins_test.go index 4166405a29..f6d2f28b54 100644 --- a/integration-cli/docker_cli_plugins_test.go +++ b/integration-cli/docker_cli_plugins_test.go @@ -4,6 +4,7 @@ import ( "github.com/docker/docker/pkg/integration/checker" "github.com/go-check/check" + "io/ioutil" "os" "path/filepath" "strings" @@ -169,3 +170,34 @@ func (s *DockerSuite) TestPluginEnableDisableNegative(c *check.C) { _, _, err = dockerCmdWithError("plugin", "remove", pName) c.Assert(err, checker.IsNil) } + +func (s *DockerSuite) TestPluginCreate(c *check.C) { + testRequires(c, DaemonIsLinux, Network) + + name := "foo/bar-driver" + temp, err := ioutil.TempDir("", "foo") + c.Assert(err, checker.IsNil) + defer os.RemoveAll(temp) + + data := `{"description": "foo plugin"}` + err = ioutil.WriteFile(filepath.Join(temp, "config.json"), []byte(data), 0644) + c.Assert(err, checker.IsNil) + + out, _, err := dockerCmdWithError("plugin", "create", name, temp) + c.Assert(err, checker.IsNil) + c.Assert(out, checker.Contains, name) + + out, _, err = dockerCmdWithError("plugin", "ls") + c.Assert(err, checker.IsNil) + c.Assert(out, checker.Contains, name) + + out, _, err = dockerCmdWithError("plugin", "create", name, temp) + c.Assert(err, checker.NotNil) + c.Assert(out, checker.Contains, "already exist") + + out, _, err = dockerCmdWithError("plugin", "ls") + c.Assert(err, checker.IsNil) + c.Assert(out, checker.Contains, name) + // The output will consists of one HEADER line and one line of foo/bar-driver + c.Assert(len(strings.Split(strings.TrimSpace(out), "\n")), checker.Equals, 2) +} diff --git a/plugin/backend_linux.go b/plugin/backend_linux.go index 59ca225a9c..95d6962c23 100644 --- a/plugin/backend_linux.go +++ b/plugin/backend_linux.go @@ -194,6 +194,18 @@ func (pm *Manager) CreateFromContext(ctx context.Context, tarCtx io.Reader, opti return err } + // In case an error happens, remove the created directory. + if err := pm.createFromContext(ctx, pluginID, pluginDir, tarCtx, options); err != nil { + if err := os.RemoveAll(pluginDir); err != nil { + logrus.Warnf("unable to remove %q from failed plugin creation: %v", pluginDir, err) + } + return err + } + + return nil +} + +func (pm *Manager) createFromContext(ctx context.Context, pluginID, pluginDir string, tarCtx io.Reader, options *types.PluginCreateOptions) error { if err := chrootarchive.Untar(tarCtx, pluginDir, nil); err != nil { return err } @@ -211,7 +223,9 @@ func (pm *Manager) CreateFromContext(ctx context.Context, tarCtx io.Reader, opti return err } - pm.pluginStore.Add(p) + if err := pm.pluginStore.Add(p); err != nil { + return err + } pm.pluginEventLogger(p.GetID(), repoName, "create") diff --git a/plugin/manager.go b/plugin/manager.go index 656f598981..a878983308 100644 --- a/plugin/manager.go +++ b/plugin/manager.go @@ -124,7 +124,7 @@ func (pm *Manager) init() error { return } - pm.pluginStore.Add(p) + pm.pluginStore.Update(p) requiresManualRestore := !pm.liveRestore && p.IsEnabled() if requiresManualRestore { diff --git a/plugin/store/store.go b/plugin/store/store.go index d764060204..fe834dc4d7 100644 --- a/plugin/store/store.go +++ b/plugin/store/store.go @@ -98,12 +98,31 @@ func (ps *Store) SetState(p *v2.Plugin, state bool) { } // Add adds a plugin to memory and plugindb. -func (ps *Store) Add(p *v2.Plugin) { +// An error will be returned if there is a collision. +func (ps *Store) Add(p *v2.Plugin) error { ps.Lock() + defer ps.Unlock() + + if v, exist := ps.plugins[p.GetID()]; exist { + return fmt.Errorf("plugin %q has the same ID %s as %q", p.Name(), p.GetID(), v.Name()) + } + if _, exist := ps.nameToID[p.Name()]; exist { + return fmt.Errorf("plugin %q already exists", p.Name()) + } + ps.plugins[p.GetID()] = p + ps.nameToID[p.Name()] = p.GetID() + ps.updatePluginDB() + return nil +} + +// Update updates a plugin to memory and plugindb. +func (ps *Store) Update(p *v2.Plugin) { + ps.Lock() + defer ps.Unlock() + ps.plugins[p.GetID()] = p ps.nameToID[p.Name()] = p.GetID() ps.updatePluginDB() - ps.Unlock() } // Remove removes a plugin from memory and plugindb.