| package v2 // import "github.com/docker/docker/plugin/v2" | |
| import ( | |
| "fmt" | |
| "net" | |
| "path/filepath" | |
| "strings" | |
| "sync" | |
| "time" | |
| "github.com/docker/docker/api/types" | |
| "github.com/docker/docker/pkg/plugingetter" | |
| "github.com/docker/docker/pkg/plugins" | |
| "github.com/opencontainers/go-digest" | |
| specs "github.com/opencontainers/runtime-spec/specs-go" | |
| ) | |
| // Plugin represents an individual plugin. | |
| type Plugin struct { | |
| mu sync.RWMutex | |
| PluginObj types.Plugin `json:"plugin"` // todo: embed struct | |
| pClient *plugins.Client | |
| refCount int | |
| Rootfs string // TODO: make private | |
| Config digest.Digest | |
| Blobsums []digest.Digest | |
| Manifest digest.Digest | |
| modifyRuntimeSpec func(*specs.Spec) | |
| SwarmServiceID string | |
| timeout time.Duration | |
| addr net.Addr | |
| } | |
| const defaultPluginRuntimeDestination = "/run/docker/plugins" | |
| // ErrInadequateCapability indicates that the plugin did not have the requested capability. | |
| type ErrInadequateCapability struct { | |
| cap string | |
| } | |
| func (e ErrInadequateCapability) Error() string { | |
| return fmt.Sprintf("plugin does not provide %q capability", e.cap) | |
| } | |
| // ScopedPath returns the path scoped to the plugin rootfs | |
| func (p *Plugin) ScopedPath(s string) string { | |
| if p.PluginObj.Config.PropagatedMount != "" && strings.HasPrefix(s, p.PluginObj.Config.PropagatedMount) { | |
| // re-scope to the propagated mount path on the host | |
| return filepath.Join(filepath.Dir(p.Rootfs), "propagated-mount", strings.TrimPrefix(s, p.PluginObj.Config.PropagatedMount)) | |
| } | |
| return filepath.Join(p.Rootfs, s) | |
| } | |
| // Client returns the plugin client. | |
| // Deprecated: use p.Addr() and manually create the client | |
| func (p *Plugin) Client() *plugins.Client { | |
| p.mu.RLock() | |
| defer p.mu.RUnlock() | |
| return p.pClient | |
| } | |
| // SetPClient set the plugin client. | |
| // Deprecated: Hardcoded plugin client is deprecated | |
| func (p *Plugin) SetPClient(client *plugins.Client) { | |
| p.mu.Lock() | |
| defer p.mu.Unlock() | |
| p.pClient = client | |
| } | |
| // 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 { | |
| return p.PluginObj.Name | |
| } | |
| // FilterByCap query the plugin for a given capability. | |
| func (p *Plugin) FilterByCap(capability string) (*Plugin, error) { | |
| capability = strings.ToLower(capability) | |
| for _, typ := range p.PluginObj.Config.Interface.Types { | |
| if typ.Capability == capability && typ.Prefix == "docker" { | |
| return p, nil | |
| } | |
| } | |
| return nil, ErrInadequateCapability{capability} | |
| } | |
| // InitEmptySettings initializes empty settings for a plugin. | |
| func (p *Plugin) InitEmptySettings() { | |
| p.PluginObj.Settings.Mounts = make([]types.PluginMount, len(p.PluginObj.Config.Mounts)) | |
| copy(p.PluginObj.Settings.Mounts, p.PluginObj.Config.Mounts) | |
| p.PluginObj.Settings.Devices = make([]types.PluginDevice, len(p.PluginObj.Config.Linux.Devices)) | |
| copy(p.PluginObj.Settings.Devices, p.PluginObj.Config.Linux.Devices) | |
| p.PluginObj.Settings.Env = make([]string, 0, len(p.PluginObj.Config.Env)) | |
| for _, env := range p.PluginObj.Config.Env { | |
| if env.Value != nil { | |
| p.PluginObj.Settings.Env = append(p.PluginObj.Settings.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value)) | |
| } | |
| } | |
| p.PluginObj.Settings.Args = make([]string, len(p.PluginObj.Config.Args.Value)) | |
| copy(p.PluginObj.Settings.Args, p.PluginObj.Config.Args.Value) | |
| } | |
| // Set is used to pass arguments to the plugin. | |
| func (p *Plugin) Set(args []string) error { | |
| p.mu.Lock() | |
| defer p.mu.Unlock() | |
| 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 | |
| } | |
| // TODO(vieux): lots of code duplication here, needs to be refactored. | |
| next: | |
| for _, set := range sets { | |
| s := set | |
| // range over all the envs in the config | |
| for _, env := range p.PluginObj.Config.Env { | |
| // found the env in the config | |
| 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()) | |
| } | |
| // is it, so lets update the settings in memory | |
| 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 | |
| if mount.Source == nil { | |
| return fmt.Errorf("Plugin config has no mount source") | |
| } | |
| *mount.Source = s.value | |
| continue next | |
| } | |
| } | |
| // range over all the devices in the config | |
| for _, device := range p.PluginObj.Config.Linux.Devices { | |
| // 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 | |
| if device.Path == nil { | |
| return fmt.Errorf("Plugin config has no device path") | |
| } | |
| *device.Path = s.value | |
| continue next | |
| } | |
| } | |
| // 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 | |
| } | |
| return fmt.Errorf("setting %q not found in the plugin configuration", s.name) | |
| } | |
| return nil | |
| } | |
| // IsEnabled returns the active state of the plugin. | |
| func (p *Plugin) IsEnabled() bool { | |
| p.mu.RLock() | |
| defer p.mu.RUnlock() | |
| return p.PluginObj.Enabled | |
| } | |
| // GetID returns the plugin's ID. | |
| func (p *Plugin) GetID() string { | |
| p.mu.RLock() | |
| defer p.mu.RUnlock() | |
| return p.PluginObj.ID | |
| } | |
| // GetSocket returns the plugin socket. | |
| func (p *Plugin) GetSocket() string { | |
| p.mu.RLock() | |
| defer p.mu.RUnlock() | |
| return p.PluginObj.Config.Interface.Socket | |
| } | |
| // GetTypes returns the interface types of a plugin. | |
| func (p *Plugin) GetTypes() []types.PluginInterfaceType { | |
| p.mu.RLock() | |
| defer p.mu.RUnlock() | |
| return p.PluginObj.Config.Interface.Types | |
| } | |
| // GetRefCount returns the reference count. | |
| func (p *Plugin) GetRefCount() int { | |
| p.mu.RLock() | |
| defer p.mu.RUnlock() | |
| return p.refCount | |
| } | |
| // AddRefCount adds to reference count. | |
| func (p *Plugin) AddRefCount(count int) { | |
| p.mu.Lock() | |
| defer p.mu.Unlock() | |
| p.refCount += count | |
| } | |
| // 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) | |
| } | |
| // SetSpecOptModifier sets the function to use to modify the generated | |
| // runtime spec. | |
| func (p *Plugin) SetSpecOptModifier(f func(*specs.Spec)) { | |
| p.mu.Lock() | |
| p.modifyRuntimeSpec = f | |
| p.mu.Unlock() | |
| } | |
| // Timeout gets the currently configured connection timeout. | |
| // This should be used when dialing the plugin. | |
| func (p *Plugin) Timeout() time.Duration { | |
| p.mu.RLock() | |
| t := p.timeout | |
| p.mu.RUnlock() | |
| return t | |
| } | |
| // SetTimeout sets the timeout to use for dialing. | |
| func (p *Plugin) SetTimeout(t time.Duration) { | |
| p.mu.Lock() | |
| p.timeout = t | |
| p.mu.Unlock() | |
| } | |
| // Addr returns the net.Addr to use to connect to the plugin socket | |
| func (p *Plugin) Addr() net.Addr { | |
| p.mu.RLock() | |
| addr := p.addr | |
| p.mu.RUnlock() | |
| return addr | |
| } | |
| // SetAddr sets the plugin address which can be used for dialing the plugin. | |
| func (p *Plugin) SetAddr(addr net.Addr) { | |
| p.mu.Lock() | |
| p.addr = addr | |
| p.mu.Unlock() | |
| } | |
| // Protocol is the protocol that should be used for interacting with the plugin. | |
| func (p *Plugin) Protocol() string { | |
| if p.PluginObj.Config.Interface.ProtocolScheme != "" { | |
| return p.PluginObj.Config.Interface.ProtocolScheme | |
| } | |
| return plugins.ProtocolSchemeHTTPV1 | |
| } |