github.com/samwhited/moby@v1.13.1/plugin/backend_linux.go (about)

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