plugin/backend_linux.go
f3711704
 package plugin
 
 import (
3d86b0c7
 	"archive/tar"
 	"compress/gzip"
b47711ae
 	"encoding/json"
5b6e1bc9
 	"io"
b47711ae
 	"io/ioutil"
f3711704
 	"net/http"
 	"os"
3d86b0c7
 	"path"
f3711704
 	"path/filepath"
afd305c4
 	"runtime"
3d86b0c7
 	"strings"
f3711704
 
3d86b0c7
 	"github.com/docker/distribution/manifest/schema2"
3a127939
 	"github.com/docker/distribution/reference"
91e197d6
 	"github.com/docker/docker/api/types"
a66e0dc3
 	"github.com/docker/docker/api/types/filters"
3d86b0c7
 	"github.com/docker/docker/distribution"
 	progressutils "github.com/docker/docker/distribution/utils"
 	"github.com/docker/docker/distribution/xfer"
342ed107
 	"github.com/docker/docker/dockerversion"
d453fe35
 	"github.com/docker/docker/errdefs"
3d86b0c7
 	"github.com/docker/docker/image"
 	"github.com/docker/docker/layer"
38de272b
 	"github.com/docker/docker/pkg/authorization"
5b6e1bc9
 	"github.com/docker/docker/pkg/chrootarchive"
e8307b86
 	"github.com/docker/docker/pkg/mount"
3d86b0c7
 	"github.com/docker/docker/pkg/pools"
 	"github.com/docker/docker/pkg/progress"
11cf394e
 	"github.com/docker/docker/pkg/system"
27a55fba
 	"github.com/docker/docker/plugin/v2"
3a127939
 	refstore "github.com/docker/docker/reference"
62c1f0ef
 	digest "github.com/opencontainers/go-digest"
fb11164c
 	"github.com/pkg/errors"
1009e6a4
 	"github.com/sirupsen/logrus"
5b6e1bc9
 	"golang.org/x/net/context"
f3711704
 )
 
a66e0dc3
 var acceptedPluginFilterTags = map[string]bool{
99d91ada
 	"enabled":    true,
 	"capability": true,
a66e0dc3
 }
 
8cb2229c
 // Disable deactivates a plugin. This means resources (volumes, networks) cant use them.
3d86b0c7
 func (pm *Manager) Disable(refOrID string, config *types.PluginDisableConfig) error {
 	p, err := pm.config.Store.GetV2Plugin(refOrID)
f3711704
 	if err != nil {
 		return err
 	}
b35490a8
 	pm.mu.RLock()
 	c := pm.cMap[p]
 	pm.mu.RUnlock()
 
8cb2229c
 	if !config.ForceDisable && p.GetRefCount() > 0 {
ebcb7d6b
 		return errors.WithStack(inUseError(p.Name()))
8cb2229c
 	}
 
38de272b
 	for _, typ := range p.GetTypes() {
 		if typ.Capability == authorization.AuthZApiImplements {
7da39862
 			pm.config.AuthzMiddleware.RemovePlugin(p.Name())
38de272b
 		}
 	}
 
b35490a8
 	if err := pm.disable(p, c); err != nil {
42abccb8
 		return err
 	}
72c3bcf2
 	pm.publisher.Publish(EventDisable{Plugin: p.PluginObj})
3d86b0c7
 	pm.config.LogPluginEvent(p.GetID(), refOrID, "disable")
42abccb8
 	return nil
f3711704
 }
 
 // Enable activates a plugin, which implies that they are ready to be used by containers.
3d86b0c7
 func (pm *Manager) Enable(refOrID string, config *types.PluginEnableConfig) error {
 	p, err := pm.config.Store.GetV2Plugin(refOrID)
f3711704
 	if err != nil {
 		return err
 	}
83ca993c
 
b35490a8
 	c := &controller{timeoutInSecs: config.Timeout}
 	if err := pm.enable(p, c, false); err != nil {
42abccb8
 		return err
 	}
72c3bcf2
 	pm.publisher.Publish(EventEnable{Plugin: p.PluginObj})
3d86b0c7
 	pm.config.LogPluginEvent(p.GetID(), refOrID, "enable")
42abccb8
 	return nil
f3711704
 }
 
49ca91fb
 // Inspect examines a plugin config
3d86b0c7
 func (pm *Manager) Inspect(refOrID string) (tp *types.Plugin, err error) {
 	p, err := pm.config.Store.GetV2Plugin(refOrID)
 	if err != nil {
 		return nil, err
0ce6e070
 	}
 
3d86b0c7
 	return &p.PluginObj, nil
 }
0ce6e070
 
3d86b0c7
 func (pm *Manager) pull(ctx context.Context, ref reference.Named, config *distribution.ImagePullConfig, outStream io.Writer) error {
 	if outStream != nil {
 		// Include a buffer so that slow client connections don't affect
 		// transfer performance.
 		progressChan := make(chan progress.Progress, 100)
 
 		writesDone := make(chan struct{})
 
 		defer func() {
 			close(progressChan)
 			<-writesDone
 		}()
 
 		var cancelFunc context.CancelFunc
 		ctx, cancelFunc = context.WithCancel(ctx)
 
 		go func() {
 			progressutils.WriteDistributionProgress(cancelFunc, outStream, progressChan)
 			close(writesDone)
 		}()
 
 		config.ProgressOutput = progress.ChanOutput(progressChan)
 	} else {
 		config.ProgressOutput = progress.DiscardOutput()
f3711704
 	}
3d86b0c7
 	return distribution.Pull(ctx, ref, config)
 }
0ce6e070
 
3d86b0c7
 type tempConfigStore struct {
 	config       []byte
 	configDigest digest.Digest
f3711704
 }
 
3d86b0c7
 func (s *tempConfigStore) Put(c []byte) (digest.Digest, error) {
 	dgst := digest.FromBytes(c)
fa3b61a2
 
3d86b0c7
 	s.config = c
 	s.configDigest = dgst
fa3b61a2
 
3d86b0c7
 	return dgst, nil
fa3b61a2
 }
9e423426
 
3d86b0c7
 func (s *tempConfigStore) Get(d digest.Digest) ([]byte, error) {
 	if d != s.configDigest {
ebcb7d6b
 		return nil, errNotFound("digest not found")
9e423426
 	}
3d86b0c7
 	return s.config, nil
 }
9e423426
 
ce8e529e
 func (s *tempConfigStore) RootFSAndOSFromConfig(c []byte) (*image.RootFS, string, error) {
3d86b0c7
 	return configToRootFS(c)
 }
9e423426
 
ebcb7d6b
 func computePrivileges(c types.PluginConfig) types.PluginPrivileges {
fa3b61a2
 	var privileges types.PluginPrivileges
04e35a01
 	if c.Network.Type != "null" && c.Network.Type != "bridge" && c.Network.Type != "" {
fa3b61a2
 		privileges = append(privileges, types.PluginPrivilege{
 			Name:        "network",
 			Description: "permissions to access a network",
 			Value:       []string{c.Network.Type},
 		})
 	}
6d6185c2
 	if c.IpcHost {
 		privileges = append(privileges, types.PluginPrivilege{
 			Name:        "host ipc namespace",
 			Description: "allow access to host ipc namespace",
 			Value:       []string{"true"},
 		})
 	}
4d1edcb2
 	if c.PidHost {
 		privileges = append(privileges, types.PluginPrivilege{
 			Name:        "host pid namespace",
 			Description: "allow access to host pid namespace",
 			Value:       []string{"true"},
 		})
 	}
fa3b61a2
 	for _, mount := range c.Mounts {
 		if mount.Source != nil {
 			privileges = append(privileges, types.PluginPrivilege{
 				Name:        "mount",
 				Description: "host path to mount",
 				Value:       []string{*mount.Source},
 			})
 		}
 	}
 	for _, device := range c.Linux.Devices {
 		if device.Path != nil {
 			privileges = append(privileges, types.PluginPrivilege{
 				Name:        "device",
 				Description: "host device to access",
 				Value:       []string{*device.Path},
 			})
 		}
 	}
f265727b
 	if c.Linux.AllowAllDevices {
fa3b61a2
 		privileges = append(privileges, types.PluginPrivilege{
f265727b
 			Name:        "allow-all-devices",
 			Description: "allow 'rwm' access to all devices",
fa3b61a2
 			Value:       []string{"true"},
 		})
 	}
 	if len(c.Linux.Capabilities) > 0 {
 		privileges = append(privileges, types.PluginPrivilege{
 			Name:        "capabilities",
 			Description: "list of additional capabilities required",
 			Value:       c.Linux.Capabilities,
 		})
 	}
 
ebcb7d6b
 	return privileges
9e423426
 }
 
fa3b61a2
 // Privileges pulls a plugin config and computes the privileges required to install it.
3d86b0c7
 func (pm *Manager) Privileges(ctx context.Context, ref reference.Named, metaHeader http.Header, authConfig *types.AuthConfig) (types.PluginPrivileges, error) {
 	// create image store instance
 	cs := &tempConfigStore{}
 
 	// DownloadManager not defined because only pulling configuration.
 	pluginPullConfig := &distribution.ImagePullConfig{
 		Config: distribution.Config{
 			MetaHeaders:      metaHeader,
 			AuthConfig:       authConfig,
 			RegistryService:  pm.config.RegistryService,
 			ImageEventLogger: func(string, string, string) {},
 			ImageStore:       cs,
 		},
 		Schema2Types: distribution.PluginTypes,
 	}
 
 	if err := pm.pull(ctx, ref, pluginPullConfig, nil); err != nil {
f3711704
 		return nil, err
 	}
3d86b0c7
 
 	if cs.config == nil {
 		return nil, errors.New("no configuration pulled")
 	}
 	var config types.PluginConfig
 	if err := json.Unmarshal(cs.config, &config); err != nil {
87a12421
 		return nil, errdefs.System(err)
3d86b0c7
 	}
 
ebcb7d6b
 	return computePrivileges(config), nil
fa3b61a2
 }
f3711704
 
03c69497
 // 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 {
ebcb7d6b
 		return err
03c69497
 	}
 
 	if p.IsEnabled() {
ebcb7d6b
 		return errors.Wrap(enabledError(p.Name()), "plugin must be disabled before upgrading")
03c69497
 	}
 
 	pm.muGC.RLock()
 	defer pm.muGC.RUnlock()
 
 	// revalidate because Pull is public
145dfd92
 	if _, err := reference.ParseNormalizedNamed(name); err != nil {
87a12421
 		return errors.Wrapf(errdefs.InvalidParameter(err), "failed to parse %q", name)
03c69497
 	}
 
 	tmpRootFSDir, err := ioutil.TempDir(pm.tmpDir(), ".rootfs")
3716dd22
 	if err != nil {
87a12421
 		return errors.Wrap(errdefs.System(err), "error preparing upgrade")
3716dd22
 	}
03c69497
 	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
 }
 
fa3b61a2
 // Pull pulls a plugin, check if the correct privileges are provided and install the plugin.
72c3bcf2
 func (pm *Manager) Pull(ctx context.Context, ref reference.Named, name string, metaHeader http.Header, authConfig *types.AuthConfig, privileges types.PluginPrivileges, outStream io.Writer, opts ...CreateOpt) (err error) {
3d86b0c7
 	pm.muGC.RLock()
 	defer pm.muGC.RUnlock()
 
 	// revalidate because Pull is public
3a127939
 	nameref, err := reference.ParseNormalizedNamed(name)
fa3b61a2
 	if err != nil {
87a12421
 		return errors.Wrapf(errdefs.InvalidParameter(err), "failed to parse %q", name)
fa3b61a2
 	}
3a127939
 	name = reference.FamiliarString(reference.TagNameOnly(nameref))
fa3b61a2
 
3d86b0c7
 	if err := pm.config.Store.validateName(name); err != nil {
87a12421
 		return errdefs.InvalidParameter(err)
fa3b61a2
 	}
 
3d86b0c7
 	tmpRootFSDir, err := ioutil.TempDir(pm.tmpDir(), ".rootfs")
3716dd22
 	if err != nil {
87a12421
 		return errors.Wrap(errdefs.System(err), "error preparing pull")
3716dd22
 	}
3d86b0c7
 	defer os.RemoveAll(tmpRootFSDir)
f3711704
 
3d86b0c7
 	dm := &downloadManager{
 		tmpDir:    tmpRootFSDir,
 		blobStore: pm.blobStore,
f3711704
 	}
 
3d86b0c7
 	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,
 	}
fa3b61a2
 
3d86b0c7
 	err = pm.pull(ctx, ref, pluginPullConfig, outStream)
fa3b61a2
 	if err != nil {
3d86b0c7
 		go pm.GC()
fa3b61a2
 		return err
 	}
 
72c3bcf2
 	refOpt := func(p *v2.Plugin) {
 		p.PluginObj.PluginReference = ref.String()
 	}
 	optsList := make([]CreateOpt, 0, len(opts)+1)
 	optsList = append(optsList, opts...)
 	optsList = append(optsList, refOpt)
 
 	p, err := pm.createPlugin(name, dm.configDigest, dm.blobs, tmpRootFSDir, &privileges, optsList...)
03c69497
 	if err != nil {
fa3b61a2
 		return err
f3711704
 	}
fa3b61a2
 
72c3bcf2
 	pm.publisher.Publish(EventCreate{Plugin: p.PluginObj})
fa3b61a2
 	return nil
f3711704
 }
 
 // List displays the list of plugins and associated metadata.
a66e0dc3
 func (pm *Manager) List(pluginFilters filters.Args) ([]types.Plugin, error) {
 	if err := pluginFilters.Validate(acceptedPluginFilterTags); err != nil {
 		return nil, err
 	}
 
 	enabledOnly := false
 	disabledOnly := false
97c5ae25
 	if pluginFilters.Contains("enabled") {
a66e0dc3
 		if pluginFilters.ExactMatch("enabled", "true") {
 			enabledOnly = true
 		} else if pluginFilters.ExactMatch("enabled", "false") {
 			disabledOnly = true
 		} else {
ebcb7d6b
 			return nil, invalidFilter{"enabled", pluginFilters.Get("enabled")}
a66e0dc3
 		}
 	}
 
3d86b0c7
 	plugins := pm.config.Store.GetAll()
27a55fba
 	out := make([]types.Plugin, 0, len(plugins))
a66e0dc3
 
99d91ada
 next:
27a55fba
 	for _, p := range plugins {
a66e0dc3
 		if enabledOnly && !p.PluginObj.Enabled {
 			continue
 		}
 		if disabledOnly && p.PluginObj.Enabled {
 			continue
 		}
97c5ae25
 		if pluginFilters.Contains("capability") {
99d91ada
 			for _, f := range p.GetTypes() {
 				if !pluginFilters.Match("capability", f.Capability) {
 					continue next
 				}
 			}
 		}
42abccb8
 		out = append(out, p.PluginObj)
f3711704
 	}
 	return out, nil
 }
 
 // Push pushes a plugin to the store.
3d86b0c7
 func (pm *Manager) Push(ctx context.Context, name string, metaHeader http.Header, authConfig *types.AuthConfig, outStream io.Writer) error {
 	p, err := pm.config.Store.GetV2Plugin(name)
2281ce7e
 	if err != nil {
 		return err
 	}
3d86b0c7
 
3a127939
 	ref, err := reference.ParseNormalizedNamed(p.Name())
b47711ae
 	if err != nil {
3d86b0c7
 		return errors.Wrapf(err, "plugin has invalid name %v for push", p.Name())
b47711ae
 	}
 
3d86b0c7
 	var po progress.Output
 	if outStream != nil {
 		// Include a buffer so that slow client connections don't affect
 		// transfer performance.
 		progressChan := make(chan progress.Progress, 100)
 
 		writesDone := make(chan struct{})
 
 		defer func() {
 			close(progressChan)
 			<-writesDone
 		}()
 
 		var cancelFunc context.CancelFunc
 		ctx, cancelFunc = context.WithCancel(ctx)
 
 		go func() {
 			progressutils.WriteDistributionProgress(cancelFunc, outStream, progressChan)
 			close(writesDone)
 		}()
 
 		po = progress.ChanOutput(progressChan)
 	} else {
 		po = progress.DiscardOutput()
 	}
 
 	// TODO: replace these with manager
 	is := &pluginConfigStore{
 		pm:     pm,
 		plugin: p,
 	}
afd305c4
 	lss := make(map[string]distribution.PushLayerProvider)
 	lss[runtime.GOOS] = &pluginLayerProvider{
3d86b0c7
 		pm:     pm,
 		plugin: p,
 	}
 	rs := &pluginReference{
 		name:     ref,
 		pluginID: p.Config,
f3711704
 	}
2281ce7e
 
3d86b0c7
 	uploadManager := xfer.NewLayerUploadManager(3)
 
 	imagePushConfig := &distribution.ImagePushConfig{
 		Config: distribution.Config{
 			MetaHeaders:      metaHeader,
 			AuthConfig:       authConfig,
 			ProgressOutput:   po,
 			RegistryService:  pm.config.RegistryService,
 			ReferenceStore:   rs,
 			ImageEventLogger: pm.config.LogPluginEvent,
 			ImageStore:       is,
 			RequireSchema2:   true,
 		},
 		ConfigMediaType: schema2.MediaTypePluginConfig,
afd305c4
 		LayerStores:     lss,
3d86b0c7
 		UploadManager:   uploadManager,
 	}
 
 	return distribution.Push(ctx, ref, imagePushConfig)
 }
 
 type pluginReference struct {
 	name     reference.Named
 	pluginID digest.Digest
 }
 
 func (r *pluginReference) References(id digest.Digest) []reference.Named {
 	if r.pluginID != id {
 		return nil
 	}
 	return []reference.Named{r.name}
 }
 
3a127939
 func (r *pluginReference) ReferencesByName(ref reference.Named) []refstore.Association {
 	return []refstore.Association{
3d86b0c7
 		{
 			Ref: r.name,
 			ID:  r.pluginID,
 		},
 	}
 }
 
 func (r *pluginReference) Get(ref reference.Named) (digest.Digest, error) {
 	if r.name.String() != ref.String() {
3a127939
 		return digest.Digest(""), refstore.ErrDoesNotExist
3d86b0c7
 	}
 	return r.pluginID, nil
 }
 
 func (r *pluginReference) AddTag(ref reference.Named, id digest.Digest, force bool) error {
 	// Read only, ignore
 	return nil
 }
 func (r *pluginReference) AddDigest(ref reference.Canonical, id digest.Digest, force bool) error {
 	// Read only, ignore
 	return nil
 }
 func (r *pluginReference) Delete(ref reference.Named) (bool, error) {
 	// Read only, ignore
 	return false, nil
 }
 
 type pluginConfigStore struct {
 	pm     *Manager
 	plugin *v2.Plugin
 }
 
 func (s *pluginConfigStore) Put([]byte) (digest.Digest, error) {
 	return digest.Digest(""), errors.New("cannot store config on push")
 }
 
 func (s *pluginConfigStore) Get(d digest.Digest) ([]byte, error) {
 	if s.plugin.Config != d {
 		return nil, errors.New("plugin not found")
 	}
 	rwc, err := s.pm.blobStore.Get(d)
f3711704
 	if err != nil {
3d86b0c7
 		return nil, err
 	}
 	defer rwc.Close()
 	return ioutil.ReadAll(rwc)
 }
 
ce8e529e
 func (s *pluginConfigStore) RootFSAndOSFromConfig(c []byte) (*image.RootFS, string, error) {
3d86b0c7
 	return configToRootFS(c)
 }
 
 type pluginLayerProvider struct {
 	pm     *Manager
 	plugin *v2.Plugin
 }
 
 func (p *pluginLayerProvider) Get(id layer.ChainID) (distribution.PushLayer, error) {
 	rootFS := rootFSFromPlugin(p.plugin.PluginObj.Config.Rootfs)
 	var i int
 	for i = 1; i <= len(rootFS.DiffIDs); i++ {
 		if layer.CreateChainID(rootFS.DiffIDs[:i]) == id {
 			break
 		}
 	}
 	if i > len(rootFS.DiffIDs) {
 		return nil, errors.New("layer not found")
 	}
 	return &pluginLayer{
 		pm:      p.pm,
 		diffIDs: rootFS.DiffIDs[:i],
 		blobs:   p.plugin.Blobsums[:i],
 	}, nil
 }
 
 type pluginLayer struct {
 	pm      *Manager
 	diffIDs []layer.DiffID
 	blobs   []digest.Digest
 }
 
 func (l *pluginLayer) ChainID() layer.ChainID {
 	return layer.CreateChainID(l.diffIDs)
 }
 
 func (l *pluginLayer) DiffID() layer.DiffID {
 	return l.diffIDs[len(l.diffIDs)-1]
 }
 
 func (l *pluginLayer) Parent() distribution.PushLayer {
 	if len(l.diffIDs) == 1 {
 		return nil
 	}
 	return &pluginLayer{
 		pm:      l.pm,
 		diffIDs: l.diffIDs[:len(l.diffIDs)-1],
 		blobs:   l.blobs[:len(l.diffIDs)-1],
f3711704
 	}
3d86b0c7
 }
0ead6244
 
3d86b0c7
 func (l *pluginLayer) Open() (io.ReadCloser, error) {
 	return l.pm.blobStore.Get(l.blobs[len(l.diffIDs)-1])
 }
 
 func (l *pluginLayer) Size() (int64, error) {
 	return l.pm.blobStore.Size(l.blobs[len(l.diffIDs)-1])
 }
 
 func (l *pluginLayer) MediaType() string {
 	return schema2.MediaTypeLayer
 }
 
 func (l *pluginLayer) Release() {
 	// Nothing needs to be release, no references held
f3711704
 }
 
 // Remove deletes plugin's root directory.
3d86b0c7
 func (pm *Manager) Remove(name string, config *types.PluginRmConfig) error {
 	p, err := pm.config.Store.GetV2Plugin(name)
b35490a8
 	pm.mu.RLock()
 	c := pm.cMap[p]
 	pm.mu.RUnlock()
 
f3711704
 	if err != nil {
 		return err
 	}
b22d07f5
 
 	if !config.ForceRemove {
b35490a8
 		if p.GetRefCount() > 0 {
ebcb7d6b
 			return inUseError(p.Name())
b22d07f5
 		}
 		if p.IsEnabled() {
ebcb7d6b
 			return enabledError(p.Name())
27a55fba
 		}
b22d07f5
 	}
 
 	if p.IsEnabled() {
b35490a8
 		if err := pm.disable(p, c); err != nil {
27a55fba
 			logrus.Errorf("failed to disable plugin '%s': %s", p.Name(), err)
 		}
42abccb8
 	}
b22d07f5
 
fb11164c
 	defer func() {
3d86b0c7
 		go pm.GC()
fb11164c
 	}()
 
3d86b0c7
 	id := p.GetID()
 	pluginDir := filepath.Join(pm.config.Root, id)
11cf394e
 
 	if err := mount.RecursiveUnmount(pluginDir); err != nil {
 		return errors.Wrap(err, "error unmounting plugin data")
e8307b86
 	}
11cf394e
 
4bf263c1
 	removeDir := pluginDir + "-removing"
 	if err := os.Rename(pluginDir, removeDir); err != nil {
11cf394e
 		return errors.Wrap(err, "error performing atomic remove of plugin dir")
 	}
 
4bf263c1
 	if err := system.EnsureRemoveAll(removeDir); err != nil {
11cf394e
 		return errors.Wrap(err, "error removing plugin dir")
c54b717c
 	}
11cf394e
 	pm.config.Store.Remove(p)
3d86b0c7
 	pm.config.LogPluginEvent(id, name, "remove")
72c3bcf2
 	pm.publisher.Publish(EventRemove{Plugin: p.PluginObj})
42abccb8
 	return nil
f3711704
 }
 
 // Set sets plugin args
 func (pm *Manager) Set(name string, args []string) error {
3d86b0c7
 	p, err := pm.config.Store.GetV2Plugin(name)
f3711704
 	if err != nil {
 		return err
 	}
3d86b0c7
 	if err := p.Set(args); err != nil {
 		return err
 	}
 	return pm.save(p)
f3711704
 }
5b6e1bc9
 
 // CreateFromContext creates a plugin from the given pluginDir which contains
49ca91fb
 // both the rootfs and the config.json and a repoName with optional tag.
3d86b0c7
 func (pm *Manager) CreateFromContext(ctx context.Context, tarCtx io.ReadCloser, options *types.PluginCreateOptions) (err error) {
 	pm.muGC.RLock()
 	defer pm.muGC.RUnlock()
 
3a127939
 	ref, err := reference.ParseNormalizedNamed(options.RepoName)
52405a9b
 	if err != nil {
3d86b0c7
 		return errors.Wrapf(err, "failed to parse reference %v", options.RepoName)
 	}
 	if _, ok := ref.(reference.Canonical); ok {
 		return errors.Errorf("canonical references are not permitted")
52405a9b
 	}
3a127939
 	name := reference.FamiliarString(reference.TagNameOnly(ref))
52405a9b
 
3d86b0c7
 	if err := pm.config.Store.validateName(name); err != nil { // fast check, real check is in createPlugin()
 		return err
 	}
5b6e1bc9
 
3d86b0c7
 	tmpRootFSDir, err := ioutil.TempDir(pm.tmpDir(), ".rootfs")
 	if err != nil {
 		return errors.Wrap(err, "failed to create temp directory")
 	}
3716dd22
 	defer os.RemoveAll(tmpRootFSDir)
 
3d86b0c7
 	var configJSON []byte
 	rootFS := splitConfigRootFSFromTar(tarCtx, &configJSON)
52405a9b
 
3d86b0c7
 	rootFSBlob, err := pm.blobStore.New()
 	if err != nil {
 		return err
52405a9b
 	}
3d86b0c7
 	defer rootFSBlob.Close()
 	gzw := gzip.NewWriter(rootFSBlob)
7a855799
 	layerDigester := digest.Canonical.Digester()
3d86b0c7
 	rootFSReader := io.TeeReader(rootFS, io.MultiWriter(gzw, layerDigester.Hash()))
52405a9b
 
3d86b0c7
 	if err := chrootarchive.Untar(rootFSReader, tmpRootFSDir, nil); err != nil {
 		return err
 	}
 	if err := rootFS.Close(); err != nil {
5b6e1bc9
 		return err
 	}
 
3d86b0c7
 	if configJSON == nil {
 		return errors.New("config not found")
 	}
 
 	if err := gzw.Close(); err != nil {
 		return errors.Wrap(err, "error closing gzip writer")
 	}
 
 	var config types.PluginConfig
 	if err := json.Unmarshal(configJSON, &config); err != nil {
 		return errors.Wrap(err, "failed to parse config")
 	}
 
 	if err := pm.validateConfig(config); err != nil {
662d4569
 		return err
 	}
 
3d86b0c7
 	pm.mu.Lock()
 	defer pm.mu.Unlock()
662d4569
 
3d86b0c7
 	rootFSBlobsum, err := rootFSBlob.Commit()
 	if err != nil {
5b6e1bc9
 		return err
 	}
3d86b0c7
 	defer func() {
 		if err != nil {
 			go pm.GC()
 		}
 	}()
 
 	config.Rootfs = &types.PluginConfigRootfs{
 		Type:    "layers",
 		DiffIds: []string{layerDigester.Digest().String()},
 	}
5b6e1bc9
 
342ed107
 	config.DockerVersion = dockerversion.Version
 
3d86b0c7
 	configBlob, err := pm.blobStore.New()
 	if err != nil {
 		return err
 	}
 	defer configBlob.Close()
 	if err := json.NewEncoder(configBlob).Encode(config); err != nil {
 		return errors.Wrap(err, "error encoding json config")
 	}
 	configBlobsum, err := configBlob.Commit()
 	if err != nil {
5b6e1bc9
 		return err
 	}
 
3d86b0c7
 	p, err := pm.createPlugin(name, configBlobsum, []digest.Digest{rootFSBlobsum}, tmpRootFSDir, nil)
 	if err != nil {
662d4569
 		return err
 	}
3a127939
 	p.PluginObj.PluginReference = name
5b6e1bc9
 
72c3bcf2
 	pm.publisher.Publish(EventCreate{Plugin: p.PluginObj})
3d86b0c7
 	pm.config.LogPluginEvent(p.PluginObj.ID, name, "create")
5b6e1bc9
 
 	return nil
 }
0ce6e070
 
3d86b0c7
 func (pm *Manager) validateConfig(config types.PluginConfig) error {
 	return nil // TODO:
 }
 
 func splitConfigRootFSFromTar(in io.ReadCloser, config *[]byte) io.ReadCloser {
 	pr, pw := io.Pipe()
 	go func() {
 		tarReader := tar.NewReader(in)
 		tarWriter := tar.NewWriter(pw)
 		defer in.Close()
 
 		hasRootFS := false
 
 		for {
 			hdr, err := tarReader.Next()
 			if err == io.EOF {
 				if !hasRootFS {
 					pw.CloseWithError(errors.Wrap(err, "no rootfs found"))
 					return
 				}
 				// Signals end of archive.
 				tarWriter.Close()
 				pw.Close()
 				return
 			}
 			if err != nil {
 				pw.CloseWithError(errors.Wrap(err, "failed to read from tar"))
 				return
 			}
 
 			content := io.Reader(tarReader)
 			name := path.Clean(hdr.Name)
 			if path.IsAbs(name) {
 				name = name[1:]
 			}
 			if name == configFileName {
 				dt, err := ioutil.ReadAll(content)
 				if err != nil {
 					pw.CloseWithError(errors.Wrapf(err, "failed to read %s", configFileName))
 					return
 				}
 				*config = dt
 			}
 			if parts := strings.Split(name, "/"); len(parts) != 0 && parts[0] == rootFSFileName {
 				hdr.Name = path.Clean(path.Join(parts[1:]...))
 				if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(strings.ToLower(hdr.Linkname), rootFSFileName+"/") {
 					hdr.Linkname = hdr.Linkname[len(rootFSFileName)+1:]
 				}
 				if err := tarWriter.WriteHeader(hdr); err != nil {
 					pw.CloseWithError(errors.Wrap(err, "error writing tar header"))
 					return
 				}
 				if _, err := pools.Copy(tarWriter, content); err != nil {
 					pw.CloseWithError(errors.Wrap(err, "error copying tar data"))
 					return
 				}
 				hasRootFS = true
 			} else {
 				io.Copy(ioutil.Discard, content)
 			}
 		}
 	}()
 	return pr
0ce6e070
 }