github.com/rish1988/moby@v25.0.2+incompatible/plugin/backend_linux.go (about)

     1  package plugin // import "github.com/docker/docker/plugin"
     2  
     3  import (
     4  	"archive/tar"
     5  	"bytes"
     6  	"compress/gzip"
     7  	"context"
     8  	"encoding/json"
     9  	"io"
    10  	"net/http"
    11  	"os"
    12  	"path"
    13  	"path/filepath"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/containerd/containerd/content"
    18  	"github.com/containerd/containerd/images"
    19  	"github.com/containerd/containerd/platforms"
    20  	"github.com/containerd/containerd/remotes"
    21  	"github.com/containerd/containerd/remotes/docker"
    22  	"github.com/containerd/log"
    23  	"github.com/distribution/reference"
    24  	"github.com/docker/distribution/manifest/schema2"
    25  	"github.com/docker/docker/api/types"
    26  	"github.com/docker/docker/api/types/backend"
    27  	"github.com/docker/docker/api/types/events"
    28  	"github.com/docker/docker/api/types/filters"
    29  	"github.com/docker/docker/api/types/registry"
    30  	"github.com/docker/docker/dockerversion"
    31  	"github.com/docker/docker/errdefs"
    32  	"github.com/docker/docker/pkg/authorization"
    33  	"github.com/docker/docker/pkg/chrootarchive"
    34  	"github.com/docker/docker/pkg/containerfs"
    35  	"github.com/docker/docker/pkg/pools"
    36  	"github.com/docker/docker/pkg/progress"
    37  	"github.com/docker/docker/pkg/stringid"
    38  	v2 "github.com/docker/docker/plugin/v2"
    39  	"github.com/moby/sys/mount"
    40  	"github.com/opencontainers/go-digest"
    41  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    42  	"github.com/pkg/errors"
    43  )
    44  
    45  var acceptedPluginFilterTags = map[string]bool{
    46  	"enabled":    true,
    47  	"capability": true,
    48  }
    49  
    50  // Disable deactivates a plugin. This means resources (volumes, networks) cant use them.
    51  func (pm *Manager) Disable(refOrID string, config *backend.PluginDisableConfig) error {
    52  	p, err := pm.config.Store.GetV2Plugin(refOrID)
    53  	if err != nil {
    54  		return err
    55  	}
    56  	pm.mu.RLock()
    57  	c := pm.cMap[p]
    58  	pm.mu.RUnlock()
    59  
    60  	if !config.ForceDisable && p.GetRefCount() > 0 {
    61  		return errors.WithStack(inUseError(p.Name()))
    62  	}
    63  
    64  	for _, typ := range p.GetTypes() {
    65  		if typ.Capability == authorization.AuthZApiImplements {
    66  			pm.config.AuthzMiddleware.RemovePlugin(p.Name())
    67  		}
    68  	}
    69  
    70  	if err := pm.disable(p, c); err != nil {
    71  		return err
    72  	}
    73  	pm.publisher.Publish(EventDisable{Plugin: p.PluginObj})
    74  	pm.config.LogPluginEvent(p.GetID(), refOrID, events.ActionDisable)
    75  	return nil
    76  }
    77  
    78  // Enable activates a plugin, which implies that they are ready to be used by containers.
    79  func (pm *Manager) Enable(refOrID string, config *backend.PluginEnableConfig) error {
    80  	p, err := pm.config.Store.GetV2Plugin(refOrID)
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	c := &controller{timeoutInSecs: config.Timeout}
    86  	if err := pm.enable(p, c, false); err != nil {
    87  		return err
    88  	}
    89  	pm.publisher.Publish(EventEnable{Plugin: p.PluginObj})
    90  	pm.config.LogPluginEvent(p.GetID(), refOrID, events.ActionEnable)
    91  	return nil
    92  }
    93  
    94  // Inspect examines a plugin config
    95  func (pm *Manager) Inspect(refOrID string) (tp *types.Plugin, err error) {
    96  	p, err := pm.config.Store.GetV2Plugin(refOrID)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	return &p.PluginObj, nil
   102  }
   103  
   104  func computePrivileges(c types.PluginConfig) types.PluginPrivileges {
   105  	var privileges types.PluginPrivileges
   106  	if c.Network.Type != "null" && c.Network.Type != "bridge" && c.Network.Type != "" {
   107  		privileges = append(privileges, types.PluginPrivilege{
   108  			Name:        "network",
   109  			Description: "permissions to access a network",
   110  			Value:       []string{c.Network.Type},
   111  		})
   112  	}
   113  	if c.IpcHost {
   114  		privileges = append(privileges, types.PluginPrivilege{
   115  			Name:        "host ipc namespace",
   116  			Description: "allow access to host ipc namespace",
   117  			Value:       []string{"true"},
   118  		})
   119  	}
   120  	if c.PidHost {
   121  		privileges = append(privileges, types.PluginPrivilege{
   122  			Name:        "host pid namespace",
   123  			Description: "allow access to host pid namespace",
   124  			Value:       []string{"true"},
   125  		})
   126  	}
   127  	for _, mnt := range c.Mounts {
   128  		if mnt.Source != nil {
   129  			privileges = append(privileges, types.PluginPrivilege{
   130  				Name:        "mount",
   131  				Description: "host path to mount",
   132  				Value:       []string{*mnt.Source},
   133  			})
   134  		}
   135  	}
   136  	for _, device := range c.Linux.Devices {
   137  		if device.Path != nil {
   138  			privileges = append(privileges, types.PluginPrivilege{
   139  				Name:        "device",
   140  				Description: "host device to access",
   141  				Value:       []string{*device.Path},
   142  			})
   143  		}
   144  	}
   145  	if c.Linux.AllowAllDevices {
   146  		privileges = append(privileges, types.PluginPrivilege{
   147  			Name:        "allow-all-devices",
   148  			Description: "allow 'rwm' access to all devices",
   149  			Value:       []string{"true"},
   150  		})
   151  	}
   152  	if len(c.Linux.Capabilities) > 0 {
   153  		privileges = append(privileges, types.PluginPrivilege{
   154  			Name:        "capabilities",
   155  			Description: "list of additional capabilities required",
   156  			Value:       c.Linux.Capabilities,
   157  		})
   158  	}
   159  
   160  	return privileges
   161  }
   162  
   163  // Privileges pulls a plugin config and computes the privileges required to install it.
   164  func (pm *Manager) Privileges(ctx context.Context, ref reference.Named, metaHeader http.Header, authConfig *registry.AuthConfig) (types.PluginPrivileges, error) {
   165  	var (
   166  		config     types.PluginConfig
   167  		configSeen bool
   168  	)
   169  
   170  	h := func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   171  		switch desc.MediaType {
   172  		case schema2.MediaTypeManifest, ocispec.MediaTypeImageManifest:
   173  			data, err := content.ReadBlob(ctx, pm.blobStore, desc)
   174  			if err != nil {
   175  				return nil, errors.Wrapf(err, "error reading image manifest from blob store for %s", ref)
   176  			}
   177  
   178  			var m ocispec.Manifest
   179  			if err := json.Unmarshal(data, &m); err != nil {
   180  				return nil, errors.Wrapf(err, "error unmarshaling image manifest for %s", ref)
   181  			}
   182  			return []ocispec.Descriptor{m.Config}, nil
   183  		case schema2.MediaTypePluginConfig:
   184  			configSeen = true
   185  			data, err := content.ReadBlob(ctx, pm.blobStore, desc)
   186  			if err != nil {
   187  				return nil, errors.Wrapf(err, "error reading plugin config from blob store for %s", ref)
   188  			}
   189  
   190  			if err := json.Unmarshal(data, &config); err != nil {
   191  				return nil, errors.Wrapf(err, "error unmarshaling plugin config for %s", ref)
   192  			}
   193  		}
   194  
   195  		return nil, nil
   196  	}
   197  
   198  	if err := pm.fetch(ctx, ref, authConfig, progress.DiscardOutput(), metaHeader, images.HandlerFunc(h)); err != nil {
   199  		return types.PluginPrivileges{}, nil
   200  	}
   201  
   202  	if !configSeen {
   203  		return types.PluginPrivileges{}, errors.Errorf("did not find plugin config for specified reference %s", ref)
   204  	}
   205  
   206  	return computePrivileges(config), nil
   207  }
   208  
   209  // Upgrade upgrades a plugin
   210  //
   211  // TODO: replace reference package usage with simpler url.Parse semantics
   212  func (pm *Manager) Upgrade(ctx context.Context, ref reference.Named, name string, metaHeader http.Header, authConfig *registry.AuthConfig, privileges types.PluginPrivileges, outStream io.Writer) (err error) {
   213  	p, err := pm.config.Store.GetV2Plugin(name)
   214  	if err != nil {
   215  		return err
   216  	}
   217  
   218  	if p.IsEnabled() {
   219  		return errors.Wrap(enabledError(p.Name()), "plugin must be disabled before upgrading")
   220  	}
   221  
   222  	// revalidate because Pull is public
   223  	if _, err := reference.ParseNormalizedNamed(name); err != nil {
   224  		return errors.Wrapf(errdefs.InvalidParameter(err), "failed to parse %q", name)
   225  	}
   226  
   227  	pm.muGC.RLock()
   228  	defer pm.muGC.RUnlock()
   229  
   230  	tmpRootFSDir, err := os.MkdirTemp(pm.tmpDir(), ".rootfs")
   231  	if err != nil {
   232  		return errors.Wrap(err, "error creating tmp dir for plugin rootfs")
   233  	}
   234  
   235  	var md fetchMeta
   236  
   237  	ctx, cancel := context.WithCancel(ctx)
   238  	out, waitProgress := setupProgressOutput(outStream, cancel)
   239  	defer waitProgress()
   240  
   241  	if err := pm.fetch(ctx, ref, authConfig, out, metaHeader, storeFetchMetadata(&md), childrenHandler(pm.blobStore), applyLayer(pm.blobStore, tmpRootFSDir, out)); err != nil {
   242  		return err
   243  	}
   244  	pm.config.LogPluginEvent(reference.FamiliarString(ref), name, events.ActionPull)
   245  
   246  	if err := validateFetchedMetadata(md); err != nil {
   247  		return err
   248  	}
   249  
   250  	if err := pm.upgradePlugin(p, md.config, md.manifest, md.blobs, tmpRootFSDir, &privileges); err != nil {
   251  		return err
   252  	}
   253  	p.PluginObj.PluginReference = ref.String()
   254  	return nil
   255  }
   256  
   257  // Pull pulls a plugin, check if the correct privileges are provided and install the plugin.
   258  //
   259  // TODO: replace reference package usage with simpler url.Parse semantics
   260  func (pm *Manager) Pull(ctx context.Context, ref reference.Named, name string, metaHeader http.Header, authConfig *registry.AuthConfig, privileges types.PluginPrivileges, outStream io.Writer, opts ...CreateOpt) (err error) {
   261  	pm.muGC.RLock()
   262  	defer pm.muGC.RUnlock()
   263  
   264  	// revalidate because Pull is public
   265  	nameref, err := reference.ParseNormalizedNamed(name)
   266  	if err != nil {
   267  		return errors.Wrapf(errdefs.InvalidParameter(err), "failed to parse %q", name)
   268  	}
   269  	name = reference.FamiliarString(reference.TagNameOnly(nameref))
   270  
   271  	if err := pm.config.Store.validateName(name); err != nil {
   272  		return errdefs.InvalidParameter(err)
   273  	}
   274  
   275  	tmpRootFSDir, err := os.MkdirTemp(pm.tmpDir(), ".rootfs")
   276  	if err != nil {
   277  		return errors.Wrap(errdefs.System(err), "error preparing upgrade")
   278  	}
   279  	defer os.RemoveAll(tmpRootFSDir)
   280  
   281  	var md fetchMeta
   282  
   283  	ctx, cancel := context.WithCancel(ctx)
   284  	out, waitProgress := setupProgressOutput(outStream, cancel)
   285  	defer waitProgress()
   286  
   287  	if err := pm.fetch(ctx, ref, authConfig, out, metaHeader, storeFetchMetadata(&md), childrenHandler(pm.blobStore), applyLayer(pm.blobStore, tmpRootFSDir, out)); err != nil {
   288  		return err
   289  	}
   290  	pm.config.LogPluginEvent(reference.FamiliarString(ref), name, events.ActionPull)
   291  
   292  	if err := validateFetchedMetadata(md); err != nil {
   293  		return err
   294  	}
   295  
   296  	refOpt := func(p *v2.Plugin) {
   297  		p.PluginObj.PluginReference = ref.String()
   298  	}
   299  	optsList := make([]CreateOpt, 0, len(opts)+1)
   300  	optsList = append(optsList, opts...)
   301  	optsList = append(optsList, refOpt)
   302  
   303  	// TODO: tmpRootFSDir is empty but should have layers in it
   304  	p, err := pm.createPlugin(name, md.config, md.manifest, md.blobs, tmpRootFSDir, &privileges, optsList...)
   305  	if err != nil {
   306  		return err
   307  	}
   308  
   309  	pm.publisher.Publish(EventCreate{Plugin: p.PluginObj})
   310  
   311  	return nil
   312  }
   313  
   314  // List displays the list of plugins and associated metadata.
   315  func (pm *Manager) List(pluginFilters filters.Args) ([]types.Plugin, error) {
   316  	if err := pluginFilters.Validate(acceptedPluginFilterTags); err != nil {
   317  		return nil, err
   318  	}
   319  
   320  	enabledOnly := false
   321  	disabledOnly := false
   322  	if pluginFilters.Contains("enabled") {
   323  		enabledFilter, err := pluginFilters.GetBoolOrDefault("enabled", false)
   324  		if err != nil {
   325  			return nil, err
   326  		}
   327  
   328  		if enabledFilter {
   329  			enabledOnly = true
   330  		} else {
   331  			disabledOnly = true
   332  		}
   333  	}
   334  
   335  	plugins := pm.config.Store.GetAll()
   336  	out := make([]types.Plugin, 0, len(plugins))
   337  
   338  next:
   339  	for _, p := range plugins {
   340  		if enabledOnly && !p.PluginObj.Enabled {
   341  			continue
   342  		}
   343  		if disabledOnly && p.PluginObj.Enabled {
   344  			continue
   345  		}
   346  		if pluginFilters.Contains("capability") {
   347  			for _, f := range p.GetTypes() {
   348  				if !pluginFilters.Match("capability", f.Capability) {
   349  					continue next
   350  				}
   351  			}
   352  		}
   353  		out = append(out, p.PluginObj)
   354  	}
   355  	return out, nil
   356  }
   357  
   358  // Push pushes a plugin to the registry.
   359  func (pm *Manager) Push(ctx context.Context, name string, metaHeader http.Header, authConfig *registry.AuthConfig, outStream io.Writer) error {
   360  	p, err := pm.config.Store.GetV2Plugin(name)
   361  	if err != nil {
   362  		return err
   363  	}
   364  
   365  	ref, err := reference.ParseNormalizedNamed(p.Name())
   366  	if err != nil {
   367  		return errors.Wrapf(err, "plugin has invalid name %v for push", p.Name())
   368  	}
   369  
   370  	statusTracker := docker.NewInMemoryTracker()
   371  
   372  	resolver, err := pm.newResolver(ctx, statusTracker, authConfig, metaHeader, false)
   373  	if err != nil {
   374  		return err
   375  	}
   376  
   377  	pusher, err := resolver.Pusher(ctx, ref.String())
   378  	if err != nil {
   379  		return errors.Wrap(err, "error creating plugin pusher")
   380  	}
   381  
   382  	pj := newPushJobs(statusTracker)
   383  
   384  	ctx, cancel := context.WithCancel(ctx)
   385  	out, waitProgress := setupProgressOutput(outStream, cancel)
   386  	defer waitProgress()
   387  
   388  	progressHandler := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
   389  		log.G(ctx).WithField("mediaType", desc.MediaType).WithField("digest", desc.Digest.String()).Debug("Preparing to push plugin layer")
   390  		id := stringid.TruncateID(desc.Digest.String())
   391  		pj.add(remotes.MakeRefKey(ctx, desc), id)
   392  		progress.Update(out, id, "Preparing")
   393  		return nil, nil
   394  	})
   395  
   396  	desc, err := pm.getManifestDescriptor(ctx, p)
   397  	if err != nil {
   398  		return errors.Wrap(err, "error reading plugin manifest")
   399  	}
   400  
   401  	progress.Messagef(out, "", "The push refers to repository [%s]", reference.FamiliarName(ref))
   402  
   403  	// TODO: If a layer already exists on the registry, the progress output just says "Preparing"
   404  	go func() {
   405  		timer := time.NewTimer(100 * time.Millisecond)
   406  		defer timer.Stop()
   407  		if !timer.Stop() {
   408  			<-timer.C
   409  		}
   410  		var statuses []contentStatus
   411  		for {
   412  			timer.Reset(100 * time.Millisecond)
   413  			select {
   414  			case <-ctx.Done():
   415  				return
   416  			case <-timer.C:
   417  				statuses = pj.status()
   418  			}
   419  
   420  			for _, s := range statuses {
   421  				out.WriteProgress(progress.Progress{ID: s.Ref, Current: s.Offset, Total: s.Total, Action: s.Status, LastUpdate: s.Offset == s.Total})
   422  			}
   423  		}
   424  	}()
   425  
   426  	// Make sure we can authenticate the request since the auth scope for plugin repos is different than a normal repo.
   427  	ctx = docker.WithScope(ctx, scope(ref, true))
   428  	if err := remotes.PushContent(ctx, pusher, desc, pm.blobStore, nil, nil, func(h images.Handler) images.Handler {
   429  		return images.Handlers(progressHandler, h)
   430  	}); err != nil {
   431  		// Try fallback to http.
   432  		// This is needed because the containerd pusher will only attempt the first registry config we pass, which would
   433  		// typically be https.
   434  		// If there are no http-only host configs found we'll error out anyway.
   435  		resolver, _ := pm.newResolver(ctx, statusTracker, authConfig, metaHeader, true)
   436  		if resolver != nil {
   437  			pusher, _ := resolver.Pusher(ctx, ref.String())
   438  			if pusher != nil {
   439  				log.G(ctx).WithField("ref", ref).Debug("Re-attmpting push with http-fallback")
   440  				err2 := remotes.PushContent(ctx, pusher, desc, pm.blobStore, nil, nil, func(h images.Handler) images.Handler {
   441  					return images.Handlers(progressHandler, h)
   442  				})
   443  				if err2 == nil {
   444  					err = nil
   445  				} else {
   446  					log.G(ctx).WithError(err2).WithField("ref", ref).Debug("Error while attempting push with http-fallback")
   447  				}
   448  			}
   449  		}
   450  		if err != nil {
   451  			return errors.Wrap(err, "error pushing plugin")
   452  		}
   453  	}
   454  
   455  	// For blobs that already exist in the registry we need to make sure to update the progress otherwise it will just say "pending"
   456  	// TODO: How to check if the layer already exists? Is it worth it?
   457  	for _, j := range pj.jobs {
   458  		progress.Update(out, pj.names[j], "Upload complete")
   459  	}
   460  
   461  	// Signal the client for content trust verification
   462  	progress.Aux(out, types.PushResult{Tag: ref.(reference.Tagged).Tag(), Digest: desc.Digest.String(), Size: int(desc.Size)})
   463  
   464  	return nil
   465  }
   466  
   467  // manifest wraps an OCI manifest, because...
   468  // Historically the registry does not support plugins unless the media type on the manifest is specifically schema2.MediaTypeManifest
   469  // So the OCI manifest media type is not supported.
   470  // Additionally, there is extra validation for the docker schema2 manifest than there is a mediatype set on the manifest itself
   471  // even though this is set on the descriptor
   472  // The OCI types do not have this field.
   473  type manifest struct {
   474  	ocispec.Manifest
   475  	MediaType string `json:"mediaType,omitempty"`
   476  }
   477  
   478  func buildManifest(ctx context.Context, s content.Manager, config digest.Digest, layers []digest.Digest) (manifest, error) {
   479  	var m manifest
   480  	m.MediaType = images.MediaTypeDockerSchema2Manifest
   481  	m.SchemaVersion = 2
   482  
   483  	configInfo, err := s.Info(ctx, config)
   484  	if err != nil {
   485  		return m, errors.Wrapf(err, "error reading plugin config content for digest %s", config)
   486  	}
   487  	m.Config = ocispec.Descriptor{
   488  		MediaType: mediaTypePluginConfig,
   489  		Size:      configInfo.Size,
   490  		Digest:    configInfo.Digest,
   491  	}
   492  
   493  	for _, l := range layers {
   494  		info, err := s.Info(ctx, l)
   495  		if err != nil {
   496  			return m, errors.Wrapf(err, "error fetching info for content digest %s", l)
   497  		}
   498  		m.Layers = append(m.Layers, ocispec.Descriptor{
   499  			MediaType: images.MediaTypeDockerSchema2LayerGzip, // TODO: This is assuming everything is a gzip compressed layer, but that may not be true.
   500  			Digest:    l,
   501  			Size:      info.Size,
   502  		})
   503  	}
   504  	return m, nil
   505  }
   506  
   507  // getManifestDescriptor gets the OCI descriptor for a manifest
   508  // It will generate a manifest if one does not exist
   509  func (pm *Manager) getManifestDescriptor(ctx context.Context, p *v2.Plugin) (ocispec.Descriptor, error) {
   510  	logger := log.G(ctx).WithField("plugin", p.Name()).WithField("digest", p.Manifest)
   511  	if p.Manifest != "" {
   512  		info, err := pm.blobStore.Info(ctx, p.Manifest)
   513  		if err == nil {
   514  			desc := ocispec.Descriptor{
   515  				Size:      info.Size,
   516  				Digest:    info.Digest,
   517  				MediaType: images.MediaTypeDockerSchema2Manifest,
   518  			}
   519  			return desc, nil
   520  		}
   521  		logger.WithError(err).Debug("Could not find plugin manifest in content store")
   522  	} else {
   523  		logger.Info("Plugin does not have manifest digest")
   524  	}
   525  	logger.Info("Building a new plugin manifest")
   526  
   527  	manifest, err := buildManifest(ctx, pm.blobStore, p.Config, p.Blobsums)
   528  	if err != nil {
   529  		return ocispec.Descriptor{}, err
   530  	}
   531  
   532  	desc, err := writeManifest(ctx, pm.blobStore, &manifest)
   533  	if err != nil {
   534  		return desc, err
   535  	}
   536  
   537  	if err := pm.save(p); err != nil {
   538  		logger.WithError(err).Error("Could not save plugin with manifest digest")
   539  	}
   540  	return desc, nil
   541  }
   542  
   543  func writeManifest(ctx context.Context, cs content.Store, m *manifest) (ocispec.Descriptor, error) {
   544  	platform := platforms.DefaultSpec()
   545  	desc := ocispec.Descriptor{
   546  		MediaType: images.MediaTypeDockerSchema2Manifest,
   547  		Platform:  &platform,
   548  	}
   549  	data, err := json.Marshal(m)
   550  	if err != nil {
   551  		return desc, errors.Wrap(err, "error encoding manifest")
   552  	}
   553  	desc.Digest = digest.FromBytes(data)
   554  	desc.Size = int64(len(data))
   555  
   556  	if err := content.WriteBlob(ctx, cs, remotes.MakeRefKey(ctx, desc), bytes.NewReader(data), desc); err != nil {
   557  		return desc, errors.Wrap(err, "error writing plugin manifest")
   558  	}
   559  	return desc, nil
   560  }
   561  
   562  // Remove deletes plugin's root directory.
   563  func (pm *Manager) Remove(name string, config *backend.PluginRmConfig) error {
   564  	p, err := pm.config.Store.GetV2Plugin(name)
   565  	pm.mu.RLock()
   566  	c := pm.cMap[p]
   567  	pm.mu.RUnlock()
   568  
   569  	if err != nil {
   570  		return err
   571  	}
   572  
   573  	if !config.ForceRemove {
   574  		if p.GetRefCount() > 0 {
   575  			return inUseError(p.Name())
   576  		}
   577  		if p.IsEnabled() {
   578  			return enabledError(p.Name())
   579  		}
   580  	}
   581  
   582  	if p.IsEnabled() {
   583  		if err := pm.disable(p, c); err != nil {
   584  			log.G(context.TODO()).Errorf("failed to disable plugin '%s': %s", p.Name(), err)
   585  		}
   586  	}
   587  
   588  	defer func() {
   589  		go pm.GC()
   590  	}()
   591  
   592  	id := p.GetID()
   593  	pluginDir := filepath.Join(pm.config.Root, id)
   594  
   595  	if err := mount.RecursiveUnmount(pluginDir); err != nil {
   596  		return errors.Wrap(err, "error unmounting plugin data")
   597  	}
   598  
   599  	if err := atomicRemoveAll(pluginDir); err != nil {
   600  		return err
   601  	}
   602  
   603  	pm.config.Store.Remove(p)
   604  	pm.config.LogPluginEvent(id, name, events.ActionRemove)
   605  	pm.publisher.Publish(EventRemove{Plugin: p.PluginObj})
   606  	return nil
   607  }
   608  
   609  // Set sets plugin args
   610  func (pm *Manager) Set(name string, args []string) error {
   611  	p, err := pm.config.Store.GetV2Plugin(name)
   612  	if err != nil {
   613  		return err
   614  	}
   615  	if err := p.Set(args); err != nil {
   616  		return err
   617  	}
   618  	return pm.save(p)
   619  }
   620  
   621  // CreateFromContext creates a plugin from the given pluginDir which contains
   622  // both the rootfs and the config.json and a repoName with optional tag.
   623  func (pm *Manager) CreateFromContext(ctx context.Context, tarCtx io.ReadCloser, options *types.PluginCreateOptions) (err error) {
   624  	pm.muGC.RLock()
   625  	defer pm.muGC.RUnlock()
   626  
   627  	ref, err := reference.ParseNormalizedNamed(options.RepoName)
   628  	if err != nil {
   629  		return errors.Wrapf(err, "failed to parse reference %v", options.RepoName)
   630  	}
   631  	if _, ok := ref.(reference.Canonical); ok {
   632  		return errors.Errorf("canonical references are not permitted")
   633  	}
   634  	name := reference.FamiliarString(reference.TagNameOnly(ref))
   635  
   636  	if err := pm.config.Store.validateName(name); err != nil { // fast check, real check is in createPlugin()
   637  		return err
   638  	}
   639  
   640  	tmpRootFSDir, err := os.MkdirTemp(pm.tmpDir(), ".rootfs")
   641  	if err != nil {
   642  		return errors.Wrap(err, "failed to create temp directory")
   643  	}
   644  	defer os.RemoveAll(tmpRootFSDir)
   645  
   646  	var configJSON []byte
   647  	rootFS := splitConfigRootFSFromTar(tarCtx, &configJSON)
   648  
   649  	rootFSBlob, err := pm.blobStore.Writer(ctx, content.WithRef(name))
   650  	if err != nil {
   651  		return err
   652  	}
   653  	defer rootFSBlob.Close()
   654  
   655  	gzw := gzip.NewWriter(rootFSBlob)
   656  	rootFSReader := io.TeeReader(rootFS, gzw)
   657  
   658  	if err := chrootarchive.Untar(rootFSReader, tmpRootFSDir, nil); err != nil {
   659  		return err
   660  	}
   661  	if err := rootFS.Close(); err != nil {
   662  		return err
   663  	}
   664  
   665  	if configJSON == nil {
   666  		return errors.New("config not found")
   667  	}
   668  
   669  	if err := gzw.Close(); err != nil {
   670  		return errors.Wrap(err, "error closing gzip writer")
   671  	}
   672  
   673  	var config types.PluginConfig
   674  	if err := json.Unmarshal(configJSON, &config); err != nil {
   675  		return errors.Wrap(err, "failed to parse config")
   676  	}
   677  
   678  	if err := pm.validateConfig(config); err != nil {
   679  		return err
   680  	}
   681  
   682  	pm.mu.Lock()
   683  	defer pm.mu.Unlock()
   684  
   685  	if err := rootFSBlob.Commit(ctx, 0, ""); err != nil {
   686  		return err
   687  	}
   688  	defer func() {
   689  		if err != nil {
   690  			go pm.GC()
   691  		}
   692  	}()
   693  
   694  	config.Rootfs = &types.PluginConfigRootfs{
   695  		Type:    "layers",
   696  		DiffIds: []string{rootFSBlob.Digest().String()},
   697  	}
   698  
   699  	config.DockerVersion = dockerversion.Version
   700  
   701  	configBlob, err := pm.blobStore.Writer(ctx, content.WithRef(name+"-config.json"))
   702  	if err != nil {
   703  		return err
   704  	}
   705  	defer configBlob.Close()
   706  	if err := json.NewEncoder(configBlob).Encode(config); err != nil {
   707  		return errors.Wrap(err, "error encoding json config")
   708  	}
   709  	if err := configBlob.Commit(ctx, 0, ""); err != nil {
   710  		return err
   711  	}
   712  
   713  	configDigest := configBlob.Digest()
   714  	layers := []digest.Digest{rootFSBlob.Digest()}
   715  
   716  	manifest, err := buildManifest(ctx, pm.blobStore, configDigest, layers)
   717  	if err != nil {
   718  		return err
   719  	}
   720  	desc, err := writeManifest(ctx, pm.blobStore, &manifest)
   721  	if err != nil {
   722  		return
   723  	}
   724  
   725  	p, err := pm.createPlugin(name, configDigest, desc.Digest, layers, tmpRootFSDir, nil)
   726  	if err != nil {
   727  		return err
   728  	}
   729  	p.PluginObj.PluginReference = name
   730  
   731  	pm.publisher.Publish(EventCreate{Plugin: p.PluginObj})
   732  	pm.config.LogPluginEvent(p.PluginObj.ID, name, events.ActionCreate)
   733  
   734  	return nil
   735  }
   736  
   737  func (pm *Manager) validateConfig(config types.PluginConfig) error {
   738  	return nil // TODO:
   739  }
   740  
   741  func splitConfigRootFSFromTar(in io.ReadCloser, config *[]byte) io.ReadCloser {
   742  	pr, pw := io.Pipe()
   743  	go func() {
   744  		tarReader := tar.NewReader(in)
   745  		tarWriter := tar.NewWriter(pw)
   746  		defer in.Close()
   747  
   748  		hasRootFS := false
   749  
   750  		for {
   751  			hdr, err := tarReader.Next()
   752  			if err == io.EOF {
   753  				if !hasRootFS {
   754  					pw.CloseWithError(errors.Wrap(err, "no rootfs found"))
   755  					return
   756  				}
   757  				// Signals end of archive.
   758  				tarWriter.Close()
   759  				pw.Close()
   760  				return
   761  			}
   762  			if err != nil {
   763  				pw.CloseWithError(errors.Wrap(err, "failed to read from tar"))
   764  				return
   765  			}
   766  
   767  			content := io.Reader(tarReader)
   768  			name := path.Clean(hdr.Name)
   769  			if path.IsAbs(name) {
   770  				name = name[1:]
   771  			}
   772  			if name == configFileName {
   773  				dt, err := io.ReadAll(content)
   774  				if err != nil {
   775  					pw.CloseWithError(errors.Wrapf(err, "failed to read %s", configFileName))
   776  					return
   777  				}
   778  				*config = dt
   779  			}
   780  			if parts := strings.Split(name, "/"); len(parts) != 0 && parts[0] == rootFSFileName {
   781  				hdr.Name = path.Clean(path.Join(parts[1:]...))
   782  				if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(strings.ToLower(hdr.Linkname), rootFSFileName+"/") {
   783  					hdr.Linkname = hdr.Linkname[len(rootFSFileName)+1:]
   784  				}
   785  				if err := tarWriter.WriteHeader(hdr); err != nil {
   786  					pw.CloseWithError(errors.Wrap(err, "error writing tar header"))
   787  					return
   788  				}
   789  				if _, err := pools.Copy(tarWriter, content); err != nil {
   790  					pw.CloseWithError(errors.Wrap(err, "error copying tar data"))
   791  					return
   792  				}
   793  				hasRootFS = true
   794  			} else {
   795  				io.Copy(io.Discard, content)
   796  			}
   797  		}
   798  	}()
   799  	return pr
   800  }
   801  
   802  func atomicRemoveAll(dir string) error {
   803  	renamed := dir + "-removing"
   804  
   805  	err := os.Rename(dir, renamed)
   806  	switch {
   807  	case os.IsNotExist(err), err == nil:
   808  		// even if `dir` doesn't exist, we can still try and remove `renamed`
   809  	case os.IsExist(err):
   810  		// Some previous remove failed, check if the origin dir exists
   811  		if e := containerfs.EnsureRemoveAll(renamed); e != nil {
   812  			return errors.Wrap(err, "rename target already exists and could not be removed")
   813  		}
   814  		if _, err := os.Stat(dir); os.IsNotExist(err) {
   815  			// origin doesn't exist, nothing left to do
   816  			return nil
   817  		}
   818  
   819  		// attempt to rename again
   820  		if err := os.Rename(dir, renamed); err != nil {
   821  			return errors.Wrap(err, "failed to rename dir for atomic removal")
   822  		}
   823  	default:
   824  		return errors.Wrap(err, "failed to rename dir for atomic removal")
   825  	}
   826  
   827  	if err := containerfs.EnsureRemoveAll(renamed); err != nil {
   828  		os.Rename(renamed, dir)
   829  		return err
   830  	}
   831  	return nil
   832  }