From fed1b91bb41674f223016321dbd3e7cbacfa2e50 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Tue, 29 Nov 2016 12:55:41 -0800 Subject: [PATCH] Use GetByName to check for collision before create any context in plugin creation This fix is a follow up to the comment: https://github.com/docker/docker/pull/28717#discussion_r90040589 Currently, the collision checking is done at the last step `Add()` of plugin creation. However, at this stage the context such as plugin directories have already been creation. In case of name collision, rollback is needed which could be expensive. This fix performs the check at the beginning of CreateFromContext using GetByName. In this way, collision fails fast and no context creation or rollback is needed. Signed-off-by: Yong Tang (cherry picked from commit 52405a9b5896fd1c3ea6d8b1ca1c70e5979e3271) --- plugin/backend_linux.go | 27 ++++++++++++++++----------- plugin/store/store.go | 7 +++++++ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/plugin/backend_linux.go b/plugin/backend_linux.go index 1bad3713f4..894bff889d 100644 --- a/plugin/backend_linux.go +++ b/plugin/backend_linux.go @@ -321,15 +321,29 @@ func (pm *Manager) Set(name string, args []string) error { // CreateFromContext creates a plugin from the given pluginDir which contains // both the rootfs and the config.json and a repoName with optional tag. func (pm *Manager) CreateFromContext(ctx context.Context, tarCtx io.Reader, options *types.PluginCreateOptions) error { + repoName := options.RepoName + ref, err := distribution.GetRef(repoName) + if err != nil { + return err + } + + name := ref.Name() + tag := distribution.GetTag(ref) pluginID := stringid.GenerateNonCryptoID() + p := v2.NewPlugin(name, pluginID, pm.runRoot, pm.libRoot, tag) + + if v, _ := pm.pluginStore.GetByName(p.Name()); v != nil { + return fmt.Errorf("plugin %q already exists", p.Name()) + } + pluginDir := filepath.Join(pm.libRoot, pluginID) if err := os.MkdirAll(pluginDir, 0755); err != nil { return err } // In case an error happens, remove the created directory. - if err := pm.createFromContext(ctx, pluginID, pluginDir, tarCtx, options); err != nil { + if err := pm.createFromContext(ctx, tarCtx, pluginDir, repoName, p); err != nil { if err := os.RemoveAll(pluginDir); err != nil { logrus.Warnf("unable to remove %q from failed plugin creation: %v", pluginDir, err) } @@ -339,20 +353,11 @@ func (pm *Manager) CreateFromContext(ctx context.Context, tarCtx io.Reader, opti return nil } -func (pm *Manager) createFromContext(ctx context.Context, pluginID, pluginDir string, tarCtx io.Reader, options *types.PluginCreateOptions) error { +func (pm *Manager) createFromContext(ctx context.Context, tarCtx io.Reader, pluginDir, repoName string, p *v2.Plugin) error { if err := chrootarchive.Untar(tarCtx, pluginDir, nil); err != nil { return err } - repoName := options.RepoName - ref, err := distribution.GetRef(repoName) - if err != nil { - return err - } - name := ref.Name() - tag := distribution.GetTag(ref) - - p := v2.NewPlugin(name, pluginID, pm.runRoot, pm.libRoot, tag) if err := p.InitPlugin(); err != nil { return err } diff --git a/plugin/store/store.go b/plugin/store/store.go index 81e89581cc..17fda34a4e 100644 --- a/plugin/store/store.go +++ b/plugin/store/store.go @@ -113,6 +113,13 @@ func (ps *Store) Add(p *v2.Plugin) error { 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()) } + // Since both Pull() and CreateFromContext() calls GetByName() before any plugin + // to search for collision (to fail fast), it is unlikely the following check + // will return an error. + // However, in case two CreateFromContext() are called at the same time, + // there is still a remote possibility that a collision might happen. + // For that reason we still perform the collision check below as it is protected + // by ps.Lock() and ps.Unlock() above. if _, exist := ps.nameToID[p.Name()]; exist { return fmt.Errorf("plugin %q already exists", p.Name()) }