github.com/devdivbcp/moby@v17.12.0-ce-rc1.0.20200726071732-2d4bfdc789ad+incompatible/plugin/backend_linux.go (about)

     1  package plugin // import "github.com/docker/docker/plugin"
     2  
     3  import (
     4  	"archive/tar"
     5  	"compress/gzip"
     6  	"context"
     7  	"encoding/json"
     8  	"io"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"os"
    12  	"path"
    13  	"path/filepath"
    14  	"runtime"
    15  	"strings"
    16  
    17  	"github.com/docker/distribution/manifest/schema2"
    18  	"github.com/docker/distribution/reference"
    19  	"github.com/docker/docker/api/types"
    20  	"github.com/docker/docker/api/types/filters"
    21  	"github.com/docker/docker/distribution"
    22  	progressutils "github.com/docker/docker/distribution/utils"
    23  	"github.com/docker/docker/distribution/xfer"
    24  	"github.com/docker/docker/dockerversion"
    25  	"github.com/docker/docker/errdefs"
    26  	"github.com/docker/docker/image"
    27  	"github.com/docker/docker/layer"
    28  	"github.com/docker/docker/pkg/authorization"
    29  	"github.com/docker/docker/pkg/chrootarchive"
    30  	"github.com/docker/docker/pkg/mount"
    31  	"github.com/docker/docker/pkg/pools"
    32  	"github.com/docker/docker/pkg/progress"
    33  	"github.com/docker/docker/pkg/system"
    34  	"github.com/docker/docker/plugin/v2"
    35  	refstore "github.com/docker/docker/reference"
    36  	"github.com/opencontainers/go-digest"
    37  	specs "github.com/opencontainers/image-spec/specs-go/v1"
    38  	"github.com/pkg/errors"
    39  	"github.com/sirupsen/logrus"
    40  )
    41  
    42  var acceptedPluginFilterTags = map[string]bool{
    43  	"enabled":    true,
    44  	"capability": true,
    45  }
    46  
    47  // Disable deactivates a plugin. This means resources (volumes, networks) cant use them.
    48  func (pm *Manager) Disable(refOrID string, config *types.PluginDisableConfig) error {
    49  	p, err := pm.config.Store.GetV2Plugin(refOrID)
    50  	if err != nil {
    51  		return err
    52  	}
    53  	pm.mu.RLock()
    54  	c := pm.cMap[p]
    55  	pm.mu.RUnlock()
    56  
    57  	if !config.ForceDisable && p.GetRefCount() > 0 {
    58  		return errors.WithStack(inUseError(p.Name()))
    59  	}
    60  
    61  	for _, typ := range p.GetTypes() {
    62  		if typ.Capability == authorization.AuthZApiImplements {
    63  			pm.config.AuthzMiddleware.RemovePlugin(p.Name())
    64  		}
    65  	}
    66  
    67  	if err := pm.disable(p, c); err != nil {
    68  		return err
    69  	}
    70  	pm.publisher.Publish(EventDisable{Plugin: p.PluginObj})
    71  	pm.config.LogPluginEvent(p.GetID(), refOrID, "disable")
    72  	return nil
    73  }
    74  
    75  // Enable activates a plugin, which implies that they are ready to be used by containers.
    76  func (pm *Manager) Enable(refOrID string, config *types.PluginEnableConfig) error {
    77  	p, err := pm.config.Store.GetV2Plugin(refOrID)
    78  	if err != nil {
    79  		return err
    80  	}
    81  
    82  	c := &controller{timeoutInSecs: config.Timeout}
    83  	if err := pm.enable(p, c, false); err != nil {
    84  		return err
    85  	}
    86  	pm.publisher.Publish(EventEnable{Plugin: p.PluginObj})
    87  	pm.config.LogPluginEvent(p.GetID(), refOrID, "enable")
    88  	return nil
    89  }
    90  
    91  // Inspect examines a plugin config
    92  func (pm *Manager) Inspect(refOrID string) (tp *types.Plugin, err error) {
    93  	p, err := pm.config.Store.GetV2Plugin(refOrID)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	return &p.PluginObj, nil
    99  }
   100  
   101  func (pm *Manager) pull(ctx context.Context, ref reference.Named, config *distribution.ImagePullConfig, outStream io.Writer) error {
   102  	if outStream != nil {
   103  		// Include a buffer so that slow client connections don't affect
   104  		// transfer performance.
   105  		progressChan := make(chan progress.Progress, 100)
   106  
   107  		writesDone := make(chan struct{})
   108  
   109  		defer func() {
   110  			close(progressChan)
   111  			<-writesDone
   112  		}()
   113  
   114  		var cancelFunc context.CancelFunc
   115  		ctx, cancelFunc = context.WithCancel(ctx)
   116  
   117  		go func() {
   118  			progressutils.WriteDistributionProgress(cancelFunc, outStream, progressChan)
   119  			close(writesDone)
   120  		}()
   121  
   122  		config.ProgressOutput = progress.ChanOutput(progressChan)
   123  	} else {
   124  		config.ProgressOutput = progress.DiscardOutput()
   125  	}
   126  	return distribution.Pull(ctx, ref, config)
   127  }
   128  
   129  type tempConfigStore struct {
   130  	config       []byte
   131  	configDigest digest.Digest
   132  }
   133  
   134  func (s *tempConfigStore) Put(c []byte) (digest.Digest, error) {
   135  	dgst := digest.FromBytes(c)
   136  
   137  	s.config = c
   138  	s.configDigest = dgst
   139  
   140  	return dgst, nil
   141  }
   142  
   143  func (s *tempConfigStore) Get(d digest.Digest) ([]byte, error) {
   144  	if d != s.configDigest {
   145  		return nil, errNotFound("digest not found")
   146  	}
   147  	return s.config, nil
   148  }
   149  
   150  func (s *tempConfigStore) RootFSFromConfig(c []byte) (*image.RootFS, error) {
   151  	return configToRootFS(c)
   152  }
   153  
   154  func (s *tempConfigStore) PlatformFromConfig(c []byte) (*specs.Platform, error) {
   155  	// TODO: LCOW/Plugins. This will need revisiting. For now use the runtime OS
   156  	return &specs.Platform{OS: runtime.GOOS}, nil
   157  }
   158  
   159  func computePrivileges(c types.PluginConfig) types.PluginPrivileges {
   160  	var privileges types.PluginPrivileges
   161  	if c.Network.Type != "null" && c.Network.Type != "bridge" && c.Network.Type != "" {
   162  		privileges = append(privileges, types.PluginPrivilege{
   163  			Name:        "network",
   164  			Description: "permissions to access a network",
   165  			Value:       []string{c.Network.Type},
   166  		})
   167  	}
   168  	if c.IpcHost {
   169  		privileges = append(privileges, types.PluginPrivilege{
   170  			Name:        "host ipc namespace",
   171  			Description: "allow access to host ipc namespace",
   172  			Value:       []string{"true"},
   173  		})
   174  	}
   175  	if c.PidHost {
   176  		privileges = append(privileges, types.PluginPrivilege{
   177  			Name:        "host pid namespace",
   178  			Description: "allow access to host pid namespace",
   179  			Value:       []string{"true"},
   180  		})
   181  	}
   182  	for _, mount := range c.Mounts {
   183  		if mount.Source != nil {
   184  			privileges = append(privileges, types.PluginPrivilege{
   185  				Name:        "mount",
   186  				Description: "host path to mount",
   187  				Value:       []string{*mount.Source},
   188  			})
   189  		}
   190  	}
   191  	for _, device := range c.Linux.Devices {
   192  		if device.Path != nil {
   193  			privileges = append(privileges, types.PluginPrivilege{
   194  				Name:        "device",
   195  				Description: "host device to access",
   196  				Value:       []string{*device.Path},
   197  			})
   198  		}
   199  	}
   200  	if c.Linux.AllowAllDevices {
   201  		privileges = append(privileges, types.PluginPrivilege{
   202  			Name:        "allow-all-devices",
   203  			Description: "allow 'rwm' access to all devices",
   204  			Value:       []string{"true"},
   205  		})
   206  	}
   207  	if len(c.Linux.Capabilities) > 0 {
   208  		privileges = append(privileges, types.PluginPrivilege{
   209  			Name:        "capabilities",
   210  			Description: "list of additional capabilities required",
   211  			Value:       c.Linux.Capabilities,
   212  		})
   213  	}
   214  
   215  	return privileges
   216  }
   217  
   218  // Privileges pulls a plugin config and computes the privileges required to install it.
   219  func (pm *Manager) Privileges(ctx context.Context, ref reference.Named, metaHeader http.Header, authConfig *types.AuthConfig) (types.PluginPrivileges, error) {
   220  	// create image store instance
   221  	cs := &tempConfigStore{}
   222  
   223  	// DownloadManager not defined because only pulling configuration.
   224  	pluginPullConfig := &distribution.ImagePullConfig{
   225  		Config: distribution.Config{
   226  			MetaHeaders:      metaHeader,
   227  			AuthConfig:       authConfig,
   228  			RegistryService:  pm.config.RegistryService,
   229  			ImageEventLogger: func(string, string, string) {},
   230  			ImageStore:       cs,
   231  		},
   232  		Schema2Types: distribution.PluginTypes,
   233  	}
   234  
   235  	if err := pm.pull(ctx, ref, pluginPullConfig, nil); err != nil {
   236  		return nil, err
   237  	}
   238  
   239  	if cs.config == nil {
   240  		return nil, errors.New("no configuration pulled")
   241  	}
   242  	var config types.PluginConfig
   243  	if err := json.Unmarshal(cs.config, &config); err != nil {
   244  		return nil, errdefs.System(err)
   245  	}
   246  
   247  	return computePrivileges(config), nil
   248  }
   249  
   250  // Upgrade upgrades a plugin
   251  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) {
   252  	p, err := pm.config.Store.GetV2Plugin(name)
   253  	if err != nil {
   254  		return err
   255  	}
   256  
   257  	if p.IsEnabled() {
   258  		return errors.Wrap(enabledError(p.Name()), "plugin must be disabled before upgrading")
   259  	}
   260  
   261  	pm.muGC.RLock()
   262  	defer pm.muGC.RUnlock()
   263  
   264  	// revalidate because Pull is public
   265  	if _, err := reference.ParseNormalizedNamed(name); err != nil {
   266  		return errors.Wrapf(errdefs.InvalidParameter(err), "failed to parse %q", name)
   267  	}
   268  
   269  	tmpRootFSDir, err := ioutil.TempDir(pm.tmpDir(), ".rootfs")
   270  	if err != nil {
   271  		return errors.Wrap(errdefs.System(err), "error preparing upgrade")
   272  	}
   273  	defer os.RemoveAll(tmpRootFSDir)
   274  
   275  	dm := &downloadManager{
   276  		tmpDir:    tmpRootFSDir,
   277  		blobStore: pm.blobStore,
   278  	}
   279  
   280  	pluginPullConfig := &distribution.ImagePullConfig{
   281  		Config: distribution.Config{
   282  			MetaHeaders:      metaHeader,
   283  			AuthConfig:       authConfig,
   284  			RegistryService:  pm.config.RegistryService,
   285  			ImageEventLogger: pm.config.LogPluginEvent,
   286  			ImageStore:       dm,
   287  		},
   288  		DownloadManager: dm, // todo: reevaluate if possible to substitute distribution/xfer dependencies instead
   289  		Schema2Types:    distribution.PluginTypes,
   290  	}
   291  
   292  	err = pm.pull(ctx, ref, pluginPullConfig, outStream)
   293  	if err != nil {
   294  		go pm.GC()
   295  		return err
   296  	}
   297  
   298  	if err := pm.upgradePlugin(p, dm.configDigest, dm.blobs, tmpRootFSDir, &privileges); err != nil {
   299  		return err
   300  	}
   301  	p.PluginObj.PluginReference = ref.String()
   302  	return nil
   303  }
   304  
   305  // Pull pulls a plugin, check if the correct privileges are provided and install the plugin.
   306  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) {
   307  	pm.muGC.RLock()
   308  	defer pm.muGC.RUnlock()
   309  
   310  	// revalidate because Pull is public
   311  	nameref, err := reference.ParseNormalizedNamed(name)
   312  	if err != nil {
   313  		return errors.Wrapf(errdefs.InvalidParameter(err), "failed to parse %q", name)
   314  	}
   315  	name = reference.FamiliarString(reference.TagNameOnly(nameref))
   316  
   317  	if err := pm.config.Store.validateName(name); err != nil {
   318  		return errdefs.InvalidParameter(err)
   319  	}
   320  
   321  	tmpRootFSDir, err := ioutil.TempDir(pm.tmpDir(), ".rootfs")
   322  	if err != nil {
   323  		return errors.Wrap(errdefs.System(err), "error preparing pull")
   324  	}
   325  	defer os.RemoveAll(tmpRootFSDir)
   326  
   327  	dm := &downloadManager{
   328  		tmpDir:    tmpRootFSDir,
   329  		blobStore: pm.blobStore,
   330  	}
   331  
   332  	pluginPullConfig := &distribution.ImagePullConfig{
   333  		Config: distribution.Config{
   334  			MetaHeaders:      metaHeader,
   335  			AuthConfig:       authConfig,
   336  			RegistryService:  pm.config.RegistryService,
   337  			ImageEventLogger: pm.config.LogPluginEvent,
   338  			ImageStore:       dm,
   339  		},
   340  		DownloadManager: dm, // todo: reevaluate if possible to substitute distribution/xfer dependencies instead
   341  		Schema2Types:    distribution.PluginTypes,
   342  	}
   343  
   344  	err = pm.pull(ctx, ref, pluginPullConfig, outStream)
   345  	if err != nil {
   346  		go pm.GC()
   347  		return err
   348  	}
   349  
   350  	refOpt := func(p *v2.Plugin) {
   351  		p.PluginObj.PluginReference = ref.String()
   352  	}
   353  	optsList := make([]CreateOpt, 0, len(opts)+1)
   354  	optsList = append(optsList, opts...)
   355  	optsList = append(optsList, refOpt)
   356  
   357  	p, err := pm.createPlugin(name, dm.configDigest, dm.blobs, tmpRootFSDir, &privileges, optsList...)
   358  	if err != nil {
   359  		return err
   360  	}
   361  
   362  	pm.publisher.Publish(EventCreate{Plugin: p.PluginObj})
   363  	return nil
   364  }
   365  
   366  // List displays the list of plugins and associated metadata.
   367  func (pm *Manager) List(pluginFilters filters.Args) ([]types.Plugin, error) {
   368  	if err := pluginFilters.Validate(acceptedPluginFilterTags); err != nil {
   369  		return nil, err
   370  	}
   371  
   372  	enabledOnly := false
   373  	disabledOnly := false
   374  	if pluginFilters.Contains("enabled") {
   375  		if pluginFilters.ExactMatch("enabled", "true") {
   376  			enabledOnly = true
   377  		} else if pluginFilters.ExactMatch("enabled", "false") {
   378  			disabledOnly = true
   379  		} else {
   380  			return nil, invalidFilter{"enabled", pluginFilters.Get("enabled")}
   381  		}
   382  	}
   383  
   384  	plugins := pm.config.Store.GetAll()
   385  	out := make([]types.Plugin, 0, len(plugins))
   386  
   387  next:
   388  	for _, p := range plugins {
   389  		if enabledOnly && !p.PluginObj.Enabled {
   390  			continue
   391  		}
   392  		if disabledOnly && p.PluginObj.Enabled {
   393  			continue
   394  		}
   395  		if pluginFilters.Contains("capability") {
   396  			for _, f := range p.GetTypes() {
   397  				if !pluginFilters.Match("capability", f.Capability) {
   398  					continue next
   399  				}
   400  			}
   401  		}
   402  		out = append(out, p.PluginObj)
   403  	}
   404  	return out, nil
   405  }
   406  
   407  // Push pushes a plugin to the store.
   408  func (pm *Manager) Push(ctx context.Context, name string, metaHeader http.Header, authConfig *types.AuthConfig, outStream io.Writer) error {
   409  	p, err := pm.config.Store.GetV2Plugin(name)
   410  	if err != nil {
   411  		return err
   412  	}
   413  
   414  	ref, err := reference.ParseNormalizedNamed(p.Name())
   415  	if err != nil {
   416  		return errors.Wrapf(err, "plugin has invalid name %v for push", p.Name())
   417  	}
   418  
   419  	var po progress.Output
   420  	if outStream != nil {
   421  		// Include a buffer so that slow client connections don't affect
   422  		// transfer performance.
   423  		progressChan := make(chan progress.Progress, 100)
   424  
   425  		writesDone := make(chan struct{})
   426  
   427  		defer func() {
   428  			close(progressChan)
   429  			<-writesDone
   430  		}()
   431  
   432  		var cancelFunc context.CancelFunc
   433  		ctx, cancelFunc = context.WithCancel(ctx)
   434  
   435  		go func() {
   436  			progressutils.WriteDistributionProgress(cancelFunc, outStream, progressChan)
   437  			close(writesDone)
   438  		}()
   439  
   440  		po = progress.ChanOutput(progressChan)
   441  	} else {
   442  		po = progress.DiscardOutput()
   443  	}
   444  
   445  	// TODO: replace these with manager
   446  	is := &pluginConfigStore{
   447  		pm:     pm,
   448  		plugin: p,
   449  	}
   450  	lss := make(map[string]distribution.PushLayerProvider)
   451  	lss[runtime.GOOS] = &pluginLayerProvider{
   452  		pm:     pm,
   453  		plugin: p,
   454  	}
   455  	rs := &pluginReference{
   456  		name:     ref,
   457  		pluginID: p.Config,
   458  	}
   459  
   460  	uploadManager := xfer.NewLayerUploadManager(3)
   461  
   462  	imagePushConfig := &distribution.ImagePushConfig{
   463  		Config: distribution.Config{
   464  			MetaHeaders:      metaHeader,
   465  			AuthConfig:       authConfig,
   466  			ProgressOutput:   po,
   467  			RegistryService:  pm.config.RegistryService,
   468  			ReferenceStore:   rs,
   469  			ImageEventLogger: pm.config.LogPluginEvent,
   470  			ImageStore:       is,
   471  			RequireSchema2:   true,
   472  		},
   473  		ConfigMediaType: schema2.MediaTypePluginConfig,
   474  		LayerStores:     lss,
   475  		UploadManager:   uploadManager,
   476  	}
   477  
   478  	return distribution.Push(ctx, ref, imagePushConfig)
   479  }
   480  
   481  type pluginReference struct {
   482  	name     reference.Named
   483  	pluginID digest.Digest
   484  }
   485  
   486  func (r *pluginReference) References(id digest.Digest) []reference.Named {
   487  	if r.pluginID != id {
   488  		return nil
   489  	}
   490  	return []reference.Named{r.name}
   491  }
   492  
   493  func (r *pluginReference) ReferencesByName(ref reference.Named) []refstore.Association {
   494  	return []refstore.Association{
   495  		{
   496  			Ref: r.name,
   497  			ID:  r.pluginID,
   498  		},
   499  	}
   500  }
   501  
   502  func (r *pluginReference) Get(ref reference.Named) (digest.Digest, error) {
   503  	if r.name.String() != ref.String() {
   504  		return digest.Digest(""), refstore.ErrDoesNotExist
   505  	}
   506  	return r.pluginID, nil
   507  }
   508  
   509  func (r *pluginReference) AddTag(ref reference.Named, id digest.Digest, force bool) error {
   510  	// Read only, ignore
   511  	return nil
   512  }
   513  func (r *pluginReference) AddDigest(ref reference.Canonical, id digest.Digest, force bool) error {
   514  	// Read only, ignore
   515  	return nil
   516  }
   517  func (r *pluginReference) Delete(ref reference.Named) (bool, error) {
   518  	// Read only, ignore
   519  	return false, nil
   520  }
   521  
   522  type pluginConfigStore struct {
   523  	pm     *Manager
   524  	plugin *v2.Plugin
   525  }
   526  
   527  func (s *pluginConfigStore) Put([]byte) (digest.Digest, error) {
   528  	return digest.Digest(""), errors.New("cannot store config on push")
   529  }
   530  
   531  func (s *pluginConfigStore) Get(d digest.Digest) ([]byte, error) {
   532  	if s.plugin.Config != d {
   533  		return nil, errors.New("plugin not found")
   534  	}
   535  	rwc, err := s.pm.blobStore.Get(d)
   536  	if err != nil {
   537  		return nil, err
   538  	}
   539  	defer rwc.Close()
   540  	return ioutil.ReadAll(rwc)
   541  }
   542  
   543  func (s *pluginConfigStore) RootFSFromConfig(c []byte) (*image.RootFS, error) {
   544  	return configToRootFS(c)
   545  }
   546  
   547  func (s *pluginConfigStore) PlatformFromConfig(c []byte) (*specs.Platform, error) {
   548  	// TODO: LCOW/Plugins. This will need revisiting. For now use the runtime OS
   549  	return &specs.Platform{OS: runtime.GOOS}, nil
   550  }
   551  
   552  type pluginLayerProvider struct {
   553  	pm     *Manager
   554  	plugin *v2.Plugin
   555  }
   556  
   557  func (p *pluginLayerProvider) Get(id layer.ChainID) (distribution.PushLayer, error) {
   558  	rootFS := rootFSFromPlugin(p.plugin.PluginObj.Config.Rootfs)
   559  	var i int
   560  	for i = 1; i <= len(rootFS.DiffIDs); i++ {
   561  		if layer.CreateChainID(rootFS.DiffIDs[:i]) == id {
   562  			break
   563  		}
   564  	}
   565  	if i > len(rootFS.DiffIDs) {
   566  		return nil, errors.New("layer not found")
   567  	}
   568  	return &pluginLayer{
   569  		pm:      p.pm,
   570  		diffIDs: rootFS.DiffIDs[:i],
   571  		blobs:   p.plugin.Blobsums[:i],
   572  	}, nil
   573  }
   574  
   575  type pluginLayer struct {
   576  	pm      *Manager
   577  	diffIDs []layer.DiffID
   578  	blobs   []digest.Digest
   579  }
   580  
   581  func (l *pluginLayer) ChainID() layer.ChainID {
   582  	return layer.CreateChainID(l.diffIDs)
   583  }
   584  
   585  func (l *pluginLayer) DiffID() layer.DiffID {
   586  	return l.diffIDs[len(l.diffIDs)-1]
   587  }
   588  
   589  func (l *pluginLayer) Parent() distribution.PushLayer {
   590  	if len(l.diffIDs) == 1 {
   591  		return nil
   592  	}
   593  	return &pluginLayer{
   594  		pm:      l.pm,
   595  		diffIDs: l.diffIDs[:len(l.diffIDs)-1],
   596  		blobs:   l.blobs[:len(l.diffIDs)-1],
   597  	}
   598  }
   599  
   600  func (l *pluginLayer) Open() (io.ReadCloser, error) {
   601  	return l.pm.blobStore.Get(l.blobs[len(l.diffIDs)-1])
   602  }
   603  
   604  func (l *pluginLayer) Size() (int64, error) {
   605  	return l.pm.blobStore.Size(l.blobs[len(l.diffIDs)-1])
   606  }
   607  
   608  func (l *pluginLayer) MediaType() string {
   609  	return schema2.MediaTypeLayer
   610  }
   611  
   612  func (l *pluginLayer) Release() {
   613  	// Nothing needs to be release, no references held
   614  }
   615  
   616  // Remove deletes plugin's root directory.
   617  func (pm *Manager) Remove(name string, config *types.PluginRmConfig) error {
   618  	p, err := pm.config.Store.GetV2Plugin(name)
   619  	pm.mu.RLock()
   620  	c := pm.cMap[p]
   621  	pm.mu.RUnlock()
   622  
   623  	if err != nil {
   624  		return err
   625  	}
   626  
   627  	if !config.ForceRemove {
   628  		if p.GetRefCount() > 0 {
   629  			return inUseError(p.Name())
   630  		}
   631  		if p.IsEnabled() {
   632  			return enabledError(p.Name())
   633  		}
   634  	}
   635  
   636  	if p.IsEnabled() {
   637  		if err := pm.disable(p, c); err != nil {
   638  			logrus.Errorf("failed to disable plugin '%s': %s", p.Name(), err)
   639  		}
   640  	}
   641  
   642  	defer func() {
   643  		go pm.GC()
   644  	}()
   645  
   646  	id := p.GetID()
   647  	pluginDir := filepath.Join(pm.config.Root, id)
   648  
   649  	if err := mount.RecursiveUnmount(pluginDir); err != nil {
   650  		return errors.Wrap(err, "error unmounting plugin data")
   651  	}
   652  
   653  	if err := atomicRemoveAll(pluginDir); err != nil {
   654  		return err
   655  	}
   656  
   657  	pm.config.Store.Remove(p)
   658  	pm.config.LogPluginEvent(id, name, "remove")
   659  	pm.publisher.Publish(EventRemove{Plugin: p.PluginObj})
   660  	return nil
   661  }
   662  
   663  // Set sets plugin args
   664  func (pm *Manager) Set(name string, args []string) error {
   665  	p, err := pm.config.Store.GetV2Plugin(name)
   666  	if err != nil {
   667  		return err
   668  	}
   669  	if err := p.Set(args); err != nil {
   670  		return err
   671  	}
   672  	return pm.save(p)
   673  }
   674  
   675  // CreateFromContext creates a plugin from the given pluginDir which contains
   676  // both the rootfs and the config.json and a repoName with optional tag.
   677  func (pm *Manager) CreateFromContext(ctx context.Context, tarCtx io.ReadCloser, options *types.PluginCreateOptions) (err error) {
   678  	pm.muGC.RLock()
   679  	defer pm.muGC.RUnlock()
   680  
   681  	ref, err := reference.ParseNormalizedNamed(options.RepoName)
   682  	if err != nil {
   683  		return errors.Wrapf(err, "failed to parse reference %v", options.RepoName)
   684  	}
   685  	if _, ok := ref.(reference.Canonical); ok {
   686  		return errors.Errorf("canonical references are not permitted")
   687  	}
   688  	name := reference.FamiliarString(reference.TagNameOnly(ref))
   689  
   690  	if err := pm.config.Store.validateName(name); err != nil { // fast check, real check is in createPlugin()
   691  		return err
   692  	}
   693  
   694  	tmpRootFSDir, err := ioutil.TempDir(pm.tmpDir(), ".rootfs")
   695  	if err != nil {
   696  		return errors.Wrap(err, "failed to create temp directory")
   697  	}
   698  	defer os.RemoveAll(tmpRootFSDir)
   699  
   700  	var configJSON []byte
   701  	rootFS := splitConfigRootFSFromTar(tarCtx, &configJSON)
   702  
   703  	rootFSBlob, err := pm.blobStore.New()
   704  	if err != nil {
   705  		return err
   706  	}
   707  	defer rootFSBlob.Close()
   708  	gzw := gzip.NewWriter(rootFSBlob)
   709  	layerDigester := digest.Canonical.Digester()
   710  	rootFSReader := io.TeeReader(rootFS, io.MultiWriter(gzw, layerDigester.Hash()))
   711  
   712  	if err := chrootarchive.Untar(rootFSReader, tmpRootFSDir, nil); err != nil {
   713  		return err
   714  	}
   715  	if err := rootFS.Close(); err != nil {
   716  		return err
   717  	}
   718  
   719  	if configJSON == nil {
   720  		return errors.New("config not found")
   721  	}
   722  
   723  	if err := gzw.Close(); err != nil {
   724  		return errors.Wrap(err, "error closing gzip writer")
   725  	}
   726  
   727  	var config types.PluginConfig
   728  	if err := json.Unmarshal(configJSON, &config); err != nil {
   729  		return errors.Wrap(err, "failed to parse config")
   730  	}
   731  
   732  	if err := pm.validateConfig(config); err != nil {
   733  		return err
   734  	}
   735  
   736  	pm.mu.Lock()
   737  	defer pm.mu.Unlock()
   738  
   739  	rootFSBlobsum, err := rootFSBlob.Commit()
   740  	if err != nil {
   741  		return err
   742  	}
   743  	defer func() {
   744  		if err != nil {
   745  			go pm.GC()
   746  		}
   747  	}()
   748  
   749  	config.Rootfs = &types.PluginConfigRootfs{
   750  		Type:    "layers",
   751  		DiffIds: []string{layerDigester.Digest().String()},
   752  	}
   753  
   754  	config.DockerVersion = dockerversion.Version
   755  
   756  	configBlob, err := pm.blobStore.New()
   757  	if err != nil {
   758  		return err
   759  	}
   760  	defer configBlob.Close()
   761  	if err := json.NewEncoder(configBlob).Encode(config); err != nil {
   762  		return errors.Wrap(err, "error encoding json config")
   763  	}
   764  	configBlobsum, err := configBlob.Commit()
   765  	if err != nil {
   766  		return err
   767  	}
   768  
   769  	p, err := pm.createPlugin(name, configBlobsum, []digest.Digest{rootFSBlobsum}, tmpRootFSDir, nil)
   770  	if err != nil {
   771  		return err
   772  	}
   773  	p.PluginObj.PluginReference = name
   774  
   775  	pm.publisher.Publish(EventCreate{Plugin: p.PluginObj})
   776  	pm.config.LogPluginEvent(p.PluginObj.ID, name, "create")
   777  
   778  	return nil
   779  }
   780  
   781  func (pm *Manager) validateConfig(config types.PluginConfig) error {
   782  	return nil // TODO:
   783  }
   784  
   785  func splitConfigRootFSFromTar(in io.ReadCloser, config *[]byte) io.ReadCloser {
   786  	pr, pw := io.Pipe()
   787  	go func() {
   788  		tarReader := tar.NewReader(in)
   789  		tarWriter := tar.NewWriter(pw)
   790  		defer in.Close()
   791  
   792  		hasRootFS := false
   793  
   794  		for {
   795  			hdr, err := tarReader.Next()
   796  			if err == io.EOF {
   797  				if !hasRootFS {
   798  					pw.CloseWithError(errors.Wrap(err, "no rootfs found"))
   799  					return
   800  				}
   801  				// Signals end of archive.
   802  				tarWriter.Close()
   803  				pw.Close()
   804  				return
   805  			}
   806  			if err != nil {
   807  				pw.CloseWithError(errors.Wrap(err, "failed to read from tar"))
   808  				return
   809  			}
   810  
   811  			content := io.Reader(tarReader)
   812  			name := path.Clean(hdr.Name)
   813  			if path.IsAbs(name) {
   814  				name = name[1:]
   815  			}
   816  			if name == configFileName {
   817  				dt, err := ioutil.ReadAll(content)
   818  				if err != nil {
   819  					pw.CloseWithError(errors.Wrapf(err, "failed to read %s", configFileName))
   820  					return
   821  				}
   822  				*config = dt
   823  			}
   824  			if parts := strings.Split(name, "/"); len(parts) != 0 && parts[0] == rootFSFileName {
   825  				hdr.Name = path.Clean(path.Join(parts[1:]...))
   826  				if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(strings.ToLower(hdr.Linkname), rootFSFileName+"/") {
   827  					hdr.Linkname = hdr.Linkname[len(rootFSFileName)+1:]
   828  				}
   829  				if err := tarWriter.WriteHeader(hdr); err != nil {
   830  					pw.CloseWithError(errors.Wrap(err, "error writing tar header"))
   831  					return
   832  				}
   833  				if _, err := pools.Copy(tarWriter, content); err != nil {
   834  					pw.CloseWithError(errors.Wrap(err, "error copying tar data"))
   835  					return
   836  				}
   837  				hasRootFS = true
   838  			} else {
   839  				io.Copy(ioutil.Discard, content)
   840  			}
   841  		}
   842  	}()
   843  	return pr
   844  }
   845  
   846  func atomicRemoveAll(dir string) error {
   847  	renamed := dir + "-removing"
   848  
   849  	err := os.Rename(dir, renamed)
   850  	switch {
   851  	case os.IsNotExist(err), err == nil:
   852  		// even if `dir` doesn't exist, we can still try and remove `renamed`
   853  	case os.IsExist(err):
   854  		// Some previous remove failed, check if the origin dir exists
   855  		if e := system.EnsureRemoveAll(renamed); e != nil {
   856  			return errors.Wrap(err, "rename target already exists and could not be removed")
   857  		}
   858  		if _, err := os.Stat(dir); os.IsNotExist(err) {
   859  			// origin doesn't exist, nothing left to do
   860  			return nil
   861  		}
   862  
   863  		// attempt to rename again
   864  		if err := os.Rename(dir, renamed); err != nil {
   865  			return errors.Wrap(err, "failed to rename dir for atomic removal")
   866  		}
   867  	default:
   868  		return errors.Wrap(err, "failed to rename dir for atomic removal")
   869  	}
   870  
   871  	if err := system.EnsureRemoveAll(renamed); err != nil {
   872  		os.Rename(renamed, dir)
   873  		return err
   874  	}
   875  	return nil
   876  }