|
@@ -13,6 +13,7 @@ import (
|
|
|
"os"
|
|
|
"path"
|
|
|
"path/filepath"
|
|
|
+ "sort"
|
|
|
"strings"
|
|
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
@@ -25,6 +26,7 @@ import (
|
|
|
"github.com/docker/docker/image"
|
|
|
"github.com/docker/docker/layer"
|
|
|
"github.com/docker/docker/pkg/chrootarchive"
|
|
|
+ "github.com/docker/docker/pkg/mount"
|
|
|
"github.com/docker/docker/pkg/pools"
|
|
|
"github.com/docker/docker/pkg/progress"
|
|
|
"github.com/docker/docker/plugin/v2"
|
|
@@ -215,6 +217,60 @@ func (pm *Manager) Privileges(ctx context.Context, ref reference.Named, metaHead
|
|
|
return computePrivileges(config)
|
|
|
}
|
|
|
|
|
|
+// Upgrade upgrades a plugin
|
|
|
+func (pm *Manager) Upgrade(ctx context.Context, ref reference.Named, name string, metaHeader http.Header, authConfig *types.AuthConfig, privileges types.PluginPrivileges, outStream io.Writer) (err error) {
|
|
|
+ p, err := pm.config.Store.GetV2Plugin(name)
|
|
|
+ if err != nil {
|
|
|
+ return errors.Wrap(err, "plugin must be installed before upgrading")
|
|
|
+ }
|
|
|
+
|
|
|
+ if p.IsEnabled() {
|
|
|
+ return fmt.Errorf("plugin must be disabled before upgrading")
|
|
|
+ }
|
|
|
+
|
|
|
+ pm.muGC.RLock()
|
|
|
+ defer pm.muGC.RUnlock()
|
|
|
+
|
|
|
+ // revalidate because Pull is public
|
|
|
+ nameref, err := reference.ParseNamed(name)
|
|
|
+ if err != nil {
|
|
|
+ return errors.Wrapf(err, "failed to parse %q", name)
|
|
|
+ }
|
|
|
+ name = reference.WithDefaultTag(nameref).String()
|
|
|
+
|
|
|
+ tmpRootFSDir, err := ioutil.TempDir(pm.tmpDir(), ".rootfs")
|
|
|
+ defer os.RemoveAll(tmpRootFSDir)
|
|
|
+
|
|
|
+ dm := &downloadManager{
|
|
|
+ tmpDir: tmpRootFSDir,
|
|
|
+ blobStore: pm.blobStore,
|
|
|
+ }
|
|
|
+
|
|
|
+ pluginPullConfig := &distribution.ImagePullConfig{
|
|
|
+ Config: distribution.Config{
|
|
|
+ MetaHeaders: metaHeader,
|
|
|
+ AuthConfig: authConfig,
|
|
|
+ RegistryService: pm.config.RegistryService,
|
|
|
+ ImageEventLogger: pm.config.LogPluginEvent,
|
|
|
+ ImageStore: dm,
|
|
|
+ },
|
|
|
+ DownloadManager: dm, // todo: reevaluate if possible to substitute distribution/xfer dependencies instead
|
|
|
+ Schema2Types: distribution.PluginTypes,
|
|
|
+ }
|
|
|
+
|
|
|
+ err = pm.pull(ctx, ref, pluginPullConfig, outStream)
|
|
|
+ if err != nil {
|
|
|
+ go pm.GC()
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := pm.upgradePlugin(p, dm.configDigest, dm.blobs, tmpRootFSDir, &privileges); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ p.PluginObj.PluginReference = ref.String()
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
// Pull pulls a plugin, check if the correct privileges are provided and install the plugin.
|
|
|
func (pm *Manager) Pull(ctx context.Context, ref reference.Named, name string, metaHeader http.Header, authConfig *types.AuthConfig, privileges types.PluginPrivileges, outStream io.Writer) (err error) {
|
|
|
pm.muGC.RLock()
|
|
@@ -257,9 +313,11 @@ func (pm *Manager) Pull(ctx context.Context, ref reference.Named, name string, m
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
- if _, err := pm.createPlugin(name, dm.configDigest, dm.blobs, tmpRootFSDir, &privileges); err != nil {
|
|
|
+ p, err := pm.createPlugin(name, dm.configDigest, dm.blobs, tmpRootFSDir, &privileges)
|
|
|
+ if err != nil {
|
|
|
return err
|
|
|
}
|
|
|
+ p.PluginObj.PluginReference = ref.String()
|
|
|
|
|
|
return nil
|
|
|
}
|
|
@@ -541,6 +599,9 @@ func (pm *Manager) Remove(name string, config *types.PluginRmConfig) error {
|
|
|
id := p.GetID()
|
|
|
pm.config.Store.Remove(p)
|
|
|
pluginDir := filepath.Join(pm.config.Root, id)
|
|
|
+ if err := recursiveUnmount(pm.config.Root); err != nil {
|
|
|
+ logrus.WithField("dir", pm.config.Root).WithField("id", id).Warn(err)
|
|
|
+ }
|
|
|
if err := os.RemoveAll(pluginDir); err != nil {
|
|
|
logrus.Warnf("unable to remove %q from plugin remove: %v", pluginDir, err)
|
|
|
}
|
|
@@ -548,6 +609,43 @@ func (pm *Manager) Remove(name string, config *types.PluginRmConfig) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+func getMounts(root string) ([]string, error) {
|
|
|
+ infos, err := mount.GetMounts()
|
|
|
+ if err != nil {
|
|
|
+ return nil, errors.Wrap(err, "failed to read mount table while performing recursive unmount")
|
|
|
+ }
|
|
|
+
|
|
|
+ var mounts []string
|
|
|
+ for _, m := range infos {
|
|
|
+ if strings.HasPrefix(m.Mountpoint, root) {
|
|
|
+ mounts = append(mounts, m.Mountpoint)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return mounts, nil
|
|
|
+}
|
|
|
+
|
|
|
+func recursiveUnmount(root string) error {
|
|
|
+ mounts, err := getMounts(root)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // sort in reverse-lexicographic order so the root mount will always be last
|
|
|
+ sort.Sort(sort.Reverse(sort.StringSlice(mounts)))
|
|
|
+
|
|
|
+ for i, m := range mounts {
|
|
|
+ if err := mount.Unmount(m); err != nil {
|
|
|
+ if i == len(mounts)-1 {
|
|
|
+ return errors.Wrapf(err, "error performing recursive unmount on %s", root)
|
|
|
+ }
|
|
|
+ logrus.WithError(err).WithField("mountpoint", m).Warn("could not unmount")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
// Set sets plugin args
|
|
|
func (pm *Manager) Set(name string, args []string) error {
|
|
|
p, err := pm.config.Store.GetV2Plugin(name)
|
|
@@ -573,7 +671,8 @@ func (pm *Manager) CreateFromContext(ctx context.Context, tarCtx io.ReadCloser,
|
|
|
if _, ok := ref.(reference.Canonical); ok {
|
|
|
return errors.Errorf("canonical references are not permitted")
|
|
|
}
|
|
|
- name := reference.WithDefaultTag(ref).String()
|
|
|
+ taggedRef := reference.WithDefaultTag(ref)
|
|
|
+ name := taggedRef.String()
|
|
|
|
|
|
if err := pm.config.Store.validateName(name); err != nil { // fast check, real check is in createPlugin()
|
|
|
return err
|
|
@@ -655,6 +754,7 @@ func (pm *Manager) CreateFromContext(ctx context.Context, tarCtx io.ReadCloser,
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
|
+ p.PluginObj.PluginReference = taggedRef.String()
|
|
|
|
|
|
pm.config.LogPluginEvent(p.PluginObj.ID, name, "create")
|
|
|
|