2016-08-26 13:02:38 -04:00
package v2
import (
2016-10-06 10:09:54 -04:00
"encoding/json"
2016-11-22 14:21:34 -05:00
"errors"
2016-10-06 10:09:54 -04:00
"fmt"
"os"
"path/filepath"
"strings"
2016-08-26 13:02:38 -04:00
"sync"
2016-09-06 14:18:12 -04:00
"github.com/docker/docker/api/types"
2016-11-16 19:18:43 -05:00
"github.com/docker/docker/oci"
2016-11-30 16:02:45 -05:00
"github.com/docker/docker/pkg/plugingetter"
2016-08-26 13:02:38 -04:00
"github.com/docker/docker/pkg/plugins"
2016-10-06 10:09:54 -04:00
"github.com/docker/docker/pkg/system"
specs "github.com/opencontainers/runtime-spec/specs-go"
2016-08-26 13:02:38 -04:00
)
// Plugin represents an individual plugin.
type Plugin struct {
2016-12-01 14:36:56 -05:00
mu sync . RWMutex
PluginObj types . Plugin ` json:"plugin" `
pClient * plugins . Client
runtimeSourcePath string
refCount int
2016-11-22 14:21:34 -05:00
LibRoot string // TODO: make private
PropagatedMount string // TODO: make private
Rootfs string // TODO: make private
2016-08-26 13:02:38 -04:00
}
2016-10-06 10:09:54 -04:00
const defaultPluginRuntimeDestination = "/run/docker/plugins"
// ErrInadequateCapability indicates that the plugin did not have the requested capability.
2016-10-25 19:12:56 -04:00
type ErrInadequateCapability struct {
cap string
}
2016-10-06 10:09:54 -04:00
2016-10-25 19:12:56 -04:00
func ( e ErrInadequateCapability ) Error ( ) string {
return fmt . Sprintf ( "plugin does not provide %q capability" , e . cap )
2016-10-06 10:09:54 -04:00
}
func newPluginObj ( name , id , tag string ) types . Plugin {
return types . Plugin { Name : name , ID : id , Tag : tag }
}
// NewPlugin creates a plugin.
2016-10-31 20:07:05 -04:00
func NewPlugin ( name , id , runRoot , libRoot , tag string ) * Plugin {
2016-10-06 10:09:54 -04:00
return & Plugin {
PluginObj : newPluginObj ( name , id , tag ) ,
2016-12-01 14:36:56 -05:00
runtimeSourcePath : filepath . Join ( runRoot , id ) ,
2016-11-22 14:21:34 -05:00
LibRoot : libRoot ,
2016-10-06 10:09:54 -04:00
}
}
2016-12-01 14:36:56 -05:00
// Restore restores the plugin
func ( p * Plugin ) Restore ( runRoot string ) {
p . runtimeSourcePath = filepath . Join ( runRoot , p . GetID ( ) )
}
// GetRuntimeSourcePath gets the Source (host) path of the plugin socket
// This path gets bind mounted into the plugin.
func ( p * Plugin ) GetRuntimeSourcePath ( ) string {
p . mu . RLock ( )
defer p . mu . RUnlock ( )
return p . runtimeSourcePath
}
2016-11-22 14:21:34 -05:00
// BasePath returns the path to which all paths returned by the plugin are relative to.
// For Plugin objects this returns the host path of the plugin container's rootfs.
func ( p * Plugin ) BasePath ( ) string {
return p . Rootfs
}
2016-10-06 10:09:54 -04:00
// Client returns the plugin client.
func ( p * Plugin ) Client ( ) * plugins . Client {
2016-12-01 14:36:56 -05:00
p . mu . RLock ( )
defer p . mu . RUnlock ( )
return p . pClient
}
// SetPClient set the plugin client.
func ( p * Plugin ) SetPClient ( client * plugins . Client ) {
p . mu . Lock ( )
defer p . mu . Unlock ( )
p . pClient = client
2016-10-06 10:09:54 -04:00
}
// IsV1 returns true for V1 plugins and false otherwise.
func ( p * Plugin ) IsV1 ( ) bool {
return false
}
// Name returns the plugin name.
func ( p * Plugin ) Name ( ) string {
name := p . PluginObj . Name
if len ( p . PluginObj . Tag ) > 0 {
// TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these
name += ":" + p . PluginObj . Tag
}
return name
}
// FilterByCap query the plugin for a given capability.
func ( p * Plugin ) FilterByCap ( capability string ) ( * Plugin , error ) {
capability = strings . ToLower ( capability )
2016-11-07 21:51:47 -05:00
for _ , typ := range p . PluginObj . Config . Interface . Types {
2016-10-06 10:09:54 -04:00
if typ . Capability == capability && typ . Prefix == "docker" {
return p , nil
}
}
2016-10-25 19:12:56 -04:00
return nil , ErrInadequateCapability { capability }
2016-10-06 10:09:54 -04:00
}
// RemoveFromDisk deletes the plugin's runtime files from disk.
func ( p * Plugin ) RemoveFromDisk ( ) error {
2016-12-01 14:36:56 -05:00
return os . RemoveAll ( p . runtimeSourcePath )
2016-10-06 10:09:54 -04:00
}
2016-11-07 21:51:47 -05:00
// InitPlugin populates the plugin object from the plugin config file.
2016-10-31 20:07:05 -04:00
func ( p * Plugin ) InitPlugin ( ) error {
2016-11-22 14:21:34 -05:00
dt , err := os . Open ( filepath . Join ( p . LibRoot , p . PluginObj . ID , "config.json" ) )
2016-10-06 10:09:54 -04:00
if err != nil {
return err
}
2016-11-07 21:51:47 -05:00
err = json . NewDecoder ( dt ) . Decode ( & p . PluginObj . Config )
2016-10-06 10:09:54 -04:00
dt . Close ( )
if err != nil {
return err
}
2016-11-07 21:51:47 -05:00
p . PluginObj . Settings . Mounts = make ( [ ] types . PluginMount , len ( p . PluginObj . Config . Mounts ) )
2016-11-22 14:21:34 -05:00
copy ( p . PluginObj . Settings . Mounts , p . PluginObj . Config . Mounts )
2016-11-07 21:51:47 -05:00
p . PluginObj . Settings . Env = make ( [ ] string , 0 , len ( p . PluginObj . Config . Env ) )
2016-11-16 19:18:43 -05:00
p . PluginObj . Settings . Devices = make ( [ ] types . PluginDevice , 0 , len ( p . PluginObj . Config . Linux . Devices ) )
copy ( p . PluginObj . Settings . Devices , p . PluginObj . Config . Linux . Devices )
2016-11-07 21:51:47 -05:00
for _ , env := range p . PluginObj . Config . Env {
2016-10-06 10:09:54 -04:00
if env . Value != nil {
2016-11-07 21:51:47 -05:00
p . PluginObj . Settings . Env = append ( p . PluginObj . Settings . Env , fmt . Sprintf ( "%s=%s" , env . Name , * env . Value ) )
2016-10-06 10:09:54 -04:00
}
}
2016-11-22 14:21:34 -05:00
p . PluginObj . Settings . Args = make ( [ ] string , len ( p . PluginObj . Config . Args . Value ) )
2016-11-07 21:51:47 -05:00
copy ( p . PluginObj . Settings . Args , p . PluginObj . Config . Args . Value )
2016-10-06 10:09:54 -04:00
2016-11-07 21:51:47 -05:00
return p . writeSettings ( )
2016-10-31 20:07:05 -04:00
}
2016-11-07 21:51:47 -05:00
func ( p * Plugin ) writeSettings ( ) error {
2016-11-22 14:21:34 -05:00
f , err := os . Create ( filepath . Join ( p . LibRoot , p . PluginObj . ID , "plugin-settings.json" ) )
2016-10-06 10:09:54 -04:00
if err != nil {
return err
}
2016-11-07 21:51:47 -05:00
err = json . NewEncoder ( f ) . Encode ( & p . PluginObj . Settings )
2016-10-06 10:09:54 -04:00
f . Close ( )
return err
}
// Set is used to pass arguments to the plugin.
func ( p * Plugin ) Set ( args [ ] string ) error {
2016-12-01 14:36:56 -05:00
p . mu . Lock ( )
defer p . mu . Unlock ( )
2016-10-31 20:07:05 -04:00
if p . PluginObj . Enabled {
return fmt . Errorf ( "cannot set on an active plugin, disable plugin before setting" )
}
sets , err := newSettables ( args )
if err != nil {
return err
}
2016-11-07 20:15:31 -05:00
// TODO(vieux): lots of code duplication here, needs to be refactored.
2016-10-31 20:07:05 -04:00
next :
for _ , s := range sets {
2016-11-07 21:51:47 -05:00
// range over all the envs in the config
for _ , env := range p . PluginObj . Config . Env {
// found the env in the config
2016-10-31 20:07:05 -04:00
if env . Name == s . name {
// is it settable ?
if ok , err := s . isSettable ( allowedSettableFieldsEnv , env . Settable ) ; err != nil {
return err
} else if ! ok {
return fmt . Errorf ( "%q is not settable" , s . prettyName ( ) )
}
2016-11-07 21:51:47 -05:00
// is it, so lets update the settings in memory
2016-11-07 20:15:31 -05:00
updateSettingsEnv ( & p . PluginObj . Settings . Env , & s )
continue next
}
}
// range over all the mounts in the config
for _ , mount := range p . PluginObj . Config . Mounts {
// found the mount in the config
if mount . Name == s . name {
// is it settable ?
if ok , err := s . isSettable ( allowedSettableFieldsMounts , mount . Settable ) ; err != nil {
return err
} else if ! ok {
return fmt . Errorf ( "%q is not settable" , s . prettyName ( ) )
}
// it is, so lets update the settings in memory
* mount . Source = s . value
continue next
}
}
// range over all the devices in the config
2016-11-16 19:18:43 -05:00
for _ , device := range p . PluginObj . Config . Linux . Devices {
2016-11-07 20:15:31 -05:00
// found the device in the config
if device . Name == s . name {
// is it settable ?
if ok , err := s . isSettable ( allowedSettableFieldsDevices , device . Settable ) ; err != nil {
return err
} else if ! ok {
return fmt . Errorf ( "%q is not settable" , s . prettyName ( ) )
}
// it is, so lets update the settings in memory
* device . Path = s . value
2016-10-31 20:07:05 -04:00
continue next
}
2016-10-06 10:09:54 -04:00
}
2016-10-31 20:07:05 -04:00
2016-11-07 20:15:31 -05:00
// found the name in the config
if p . PluginObj . Config . Args . Name == s . name {
// is it settable ?
if ok , err := s . isSettable ( allowedSettableFieldsArgs , p . PluginObj . Config . Args . Settable ) ; err != nil {
return err
} else if ! ok {
return fmt . Errorf ( "%q is not settable" , s . prettyName ( ) )
}
// it is, so lets update the settings in memory
p . PluginObj . Settings . Args = strings . Split ( s . value , " " )
continue next
}
2016-10-31 20:07:05 -04:00
return fmt . Errorf ( "setting %q not found in the plugin configuration" , s . name )
2016-10-06 10:09:54 -04:00
}
2016-10-31 20:07:05 -04:00
2016-11-07 21:51:47 -05:00
// update the settings on disk
return p . writeSettings ( )
2016-10-06 10:09:54 -04:00
}
// IsEnabled returns the active state of the plugin.
func ( p * Plugin ) IsEnabled ( ) bool {
2016-12-01 14:36:56 -05:00
p . mu . RLock ( )
defer p . mu . RUnlock ( )
2016-10-06 10:09:54 -04:00
return p . PluginObj . Enabled
}
// GetID returns the plugin's ID.
func ( p * Plugin ) GetID ( ) string {
2016-12-01 14:36:56 -05:00
p . mu . RLock ( )
defer p . mu . RUnlock ( )
2016-10-06 10:09:54 -04:00
return p . PluginObj . ID
}
// GetSocket returns the plugin socket.
func ( p * Plugin ) GetSocket ( ) string {
2016-12-01 14:36:56 -05:00
p . mu . RLock ( )
defer p . mu . RUnlock ( )
2016-10-06 10:09:54 -04:00
2016-11-07 21:51:47 -05:00
return p . PluginObj . Config . Interface . Socket
2016-10-06 10:09:54 -04:00
}
// GetTypes returns the interface types of a plugin.
func ( p * Plugin ) GetTypes ( ) [ ] types . PluginInterfaceType {
2016-12-01 14:36:56 -05:00
p . mu . RLock ( )
defer p . mu . RUnlock ( )
2016-10-06 10:09:54 -04:00
2016-11-07 21:51:47 -05:00
return p . PluginObj . Config . Interface . Types
2016-10-06 10:09:54 -04:00
}
2016-12-01 14:36:56 -05:00
// GetRefCount returns the reference count.
func ( p * Plugin ) GetRefCount ( ) int {
p . mu . RLock ( )
defer p . mu . RUnlock ( )
return p . refCount
}
2016-12-09 12:34:30 -05:00
// AddRefCount adds to reference count.
func ( p * Plugin ) AddRefCount ( count int ) {
2016-12-01 14:36:56 -05:00
p . mu . Lock ( )
defer p . mu . Unlock ( )
2016-12-09 12:34:30 -05:00
p . refCount += count
2016-12-01 14:36:56 -05:00
}
2016-11-30 16:02:45 -05:00
// Acquire increments the plugin's reference count
// This should be followed up by `Release()` when the plugin is no longer in use.
func ( p * Plugin ) Acquire ( ) {
p . AddRefCount ( plugingetter . ACQUIRE )
}
// Release decrements the plugin's reference count
// This should only be called when the plugin is no longer in use, e.g. with
// via `Acquire()` or getter.Get("name", "type", plugingetter.ACQUIRE)
func ( p * Plugin ) Release ( ) {
p . AddRefCount ( plugingetter . RELEASE )
}
2016-10-06 10:09:54 -04:00
// InitSpec creates an OCI spec from the plugin's config.
2016-11-22 14:21:34 -05:00
func ( p * Plugin ) InitSpec ( s specs . Spec ) ( * specs . Spec , error ) {
2016-10-06 10:09:54 -04:00
s . Root = specs . Root {
2016-11-22 14:21:34 -05:00
Path : p . Rootfs ,
2016-11-07 21:51:47 -05:00
Readonly : false , // TODO: all plugins should be readonly? settable in config?
2016-10-06 10:09:54 -04:00
}
2016-11-22 14:21:34 -05:00
userMounts := make ( map [ string ] struct { } , len ( p . PluginObj . Settings . Mounts ) )
for _ , m := range p . PluginObj . Settings . Mounts {
2016-11-16 19:18:43 -05:00
userMounts [ m . Destination ] = struct { } { }
2016-09-09 12:27:53 -04:00
}
2016-11-22 14:21:34 -05:00
if err := os . MkdirAll ( p . runtimeSourcePath , 0755 ) ; err != nil {
return nil , err
}
2016-11-16 19:18:43 -05:00
mounts := append ( p . PluginObj . Config . Mounts , types . PluginMount {
2016-12-01 14:36:56 -05:00
Source : & p . runtimeSourcePath ,
2016-10-06 10:09:54 -04:00
Destination : defaultPluginRuntimeDestination ,
Type : "bind" ,
Options : [ ] string { "rbind" , "rshared" } ,
} )
2016-09-09 12:32:12 -04:00
if p . PluginObj . Config . Network . Type != "" {
// 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)
if p . PluginObj . Config . Network . Type == "host" {
2016-11-22 16:42:11 -05:00
oci . RemoveNamespace ( & s , specs . NamespaceType ( "network" ) )
2016-09-09 12:32:12 -04:00
}
etcHosts := "/etc/hosts"
resolvConf := "/etc/resolv.conf"
mounts = append ( mounts ,
types . PluginMount {
Source : & etcHosts ,
Destination : etcHosts ,
Type : "bind" ,
Options : [ ] string { "rbind" , "ro" } ,
} ,
types . PluginMount {
Source : & resolvConf ,
Destination : resolvConf ,
Type : "bind" ,
Options : [ ] string { "rbind" , "ro" } ,
} )
}
2016-11-22 14:21:34 -05:00
for _ , mnt := range mounts {
2016-10-06 10:09:54 -04:00
m := specs . Mount {
2016-11-22 14:21:34 -05:00
Destination : mnt . Destination ,
Type : mnt . Type ,
Options : mnt . Options ,
2016-10-06 10:09:54 -04:00
}
2016-11-22 14:21:34 -05:00
if mnt . Source == nil {
return nil , errors . New ( "mount source is not specified" )
2016-10-06 10:09:54 -04:00
}
2016-11-22 14:21:34 -05:00
m . Source = * mnt . Source
2016-10-06 10:09:54 -04:00
s . Mounts = append ( s . Mounts , m )
}
2016-09-09 12:27:53 -04:00
for i , m := range s . Mounts {
2016-11-16 19:18:43 -05:00
if strings . HasPrefix ( m . Destination , "/dev/" ) {
if _ , ok := userMounts [ m . Destination ] ; ok {
s . Mounts = append ( s . Mounts [ : i ] , s . Mounts [ i + 1 : ] ... )
}
}
}
2016-11-22 14:21:34 -05:00
if p . PluginObj . Config . PropagatedMount != "" {
p . PropagatedMount = filepath . Join ( p . Rootfs , p . PluginObj . Config . PropagatedMount )
s . Linux . RootfsPropagation = "rshared"
}
2016-11-16 19:18:43 -05:00
if p . PluginObj . Config . Linux . DeviceCreation {
rwm := "rwm"
s . Linux . Resources . Devices = [ ] specs . DeviceCgroup { { Allow : true , Access : & rwm } }
}
2016-11-22 14:21:34 -05:00
for _ , dev := range p . PluginObj . Settings . Devices {
2016-11-16 19:18:43 -05:00
path := * dev . Path
d , dPermissions , err := oci . DevicesFromPath ( path , path , "rwm" )
if err != nil {
return nil , err
2016-09-09 12:27:53 -04:00
}
2016-11-16 19:18:43 -05:00
s . Linux . Devices = append ( s . Linux . Devices , d ... )
s . Linux . Resources . Devices = append ( s . Linux . Resources . Devices , dPermissions ... )
2016-09-09 12:27:53 -04:00
}
2016-11-07 21:51:47 -05:00
envs := make ( [ ] string , 1 , len ( p . PluginObj . Settings . Env ) + 1 )
2016-10-06 10:09:54 -04:00
envs [ 0 ] = "PATH=" + system . DefaultPathEnv
2016-11-07 21:51:47 -05:00
envs = append ( envs , p . PluginObj . Settings . Env ... )
2016-10-06 10:09:54 -04:00
2016-11-07 21:51:47 -05:00
args := append ( p . PluginObj . Config . Entrypoint , p . PluginObj . Settings . Args ... )
cwd := p . PluginObj . Config . Workdir
2016-10-06 10:09:54 -04:00
if len ( cwd ) == 0 {
cwd = "/"
}
2016-09-09 12:27:53 -04:00
s . Process . Terminal = false
s . Process . Args = args
s . Process . Cwd = cwd
s . Process . Env = envs
s . Process . Capabilities = append ( s . Process . Capabilities , p . PluginObj . Config . Linux . Capabilities ... )
2016-10-06 10:09:54 -04:00
return & s , nil
}