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