github.com/rhatdan/docker@v0.7.7-0.20180119204836-47a0dcbcd20a/plugin/backend_linux.go (about)

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