github.com/kim0/docker@v0.6.2-0.20161130212042-4addda3f07e7/plugin/v2/plugin.go (about)

     1  package v2
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  	"sync"
    11  
    12  	"github.com/docker/docker/api/types"
    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  }
    28  
    29  const defaultPluginRuntimeDestination = "/run/docker/plugins"
    30  
    31  // ErrInadequateCapability indicates that the plugin did not have the requested capability.
    32  type ErrInadequateCapability struct {
    33  	cap string
    34  }
    35  
    36  func (e ErrInadequateCapability) Error() string {
    37  	return fmt.Sprintf("plugin does not provide %q capability", e.cap)
    38  }
    39  
    40  func newPluginObj(name, id, tag string) types.Plugin {
    41  	return types.Plugin{Name: name, ID: id, Tag: tag}
    42  }
    43  
    44  // NewPlugin creates a plugin.
    45  func NewPlugin(name, id, runRoot, tag string) *Plugin {
    46  	return &Plugin{
    47  		PluginObj:         newPluginObj(name, id, tag),
    48  		RuntimeSourcePath: filepath.Join(runRoot, id),
    49  	}
    50  }
    51  
    52  // Client returns the plugin client.
    53  func (p *Plugin) Client() *plugins.Client {
    54  	return p.PClient
    55  }
    56  
    57  // IsV1 returns true for V1 plugins and false otherwise.
    58  func (p *Plugin) IsV1() bool {
    59  	return false
    60  }
    61  
    62  // Name returns the plugin name.
    63  func (p *Plugin) Name() string {
    64  	name := p.PluginObj.Name
    65  	if len(p.PluginObj.Tag) > 0 {
    66  		// TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these
    67  		name += ":" + p.PluginObj.Tag
    68  	}
    69  	return name
    70  }
    71  
    72  // FilterByCap query the plugin for a given capability.
    73  func (p *Plugin) FilterByCap(capability string) (*Plugin, error) {
    74  	capability = strings.ToLower(capability)
    75  	for _, typ := range p.PluginObj.Manifest.Interface.Types {
    76  		if typ.Capability == capability && typ.Prefix == "docker" {
    77  			return p, nil
    78  		}
    79  	}
    80  	return nil, ErrInadequateCapability{capability}
    81  }
    82  
    83  // RemoveFromDisk deletes the plugin's runtime files from disk.
    84  func (p *Plugin) RemoveFromDisk() error {
    85  	return os.RemoveAll(p.RuntimeSourcePath)
    86  }
    87  
    88  // InitPlugin populates the plugin object from the plugin manifest file.
    89  func (p *Plugin) InitPlugin(libRoot string) error {
    90  	dt, err := os.Open(filepath.Join(libRoot, p.PluginObj.ID, "manifest.json"))
    91  	if err != nil {
    92  		return err
    93  	}
    94  	err = json.NewDecoder(dt).Decode(&p.PluginObj.Manifest)
    95  	dt.Close()
    96  	if err != nil {
    97  		return err
    98  	}
    99  
   100  	p.PluginObj.Config.Mounts = make([]types.PluginMount, len(p.PluginObj.Manifest.Mounts))
   101  	for i, mount := range p.PluginObj.Manifest.Mounts {
   102  		p.PluginObj.Config.Mounts[i] = mount
   103  	}
   104  	p.PluginObj.Config.Env = make([]string, 0, len(p.PluginObj.Manifest.Env))
   105  	for _, env := range p.PluginObj.Manifest.Env {
   106  		if env.Value != nil {
   107  			p.PluginObj.Config.Env = append(p.PluginObj.Config.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value))
   108  		}
   109  	}
   110  	copy(p.PluginObj.Config.Args, p.PluginObj.Manifest.Args.Value)
   111  
   112  	f, err := os.Create(filepath.Join(libRoot, p.PluginObj.ID, "plugin-config.json"))
   113  	if err != nil {
   114  		return err
   115  	}
   116  	err = json.NewEncoder(f).Encode(&p.PluginObj.Config)
   117  	f.Close()
   118  	return err
   119  }
   120  
   121  // Set is used to pass arguments to the plugin.
   122  func (p *Plugin) Set(args []string) error {
   123  	m := make(map[string]string, len(args))
   124  	for _, arg := range args {
   125  		i := strings.Index(arg, "=")
   126  		if i < 0 {
   127  			return fmt.Errorf("No equal sign '=' found in %s", arg)
   128  		}
   129  		m[arg[:i]] = arg[i+1:]
   130  	}
   131  	return errors.New("not implemented")
   132  }
   133  
   134  // ComputePrivileges takes the manifest file and computes the list of access necessary
   135  // for the plugin on the host.
   136  func (p *Plugin) ComputePrivileges() types.PluginPrivileges {
   137  	m := p.PluginObj.Manifest
   138  	var privileges types.PluginPrivileges
   139  	if m.Network.Type != "null" && m.Network.Type != "bridge" {
   140  		privileges = append(privileges, types.PluginPrivilege{
   141  			Name:        "network",
   142  			Description: "",
   143  			Value:       []string{m.Network.Type},
   144  		})
   145  	}
   146  	for _, mount := range m.Mounts {
   147  		if mount.Source != nil {
   148  			privileges = append(privileges, types.PluginPrivilege{
   149  				Name:        "mount",
   150  				Description: "",
   151  				Value:       []string{*mount.Source},
   152  			})
   153  		}
   154  	}
   155  	for _, device := range m.Devices {
   156  		if device.Path != nil {
   157  			privileges = append(privileges, types.PluginPrivilege{
   158  				Name:        "device",
   159  				Description: "",
   160  				Value:       []string{*device.Path},
   161  			})
   162  		}
   163  	}
   164  	if len(m.Capabilities) > 0 {
   165  		privileges = append(privileges, types.PluginPrivilege{
   166  			Name:        "capabilities",
   167  			Description: "",
   168  			Value:       m.Capabilities,
   169  		})
   170  	}
   171  	return privileges
   172  }
   173  
   174  // IsEnabled returns the active state of the plugin.
   175  func (p *Plugin) IsEnabled() bool {
   176  	p.RLock()
   177  	defer p.RUnlock()
   178  
   179  	return p.PluginObj.Enabled
   180  }
   181  
   182  // GetID returns the plugin's ID.
   183  func (p *Plugin) GetID() string {
   184  	p.RLock()
   185  	defer p.RUnlock()
   186  
   187  	return p.PluginObj.ID
   188  }
   189  
   190  // GetSocket returns the plugin socket.
   191  func (p *Plugin) GetSocket() string {
   192  	p.RLock()
   193  	defer p.RUnlock()
   194  
   195  	return p.PluginObj.Manifest.Interface.Socket
   196  }
   197  
   198  // GetTypes returns the interface types of a plugin.
   199  func (p *Plugin) GetTypes() []types.PluginInterfaceType {
   200  	p.RLock()
   201  	defer p.RUnlock()
   202  
   203  	return p.PluginObj.Manifest.Interface.Types
   204  }
   205  
   206  // InitSpec creates an OCI spec from the plugin's config.
   207  func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
   208  	rootfs := filepath.Join(libRoot, p.PluginObj.ID, "rootfs")
   209  	s.Root = specs.Root{
   210  		Path:     rootfs,
   211  		Readonly: false, // TODO: all plugins should be readonly? settable in manifest?
   212  	}
   213  
   214  	mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{
   215  		Source:      &p.RuntimeSourcePath,
   216  		Destination: defaultPluginRuntimeDestination,
   217  		Type:        "bind",
   218  		Options:     []string{"rbind", "rshared"},
   219  	})
   220  	for _, mount := range mounts {
   221  		m := specs.Mount{
   222  			Destination: mount.Destination,
   223  			Type:        mount.Type,
   224  			Options:     mount.Options,
   225  		}
   226  		// TODO: if nil, then it's required and user didn't set it
   227  		if mount.Source != nil {
   228  			m.Source = *mount.Source
   229  		}
   230  		if m.Source != "" && m.Type == "bind" {
   231  			fi, err := os.Lstat(filepath.Join(rootfs, m.Destination)) // TODO: followsymlinks
   232  			if err != nil {
   233  				return nil, err
   234  			}
   235  			if fi.IsDir() {
   236  				if err := os.MkdirAll(m.Source, 0700); err != nil {
   237  					return nil, err
   238  				}
   239  			}
   240  		}
   241  		s.Mounts = append(s.Mounts, m)
   242  	}
   243  
   244  	envs := make([]string, 1, len(p.PluginObj.Config.Env)+1)
   245  	envs[0] = "PATH=" + system.DefaultPathEnv
   246  	envs = append(envs, p.PluginObj.Config.Env...)
   247  
   248  	args := append(p.PluginObj.Manifest.Entrypoint, p.PluginObj.Config.Args...)
   249  	cwd := p.PluginObj.Manifest.Workdir
   250  	if len(cwd) == 0 {
   251  		cwd = "/"
   252  	}
   253  	s.Process = specs.Process{
   254  		Terminal: false,
   255  		Args:     args,
   256  		Cwd:      cwd,
   257  		Env:      envs,
   258  	}
   259  
   260  	return &s, nil
   261  }