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 }