github.com/zhuohuang-hust/src-cbuild@v0.0.0-20230105071821-c7aab3e7c840/plugin/v2/plugin.go (about)

     1  package v2
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  	"sync"
    10  
    11  	"github.com/docker/docker/api/types"
    12  	"github.com/docker/docker/oci"
    13  	"github.com/docker/docker/pkg/plugins"
    14  	"github.com/docker/docker/pkg/system"
    15  	specs "github.com/opencontainers/runtime-spec/specs-go"
    16  )
    17  
    18  // Plugin represents an individual plugin.
    19  type Plugin struct {
    20  	sync.RWMutex
    21  	PluginObj         types.Plugin    `json:"plugin"`
    22  	PClient           *plugins.Client `json:"-"`
    23  	RuntimeSourcePath string          `json:"-"`
    24  	RefCount          int             `json:"-"`
    25  	Restart           bool            `json:"-"`
    26  	ExitChan          chan bool       `json:"-"`
    27  	LibRoot           string          `json:"-"`
    28  	TimeoutInSecs     int             `json:"-"`
    29  }
    30  
    31  const defaultPluginRuntimeDestination = "/run/docker/plugins"
    32  
    33  // ErrInadequateCapability indicates that the plugin did not have the requested capability.
    34  type ErrInadequateCapability struct {
    35  	cap string
    36  }
    37  
    38  func (e ErrInadequateCapability) Error() string {
    39  	return fmt.Sprintf("plugin does not provide %q capability", e.cap)
    40  }
    41  
    42  func newPluginObj(name, id, tag string) types.Plugin {
    43  	return types.Plugin{Name: name, ID: id, Tag: tag}
    44  }
    45  
    46  // NewPlugin creates a plugin.
    47  func NewPlugin(name, id, runRoot, libRoot, tag string) *Plugin {
    48  	return &Plugin{
    49  		PluginObj:         newPluginObj(name, id, tag),
    50  		RuntimeSourcePath: filepath.Join(runRoot, id),
    51  		LibRoot:           libRoot,
    52  	}
    53  }
    54  
    55  // Client returns the plugin client.
    56  func (p *Plugin) Client() *plugins.Client {
    57  	return p.PClient
    58  }
    59  
    60  // IsV1 returns true for V1 plugins and false otherwise.
    61  func (p *Plugin) IsV1() bool {
    62  	return false
    63  }
    64  
    65  // Name returns the plugin name.
    66  func (p *Plugin) Name() string {
    67  	name := p.PluginObj.Name
    68  	if len(p.PluginObj.Tag) > 0 {
    69  		// TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these
    70  		name += ":" + p.PluginObj.Tag
    71  	}
    72  	return name
    73  }
    74  
    75  // FilterByCap query the plugin for a given capability.
    76  func (p *Plugin) FilterByCap(capability string) (*Plugin, error) {
    77  	capability = strings.ToLower(capability)
    78  	for _, typ := range p.PluginObj.Config.Interface.Types {
    79  		if typ.Capability == capability && typ.Prefix == "docker" {
    80  			return p, nil
    81  		}
    82  	}
    83  	return nil, ErrInadequateCapability{capability}
    84  }
    85  
    86  // RemoveFromDisk deletes the plugin's runtime files from disk.
    87  func (p *Plugin) RemoveFromDisk() error {
    88  	return os.RemoveAll(p.RuntimeSourcePath)
    89  }
    90  
    91  // InitPlugin populates the plugin object from the plugin config file.
    92  func (p *Plugin) InitPlugin() error {
    93  	dt, err := os.Open(filepath.Join(p.LibRoot, p.PluginObj.ID, "config.json"))
    94  	if err != nil {
    95  		return err
    96  	}
    97  	err = json.NewDecoder(dt).Decode(&p.PluginObj.Config)
    98  	dt.Close()
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	p.PluginObj.Settings.Mounts = make([]types.PluginMount, len(p.PluginObj.Config.Mounts))
   104  	for i, mount := range p.PluginObj.Config.Mounts {
   105  		p.PluginObj.Settings.Mounts[i] = mount
   106  	}
   107  	p.PluginObj.Settings.Env = make([]string, 0, len(p.PluginObj.Config.Env))
   108  	p.PluginObj.Settings.Devices = make([]types.PluginDevice, 0, len(p.PluginObj.Config.Linux.Devices))
   109  	copy(p.PluginObj.Settings.Devices, p.PluginObj.Config.Linux.Devices)
   110  	for _, env := range p.PluginObj.Config.Env {
   111  		if env.Value != nil {
   112  			p.PluginObj.Settings.Env = append(p.PluginObj.Settings.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value))
   113  		}
   114  	}
   115  	copy(p.PluginObj.Settings.Args, p.PluginObj.Config.Args.Value)
   116  
   117  	return p.writeSettings()
   118  }
   119  
   120  func (p *Plugin) writeSettings() error {
   121  	f, err := os.Create(filepath.Join(p.LibRoot, p.PluginObj.ID, "plugin-settings.json"))
   122  	if err != nil {
   123  		return err
   124  	}
   125  	err = json.NewEncoder(f).Encode(&p.PluginObj.Settings)
   126  	f.Close()
   127  	return err
   128  }
   129  
   130  // Set is used to pass arguments to the plugin.
   131  func (p *Plugin) Set(args []string) error {
   132  	p.Lock()
   133  	defer p.Unlock()
   134  
   135  	if p.PluginObj.Enabled {
   136  		return fmt.Errorf("cannot set on an active plugin, disable plugin before setting")
   137  	}
   138  
   139  	sets, err := newSettables(args)
   140  	if err != nil {
   141  		return err
   142  	}
   143  
   144  	// TODO(vieux): lots of code duplication here, needs to be refactored.
   145  
   146  next:
   147  	for _, s := range sets {
   148  		// range over all the envs in the config
   149  		for _, env := range p.PluginObj.Config.Env {
   150  			// found the env in the config
   151  			if env.Name == s.name {
   152  				// is it settable ?
   153  				if ok, err := s.isSettable(allowedSettableFieldsEnv, env.Settable); err != nil {
   154  					return err
   155  				} else if !ok {
   156  					return fmt.Errorf("%q is not settable", s.prettyName())
   157  				}
   158  				// is it, so lets update the settings in memory
   159  				updateSettingsEnv(&p.PluginObj.Settings.Env, &s)
   160  				continue next
   161  			}
   162  		}
   163  
   164  		// range over all the mounts in the config
   165  		for _, mount := range p.PluginObj.Config.Mounts {
   166  			// found the mount in the config
   167  			if mount.Name == s.name {
   168  				// is it settable ?
   169  				if ok, err := s.isSettable(allowedSettableFieldsMounts, mount.Settable); err != nil {
   170  					return err
   171  				} else if !ok {
   172  					return fmt.Errorf("%q is not settable", s.prettyName())
   173  				}
   174  
   175  				// it is, so lets update the settings in memory
   176  				*mount.Source = s.value
   177  				continue next
   178  			}
   179  		}
   180  
   181  		// range over all the devices in the config
   182  		for _, device := range p.PluginObj.Config.Linux.Devices {
   183  			// found the device in the config
   184  			if device.Name == s.name {
   185  				// is it settable ?
   186  				if ok, err := s.isSettable(allowedSettableFieldsDevices, device.Settable); err != nil {
   187  					return err
   188  				} else if !ok {
   189  					return fmt.Errorf("%q is not settable", s.prettyName())
   190  				}
   191  
   192  				// it is, so lets update the settings in memory
   193  				*device.Path = s.value
   194  				continue next
   195  			}
   196  		}
   197  
   198  		// found the name in the config
   199  		if p.PluginObj.Config.Args.Name == s.name {
   200  			// is it settable ?
   201  			if ok, err := s.isSettable(allowedSettableFieldsArgs, p.PluginObj.Config.Args.Settable); err != nil {
   202  				return err
   203  			} else if !ok {
   204  				return fmt.Errorf("%q is not settable", s.prettyName())
   205  			}
   206  
   207  			// it is, so lets update the settings in memory
   208  			p.PluginObj.Settings.Args = strings.Split(s.value, " ")
   209  			continue next
   210  		}
   211  
   212  		return fmt.Errorf("setting %q not found in the plugin configuration", s.name)
   213  	}
   214  
   215  	// update the settings on disk
   216  	return p.writeSettings()
   217  }
   218  
   219  // IsEnabled returns the active state of the plugin.
   220  func (p *Plugin) IsEnabled() bool {
   221  	p.RLock()
   222  	defer p.RUnlock()
   223  
   224  	return p.PluginObj.Enabled
   225  }
   226  
   227  // GetID returns the plugin's ID.
   228  func (p *Plugin) GetID() string {
   229  	p.RLock()
   230  	defer p.RUnlock()
   231  
   232  	return p.PluginObj.ID
   233  }
   234  
   235  // GetSocket returns the plugin socket.
   236  func (p *Plugin) GetSocket() string {
   237  	p.RLock()
   238  	defer p.RUnlock()
   239  
   240  	return p.PluginObj.Config.Interface.Socket
   241  }
   242  
   243  // GetTypes returns the interface types of a plugin.
   244  func (p *Plugin) GetTypes() []types.PluginInterfaceType {
   245  	p.RLock()
   246  	defer p.RUnlock()
   247  
   248  	return p.PluginObj.Config.Interface.Types
   249  }
   250  
   251  // InitSpec creates an OCI spec from the plugin's config.
   252  func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
   253  	rootfs := filepath.Join(libRoot, p.PluginObj.ID, "rootfs")
   254  	s.Root = specs.Root{
   255  		Path:     rootfs,
   256  		Readonly: false, // TODO: all plugins should be readonly? settable in config?
   257  	}
   258  
   259  	userMounts := make(map[string]struct{}, len(p.PluginObj.Config.Mounts))
   260  	for _, m := range p.PluginObj.Config.Mounts {
   261  		userMounts[m.Destination] = struct{}{}
   262  	}
   263  
   264  	mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{
   265  		Source:      &p.RuntimeSourcePath,
   266  		Destination: defaultPluginRuntimeDestination,
   267  		Type:        "bind",
   268  		Options:     []string{"rbind", "rshared"},
   269  	})
   270  
   271  	if p.PluginObj.Config.Network.Type != "" {
   272  		// TODO: if net == bridge, use libnetwork controller to create a new plugin-specific bridge, bind mount /etc/hosts and /etc/resolv.conf look at the docker code (allocateNetwork, initialize)
   273  		if p.PluginObj.Config.Network.Type == "host" {
   274  			oci.RemoveNamespace(&s, specs.NamespaceType("network"))
   275  		}
   276  		etcHosts := "/etc/hosts"
   277  		resolvConf := "/etc/resolv.conf"
   278  		mounts = append(mounts,
   279  			types.PluginMount{
   280  				Source:      &etcHosts,
   281  				Destination: etcHosts,
   282  				Type:        "bind",
   283  				Options:     []string{"rbind", "ro"},
   284  			},
   285  			types.PluginMount{
   286  				Source:      &resolvConf,
   287  				Destination: resolvConf,
   288  				Type:        "bind",
   289  				Options:     []string{"rbind", "ro"},
   290  			})
   291  	}
   292  
   293  	for _, mount := range mounts {
   294  		m := specs.Mount{
   295  			Destination: mount.Destination,
   296  			Type:        mount.Type,
   297  			Options:     mount.Options,
   298  		}
   299  		// TODO: if nil, then it's required and user didn't set it
   300  		if mount.Source != nil {
   301  			m.Source = *mount.Source
   302  		}
   303  		if m.Source != "" && m.Type == "bind" {
   304  			fi, err := os.Lstat(filepath.Join(rootfs, m.Destination)) // TODO: followsymlinks
   305  			if err != nil {
   306  				return nil, err
   307  			}
   308  			if fi.IsDir() {
   309  				if err := os.MkdirAll(m.Source, 0700); err != nil {
   310  					return nil, err
   311  				}
   312  			}
   313  		}
   314  		s.Mounts = append(s.Mounts, m)
   315  	}
   316  
   317  	for i, m := range s.Mounts {
   318  		if strings.HasPrefix(m.Destination, "/dev/") {
   319  			if _, ok := userMounts[m.Destination]; ok {
   320  				s.Mounts = append(s.Mounts[:i], s.Mounts[i+1:]...)
   321  			}
   322  		}
   323  	}
   324  
   325  	if p.PluginObj.Config.Linux.DeviceCreation {
   326  		rwm := "rwm"
   327  		s.Linux.Resources.Devices = []specs.DeviceCgroup{{Allow: true, Access: &rwm}}
   328  	}
   329  	for _, dev := range p.PluginObj.Config.Linux.Devices {
   330  		path := *dev.Path
   331  		d, dPermissions, err := oci.DevicesFromPath(path, path, "rwm")
   332  		if err != nil {
   333  			return nil, err
   334  		}
   335  		s.Linux.Devices = append(s.Linux.Devices, d...)
   336  		s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, dPermissions...)
   337  	}
   338  
   339  	envs := make([]string, 1, len(p.PluginObj.Settings.Env)+1)
   340  	envs[0] = "PATH=" + system.DefaultPathEnv
   341  	envs = append(envs, p.PluginObj.Settings.Env...)
   342  
   343  	args := append(p.PluginObj.Config.Entrypoint, p.PluginObj.Settings.Args...)
   344  	cwd := p.PluginObj.Config.Workdir
   345  	if len(cwd) == 0 {
   346  		cwd = "/"
   347  	}
   348  	s.Process.Terminal = false
   349  	s.Process.Args = args
   350  	s.Process.Cwd = cwd
   351  	s.Process.Env = envs
   352  
   353  	s.Process.Capabilities = append(s.Process.Capabilities, p.PluginObj.Config.Linux.Capabilities...)
   354  
   355  	return &s, nil
   356  }