github.com/zhuohuang-hust/src-cbuild@v0.0.0-20230105071821-c7aab3e7c840/plugin/v2/plugin.go (about) 1 package v2 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strings" 9 "sync" 10 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/oci" 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 LibRoot string `json:"-"` 28 TimeoutInSecs int `json:"-"` 29 } 30 31 const defaultPluginRuntimeDestination = "/run/docker/plugins" 32 33 // ErrInadequateCapability indicates that the plugin did not have the requested capability. 34 type ErrInadequateCapability struct { 35 cap string 36 } 37 38 func (e ErrInadequateCapability) Error() string { 39 return fmt.Sprintf("plugin does not provide %q capability", e.cap) 40 } 41 42 func newPluginObj(name, id, tag string) types.Plugin { 43 return types.Plugin{Name: name, ID: id, Tag: tag} 44 } 45 46 // NewPlugin creates a plugin. 47 func NewPlugin(name, id, runRoot, libRoot, tag string) *Plugin { 48 return &Plugin{ 49 PluginObj: newPluginObj(name, id, tag), 50 RuntimeSourcePath: filepath.Join(runRoot, id), 51 LibRoot: libRoot, 52 } 53 } 54 55 // Client returns the plugin client. 56 func (p *Plugin) Client() *plugins.Client { 57 return p.PClient 58 } 59 60 // IsV1 returns true for V1 plugins and false otherwise. 61 func (p *Plugin) IsV1() bool { 62 return false 63 } 64 65 // Name returns the plugin name. 66 func (p *Plugin) Name() string { 67 name := p.PluginObj.Name 68 if len(p.PluginObj.Tag) > 0 { 69 // TODO: this feels hacky, maybe we should be storing the distribution reference rather than splitting these 70 name += ":" + p.PluginObj.Tag 71 } 72 return name 73 } 74 75 // FilterByCap query the plugin for a given capability. 76 func (p *Plugin) FilterByCap(capability string) (*Plugin, error) { 77 capability = strings.ToLower(capability) 78 for _, typ := range p.PluginObj.Config.Interface.Types { 79 if typ.Capability == capability && typ.Prefix == "docker" { 80 return p, nil 81 } 82 } 83 return nil, ErrInadequateCapability{capability} 84 } 85 86 // RemoveFromDisk deletes the plugin's runtime files from disk. 87 func (p *Plugin) RemoveFromDisk() error { 88 return os.RemoveAll(p.RuntimeSourcePath) 89 } 90 91 // InitPlugin populates the plugin object from the plugin config file. 92 func (p *Plugin) InitPlugin() error { 93 dt, err := os.Open(filepath.Join(p.LibRoot, p.PluginObj.ID, "config.json")) 94 if err != nil { 95 return err 96 } 97 err = json.NewDecoder(dt).Decode(&p.PluginObj.Config) 98 dt.Close() 99 if err != nil { 100 return err 101 } 102 103 p.PluginObj.Settings.Mounts = make([]types.PluginMount, len(p.PluginObj.Config.Mounts)) 104 for i, mount := range p.PluginObj.Config.Mounts { 105 p.PluginObj.Settings.Mounts[i] = mount 106 } 107 p.PluginObj.Settings.Env = make([]string, 0, len(p.PluginObj.Config.Env)) 108 p.PluginObj.Settings.Devices = make([]types.PluginDevice, 0, len(p.PluginObj.Config.Linux.Devices)) 109 copy(p.PluginObj.Settings.Devices, p.PluginObj.Config.Linux.Devices) 110 for _, env := range p.PluginObj.Config.Env { 111 if env.Value != nil { 112 p.PluginObj.Settings.Env = append(p.PluginObj.Settings.Env, fmt.Sprintf("%s=%s", env.Name, *env.Value)) 113 } 114 } 115 copy(p.PluginObj.Settings.Args, p.PluginObj.Config.Args.Value) 116 117 return p.writeSettings() 118 } 119 120 func (p *Plugin) writeSettings() error { 121 f, err := os.Create(filepath.Join(p.LibRoot, p.PluginObj.ID, "plugin-settings.json")) 122 if err != nil { 123 return err 124 } 125 err = json.NewEncoder(f).Encode(&p.PluginObj.Settings) 126 f.Close() 127 return err 128 } 129 130 // Set is used to pass arguments to the plugin. 131 func (p *Plugin) Set(args []string) error { 132 p.Lock() 133 defer p.Unlock() 134 135 if p.PluginObj.Enabled { 136 return fmt.Errorf("cannot set on an active plugin, disable plugin before setting") 137 } 138 139 sets, err := newSettables(args) 140 if err != nil { 141 return err 142 } 143 144 // TODO(vieux): lots of code duplication here, needs to be refactored. 145 146 next: 147 for _, s := range sets { 148 // range over all the envs in the config 149 for _, env := range p.PluginObj.Config.Env { 150 // found the env in the config 151 if env.Name == s.name { 152 // is it settable ? 153 if ok, err := s.isSettable(allowedSettableFieldsEnv, env.Settable); err != nil { 154 return err 155 } else if !ok { 156 return fmt.Errorf("%q is not settable", s.prettyName()) 157 } 158 // is it, so lets update the settings in memory 159 updateSettingsEnv(&p.PluginObj.Settings.Env, &s) 160 continue next 161 } 162 } 163 164 // range over all the mounts in the config 165 for _, mount := range p.PluginObj.Config.Mounts { 166 // found the mount in the config 167 if mount.Name == s.name { 168 // is it settable ? 169 if ok, err := s.isSettable(allowedSettableFieldsMounts, mount.Settable); err != nil { 170 return err 171 } else if !ok { 172 return fmt.Errorf("%q is not settable", s.prettyName()) 173 } 174 175 // it is, so lets update the settings in memory 176 *mount.Source = s.value 177 continue next 178 } 179 } 180 181 // range over all the devices in the config 182 for _, device := range p.PluginObj.Config.Linux.Devices { 183 // found the device in the config 184 if device.Name == s.name { 185 // is it settable ? 186 if ok, err := s.isSettable(allowedSettableFieldsDevices, device.Settable); err != nil { 187 return err 188 } else if !ok { 189 return fmt.Errorf("%q is not settable", s.prettyName()) 190 } 191 192 // it is, so lets update the settings in memory 193 *device.Path = s.value 194 continue next 195 } 196 } 197 198 // found the name in the config 199 if p.PluginObj.Config.Args.Name == s.name { 200 // is it settable ? 201 if ok, err := s.isSettable(allowedSettableFieldsArgs, p.PluginObj.Config.Args.Settable); err != nil { 202 return err 203 } else if !ok { 204 return fmt.Errorf("%q is not settable", s.prettyName()) 205 } 206 207 // it is, so lets update the settings in memory 208 p.PluginObj.Settings.Args = strings.Split(s.value, " ") 209 continue next 210 } 211 212 return fmt.Errorf("setting %q not found in the plugin configuration", s.name) 213 } 214 215 // update the settings on disk 216 return p.writeSettings() 217 } 218 219 // IsEnabled returns the active state of the plugin. 220 func (p *Plugin) IsEnabled() bool { 221 p.RLock() 222 defer p.RUnlock() 223 224 return p.PluginObj.Enabled 225 } 226 227 // GetID returns the plugin's ID. 228 func (p *Plugin) GetID() string { 229 p.RLock() 230 defer p.RUnlock() 231 232 return p.PluginObj.ID 233 } 234 235 // GetSocket returns the plugin socket. 236 func (p *Plugin) GetSocket() string { 237 p.RLock() 238 defer p.RUnlock() 239 240 return p.PluginObj.Config.Interface.Socket 241 } 242 243 // GetTypes returns the interface types of a plugin. 244 func (p *Plugin) GetTypes() []types.PluginInterfaceType { 245 p.RLock() 246 defer p.RUnlock() 247 248 return p.PluginObj.Config.Interface.Types 249 } 250 251 // InitSpec creates an OCI spec from the plugin's config. 252 func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) { 253 rootfs := filepath.Join(libRoot, p.PluginObj.ID, "rootfs") 254 s.Root = specs.Root{ 255 Path: rootfs, 256 Readonly: false, // TODO: all plugins should be readonly? settable in config? 257 } 258 259 userMounts := make(map[string]struct{}, len(p.PluginObj.Config.Mounts)) 260 for _, m := range p.PluginObj.Config.Mounts { 261 userMounts[m.Destination] = struct{}{} 262 } 263 264 mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{ 265 Source: &p.RuntimeSourcePath, 266 Destination: defaultPluginRuntimeDestination, 267 Type: "bind", 268 Options: []string{"rbind", "rshared"}, 269 }) 270 271 if p.PluginObj.Config.Network.Type != "" { 272 // 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) 273 if p.PluginObj.Config.Network.Type == "host" { 274 oci.RemoveNamespace(&s, specs.NamespaceType("network")) 275 } 276 etcHosts := "/etc/hosts" 277 resolvConf := "/etc/resolv.conf" 278 mounts = append(mounts, 279 types.PluginMount{ 280 Source: &etcHosts, 281 Destination: etcHosts, 282 Type: "bind", 283 Options: []string{"rbind", "ro"}, 284 }, 285 types.PluginMount{ 286 Source: &resolvConf, 287 Destination: resolvConf, 288 Type: "bind", 289 Options: []string{"rbind", "ro"}, 290 }) 291 } 292 293 for _, mount := range mounts { 294 m := specs.Mount{ 295 Destination: mount.Destination, 296 Type: mount.Type, 297 Options: mount.Options, 298 } 299 // TODO: if nil, then it's required and user didn't set it 300 if mount.Source != nil { 301 m.Source = *mount.Source 302 } 303 if m.Source != "" && m.Type == "bind" { 304 fi, err := os.Lstat(filepath.Join(rootfs, m.Destination)) // TODO: followsymlinks 305 if err != nil { 306 return nil, err 307 } 308 if fi.IsDir() { 309 if err := os.MkdirAll(m.Source, 0700); err != nil { 310 return nil, err 311 } 312 } 313 } 314 s.Mounts = append(s.Mounts, m) 315 } 316 317 for i, m := range s.Mounts { 318 if strings.HasPrefix(m.Destination, "/dev/") { 319 if _, ok := userMounts[m.Destination]; ok { 320 s.Mounts = append(s.Mounts[:i], s.Mounts[i+1:]...) 321 } 322 } 323 } 324 325 if p.PluginObj.Config.Linux.DeviceCreation { 326 rwm := "rwm" 327 s.Linux.Resources.Devices = []specs.DeviceCgroup{{Allow: true, Access: &rwm}} 328 } 329 for _, dev := range p.PluginObj.Config.Linux.Devices { 330 path := *dev.Path 331 d, dPermissions, err := oci.DevicesFromPath(path, path, "rwm") 332 if err != nil { 333 return nil, err 334 } 335 s.Linux.Devices = append(s.Linux.Devices, d...) 336 s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, dPermissions...) 337 } 338 339 envs := make([]string, 1, len(p.PluginObj.Settings.Env)+1) 340 envs[0] = "PATH=" + system.DefaultPathEnv 341 envs = append(envs, p.PluginObj.Settings.Env...) 342 343 args := append(p.PluginObj.Config.Entrypoint, p.PluginObj.Settings.Args...) 344 cwd := p.PluginObj.Config.Workdir 345 if len(cwd) == 0 { 346 cwd = "/" 347 } 348 s.Process.Terminal = false 349 s.Process.Args = args 350 s.Process.Cwd = cwd 351 s.Process.Env = envs 352 353 s.Process.Capabilities = append(s.Process.Capabilities, p.PluginObj.Config.Linux.Capabilities...) 354 355 return &s, nil 356 }