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 <yong.tang.github@outlook.com>
This commit is contained in:
Yong Tang 2016-11-29 12:55:41 -08:00
parent f244174664
commit 52405a9b58
2 changed files with 23 additions and 11 deletions

View File

@ -200,15 +200,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)
}
@ -218,20 +232,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
}

View File

@ -106,6 +106,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())
}