Procházet zdrojové kódy

Merge pull request #28601 from tiborvass/plugin-misc-fixes

Plugin miscellaneous fixes
Anusha Ragunathan před 8 roky
rodič
revize
23ea9e45fd

+ 17 - 10
api/swagger.yaml

@@ -1329,9 +1329,8 @@ definitions:
           - Entrypoint
           - Workdir
           - Network
-          - Capabilities
+          - Linux
           - Mounts
-          - Devices
           - Env
           - Args
         properties:
@@ -1379,18 +1378,26 @@ definitions:
               Type:
                 x-nullable: false
                 type: "string"
-          Capabilities:
-            type: "array"
-            items:
-              type: "string"
+          Linux:
+            type: "object"
+            x-nullable: false
+            required: [Capabilities, DeviceCreation, Devices]
+            properties:
+              Capabilities:
+                type: "array"
+                items:
+                  type: "string"
+              DeviceCreation:
+                type: "boolean"
+                x-nullable: false
+              Devices:
+                type: "array"
+                items:
+                  $ref: "#/definitions/PluginDevice"
           Mounts:
             type: "array"
             items:
               $ref: "#/definitions/PluginMount"
-          Devices:
-            type: "array"
-            items:
-              $ref: "#/definitions/PluginDevice"
           Env:
             type: "array"
             items:

+ 21 - 8
api/types/plugin.go

@@ -39,18 +39,10 @@ type PluginConfig struct {
 	// Required: true
 	Args PluginConfigArgs `json:"Args"`
 
-	// capabilities
-	// Required: true
-	Capabilities []string `json:"Capabilities"`
-
 	// description
 	// Required: true
 	Description string `json:"Description"`
 
-	// devices
-	// Required: true
-	Devices []PluginDevice `json:"Devices"`
-
 	// documentation
 	// Required: true
 	Documentation string `json:"Documentation"`
@@ -67,6 +59,10 @@ type PluginConfig struct {
 	// Required: true
 	Interface PluginConfigInterface `json:"Interface"`
 
+	// linux
+	// Required: true
+	Linux PluginConfigLinux `json:"Linux"`
+
 	// mounts
 	// Required: true
 	Mounts []PluginMount `json:"Mounts"`
@@ -117,6 +113,23 @@ type PluginConfigInterface struct {
 	Types []PluginInterfaceType `json:"Types"`
 }
 
+// PluginConfigLinux plugin config linux
+// swagger:model PluginConfigLinux
+type PluginConfigLinux struct {
+
+	// capabilities
+	// Required: true
+	Capabilities []string `json:"Capabilities"`
+
+	// device creation
+	// Required: true
+	DeviceCreation bool `json:"DeviceCreation"`
+
+	// devices
+	// Required: true
+	Devices []PluginDevice `json:"Devices"`
+}
+
 // PluginConfigNetwork plugin config network
 // swagger:model PluginConfigNetwork
 type PluginConfigNetwork struct {

+ 0 - 80
daemon/container_operations_unix.go

@@ -8,13 +8,11 @@ import (
 	"os"
 	"path/filepath"
 	"strconv"
-	"strings"
 	"syscall"
 	"time"
 
 	"github.com/Sirupsen/logrus"
 	"github.com/cloudflare/cfssl/log"
-	containertypes "github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/container"
 	"github.com/docker/docker/daemon/links"
 	"github.com/docker/docker/pkg/idtools"
@@ -22,16 +20,10 @@ import (
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/runconfig"
 	"github.com/docker/libnetwork"
-	"github.com/opencontainers/runc/libcontainer/configs"
-	"github.com/opencontainers/runc/libcontainer/devices"
 	"github.com/opencontainers/runc/libcontainer/label"
-	"github.com/opencontainers/runtime-spec/specs-go"
 	"github.com/pkg/errors"
 )
 
-func u32Ptr(i int64) *uint32     { u := uint32(i); return &u }
-func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm }
-
 func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]string, error) {
 	var env []string
 	children := daemon.children(container)
@@ -247,78 +239,6 @@ func killProcessDirectly(container *container.Container) error {
 	return nil
 }
 
-func specDevice(d *configs.Device) specs.Device {
-	return specs.Device{
-		Type:     string(d.Type),
-		Path:     d.Path,
-		Major:    d.Major,
-		Minor:    d.Minor,
-		FileMode: fmPtr(int64(d.FileMode)),
-		UID:      u32Ptr(int64(d.Uid)),
-		GID:      u32Ptr(int64(d.Gid)),
-	}
-}
-
-func specDeviceCgroup(d *configs.Device) specs.DeviceCgroup {
-	t := string(d.Type)
-	return specs.DeviceCgroup{
-		Allow:  true,
-		Type:   &t,
-		Major:  &d.Major,
-		Minor:  &d.Minor,
-		Access: &d.Permissions,
-	}
-}
-
-func getDevicesFromPath(deviceMapping containertypes.DeviceMapping) (devs []specs.Device, devPermissions []specs.DeviceCgroup, err error) {
-	resolvedPathOnHost := deviceMapping.PathOnHost
-
-	// check if it is a symbolic link
-	if src, e := os.Lstat(deviceMapping.PathOnHost); e == nil && src.Mode()&os.ModeSymlink == os.ModeSymlink {
-		if linkedPathOnHost, e := filepath.EvalSymlinks(deviceMapping.PathOnHost); e == nil {
-			resolvedPathOnHost = linkedPathOnHost
-		}
-	}
-
-	device, err := devices.DeviceFromPath(resolvedPathOnHost, deviceMapping.CgroupPermissions)
-	// if there was no error, return the device
-	if err == nil {
-		device.Path = deviceMapping.PathInContainer
-		return append(devs, specDevice(device)), append(devPermissions, specDeviceCgroup(device)), nil
-	}
-
-	// if the device is not a device node
-	// try to see if it's a directory holding many devices
-	if err == devices.ErrNotADevice {
-
-		// check if it is a directory
-		if src, e := os.Stat(resolvedPathOnHost); e == nil && src.IsDir() {
-
-			// mount the internal devices recursively
-			filepath.Walk(resolvedPathOnHost, func(dpath string, f os.FileInfo, e error) error {
-				childDevice, e := devices.DeviceFromPath(dpath, deviceMapping.CgroupPermissions)
-				if e != nil {
-					// ignore the device
-					return nil
-				}
-
-				// add the device to userSpecified devices
-				childDevice.Path = strings.Replace(dpath, resolvedPathOnHost, deviceMapping.PathInContainer, 1)
-				devs = append(devs, specDevice(childDevice))
-				devPermissions = append(devPermissions, specDeviceCgroup(childDevice))
-
-				return nil
-			})
-		}
-	}
-
-	if len(devs) > 0 {
-		return devs, devPermissions, nil
-	}
-
-	return devs, devPermissions, fmt.Errorf("error gathering device information while adding custom device %q: %s", deviceMapping.PathOnHost, err)
-}
-
 func detachMounted(path string) error {
 	return syscall.Unmount(path, syscall.MNT_DETACH)
 }

+ 5 - 17
daemon/oci_linux.go

@@ -88,7 +88,7 @@ func setDevices(s *specs.Spec, c *container.Container) error {
 			return err
 		}
 		for _, d := range hostDevices {
-			devs = append(devs, specDevice(d))
+			devs = append(devs, oci.Device(d))
 		}
 		rwm := "rwm"
 		devPermissions = []specs.DeviceCgroup{
@@ -99,7 +99,7 @@ func setDevices(s *specs.Spec, c *container.Container) error {
 		}
 	} else {
 		for _, deviceMapping := range c.HostConfig.Devices {
-			d, dPermissions, err := getDevicesFromPath(deviceMapping)
+			d, dPermissions, err := oci.DevicesFromPath(deviceMapping.PathOnHost, deviceMapping.PathInContainer, deviceMapping.CgroupPermissions)
 			if err != nil {
 				return err
 			}
@@ -221,18 +221,6 @@ func setCapabilities(s *specs.Spec, c *container.Container) error {
 	return nil
 }
 
-func delNamespace(s *specs.Spec, nsType specs.NamespaceType) {
-	idx := -1
-	for i, n := range s.Linux.Namespaces {
-		if n.Type == nsType {
-			idx = i
-		}
-	}
-	if idx >= 0 {
-		s.Linux.Namespaces = append(s.Linux.Namespaces[:idx], s.Linux.Namespaces[idx+1:]...)
-	}
-}
-
 func setNamespaces(daemon *Daemon, s *specs.Spec, c *container.Container) error {
 	userNS := false
 	// user
@@ -283,7 +271,7 @@ func setNamespaces(daemon *Daemon, s *specs.Spec, c *container.Container) error
 			setNamespace(s, nsUser)
 		}
 	} else if c.HostConfig.IpcMode.IsHost() {
-		delNamespace(s, specs.NamespaceType("ipc"))
+		oci.RemoveNamespace(s, specs.NamespaceType("ipc"))
 	} else {
 		ns := specs.Namespace{Type: "ipc"}
 		setNamespace(s, ns)
@@ -304,14 +292,14 @@ func setNamespaces(daemon *Daemon, s *specs.Spec, c *container.Container) error
 			setNamespace(s, nsUser)
 		}
 	} else if c.HostConfig.PidMode.IsHost() {
-		delNamespace(s, specs.NamespaceType("pid"))
+		oci.RemoveNamespace(s, specs.NamespaceType("pid"))
 	} else {
 		ns := specs.Namespace{Type: "pid"}
 		setNamespace(s, ns)
 	}
 	// uts
 	if c.HostConfig.UTSMode.IsHost() {
-		delNamespace(s, specs.NamespaceType("uts"))
+		oci.RemoveNamespace(s, specs.NamespaceType("uts"))
 		s.Hostname = ""
 	}
 

+ 22 - 20
docs/extend/config.md

@@ -16,6 +16,7 @@ keywords: "API, Usage, plugins, documentation, developer"
      will be rejected.
 -->
 
+
 # Plugin Config Version 0 of Plugin V2
 
 This document outlines the format of the V0 plugin configuration. The plugin
@@ -85,10 +86,6 @@ Config provides the base accessible fields for working with V0 plugin format
       	- **host**
       	- **none**
 
-- **`capabilities`** *array*
-
-   capabilities of the plugin (*Linux only*), see list [`here`](https://github.com/opencontainers/runc/blob/master/libcontainer/SPEC.md#security)
-
 - **`mounts`** *PluginMount array*
 
    mount of the plugin, struct consisting of the following fields, see [`MOUNTS`](https://github.com/opencontainers/runtime-spec/blob/master/config.md#mounts)
@@ -117,22 +114,6 @@ Config provides the base accessible fields for working with V0 plugin format
 
 	  options of the mount.
 
-- **`devices`** *PluginDevice array*
-
-    device of the plugin, (*Linux only*), struct consisting of the following fields, see [`DEVICES`](https://github.com/opencontainers/runtime-spec/blob/master/config-linux.md#devices)
-
-    - **`name`** *string*
-
-	  name of the device.
-
-    - **`description`** *string*
-
-      description of the device.
-
-    - **`path`** *string*
-
-	  path of the device.
-
 - **`env`** *PluginEnv array*
 
    env of the plugin, struct consisting of the following fields
@@ -165,6 +146,27 @@ Config provides the base accessible fields for working with V0 plugin format
 
 	  values of the args.
 
+- **`linux`** *PluginLinux*
+
+    - **`capabilities`** *string array*
+
+          capabilities of the plugin (*Linux only*), see list [`here`](https://github.com/opencontainers/runc/blob/master/libcontainer/SPEC.md#security)
+
+    - **`devices`** *PluginDevice array*
+
+          device of the plugin, (*Linux only*), struct consisting of the following fields, see [`DEVICES`](https://github.com/opencontainers/runtime-spec/blob/master/config-linux.md#devices)
+
+          - **`name`** *string*
+
+	      name of the device.
+
+          - **`description`** *string*
+
+              description of the device.
+
+          - **`path`** *string*
+
+              path of the device.
 
 ## Example Config
 

+ 24 - 26
integration-cli/docker_cli_daemon_plugins_test.go

@@ -13,8 +13,6 @@ import (
 	"github.com/go-check/check"
 )
 
-var pluginName = "tiborvass/no-remove"
-
 // TestDaemonRestartWithPluginEnabled tests state restore for an enabled plugin
 func (s *DockerDaemonSuite) TestDaemonRestartWithPluginEnabled(c *check.C) {
 	testRequires(c, Network)
@@ -23,15 +21,15 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithPluginEnabled(c *check.C) {
 		c.Fatalf("Could not start daemon: %v", err)
 	}
 
-	if out, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", pluginName); err != nil {
+	if out, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", pName); err != nil {
 		c.Fatalf("Could not install plugin: %v %s", err, out)
 	}
 
 	defer func() {
-		if out, err := s.d.Cmd("plugin", "disable", pluginName); err != nil {
+		if out, err := s.d.Cmd("plugin", "disable", pName); err != nil {
 			c.Fatalf("Could not disable plugin: %v %s", err, out)
 		}
-		if out, err := s.d.Cmd("plugin", "remove", pluginName); err != nil {
+		if out, err := s.d.Cmd("plugin", "remove", pName); err != nil {
 			c.Fatalf("Could not remove plugin: %v %s", err, out)
 		}
 	}()
@@ -44,7 +42,7 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithPluginEnabled(c *check.C) {
 	if err != nil {
 		c.Fatalf("Could not list plugins: %v %s", err, out)
 	}
-	c.Assert(out, checker.Contains, pluginName)
+	c.Assert(out, checker.Contains, pName)
 	c.Assert(out, checker.Contains, "true")
 }
 
@@ -56,12 +54,12 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithPluginDisabled(c *check.C) {
 		c.Fatalf("Could not start daemon: %v", err)
 	}
 
-	if out, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", pluginName, "--disable"); err != nil {
+	if out, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", pName, "--disable"); err != nil {
 		c.Fatalf("Could not install plugin: %v %s", err, out)
 	}
 
 	defer func() {
-		if out, err := s.d.Cmd("plugin", "remove", pluginName); err != nil {
+		if out, err := s.d.Cmd("plugin", "remove", pName); err != nil {
 			c.Fatalf("Could not remove plugin: %v %s", err, out)
 		}
 	}()
@@ -74,7 +72,7 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithPluginDisabled(c *check.C) {
 	if err != nil {
 		c.Fatalf("Could not list plugins: %v %s", err, out)
 	}
-	c.Assert(out, checker.Contains, pluginName)
+	c.Assert(out, checker.Contains, pName)
 	c.Assert(out, checker.Contains, "false")
 }
 
@@ -86,17 +84,17 @@ func (s *DockerDaemonSuite) TestDaemonKillLiveRestoreWithPlugins(c *check.C) {
 	if err := s.d.Start("--live-restore"); err != nil {
 		c.Fatalf("Could not start daemon: %v", err)
 	}
-	if out, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", pluginName); err != nil {
+	if out, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", pName); err != nil {
 		c.Fatalf("Could not install plugin: %v %s", err, out)
 	}
 	defer func() {
 		if err := s.d.Restart("--live-restore"); err != nil {
 			c.Fatalf("Could not restart daemon: %v", err)
 		}
-		if out, err := s.d.Cmd("plugin", "disable", pluginName); err != nil {
+		if out, err := s.d.Cmd("plugin", "disable", pName); err != nil {
 			c.Fatalf("Could not disable plugin: %v %s", err, out)
 		}
-		if out, err := s.d.Cmd("plugin", "remove", pluginName); err != nil {
+		if out, err := s.d.Cmd("plugin", "remove", pName); err != nil {
 			c.Fatalf("Could not remove plugin: %v %s", err, out)
 		}
 	}()
@@ -105,7 +103,7 @@ func (s *DockerDaemonSuite) TestDaemonKillLiveRestoreWithPlugins(c *check.C) {
 		c.Fatalf("Could not kill daemon: %v", err)
 	}
 
-	cmd := exec.Command("pgrep", "-f", "plugin-no-remove")
+	cmd := exec.Command("pgrep", "-f", pluginProcessName)
 	if out, ec, err := runCommandWithOutput(cmd); ec != 0 {
 		c.Fatalf("Expected exit code '0', got %d err: %v output: %s ", ec, err, out)
 	}
@@ -119,17 +117,17 @@ func (s *DockerDaemonSuite) TestDaemonShutdownLiveRestoreWithPlugins(c *check.C)
 	if err := s.d.Start("--live-restore"); err != nil {
 		c.Fatalf("Could not start daemon: %v", err)
 	}
-	if out, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", pluginName); err != nil {
+	if out, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", pName); err != nil {
 		c.Fatalf("Could not install plugin: %v %s", err, out)
 	}
 	defer func() {
 		if err := s.d.Restart("--live-restore"); err != nil {
 			c.Fatalf("Could not restart daemon: %v", err)
 		}
-		if out, err := s.d.Cmd("plugin", "disable", pluginName); err != nil {
+		if out, err := s.d.Cmd("plugin", "disable", pName); err != nil {
 			c.Fatalf("Could not disable plugin: %v %s", err, out)
 		}
-		if out, err := s.d.Cmd("plugin", "remove", pluginName); err != nil {
+		if out, err := s.d.Cmd("plugin", "remove", pName); err != nil {
 			c.Fatalf("Could not remove plugin: %v %s", err, out)
 		}
 	}()
@@ -138,7 +136,7 @@ func (s *DockerDaemonSuite) TestDaemonShutdownLiveRestoreWithPlugins(c *check.C)
 		c.Fatalf("Could not kill daemon: %v", err)
 	}
 
-	cmd := exec.Command("pgrep", "-f", "plugin-no-remove")
+	cmd := exec.Command("pgrep", "-f", pluginProcessName)
 	if out, ec, err := runCommandWithOutput(cmd); ec != 0 {
 		c.Fatalf("Expected exit code '0', got %d err: %v output: %s ", ec, err, out)
 	}
@@ -151,7 +149,7 @@ func (s *DockerDaemonSuite) TestDaemonShutdownWithPlugins(c *check.C) {
 	if err := s.d.Start(); err != nil {
 		c.Fatalf("Could not start daemon: %v", err)
 	}
-	if out, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", pluginName); err != nil {
+	if out, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", pName); err != nil {
 		c.Fatalf("Could not install plugin: %v %s", err, out)
 	}
 
@@ -159,10 +157,10 @@ func (s *DockerDaemonSuite) TestDaemonShutdownWithPlugins(c *check.C) {
 		if err := s.d.Restart(); err != nil {
 			c.Fatalf("Could not restart daemon: %v", err)
 		}
-		if out, err := s.d.Cmd("plugin", "disable", pluginName); err != nil {
+		if out, err := s.d.Cmd("plugin", "disable", pName); err != nil {
 			c.Fatalf("Could not disable plugin: %v %s", err, out)
 		}
-		if out, err := s.d.Cmd("plugin", "remove", pluginName); err != nil {
+		if out, err := s.d.Cmd("plugin", "remove", pName); err != nil {
 			c.Fatalf("Could not remove plugin: %v %s", err, out)
 		}
 	}()
@@ -177,7 +175,7 @@ func (s *DockerDaemonSuite) TestDaemonShutdownWithPlugins(c *check.C) {
 		}
 	}
 
-	cmd := exec.Command("pgrep", "-f", "plugin-no-remove")
+	cmd := exec.Command("pgrep", "-f", pluginProcessName)
 	if out, ec, err := runCommandWithOutput(cmd); ec != 1 {
 		c.Fatalf("Expected exit code '1', got %d err: %v output: %s ", ec, err, out)
 	}
@@ -195,20 +193,20 @@ func (s *DockerDaemonSuite) TestVolumePlugin(c *check.C) {
 	if err := s.d.Start(); err != nil {
 		c.Fatalf("Could not start daemon: %v", err)
 	}
-	out, err := s.d.Cmd("plugin", "install", pluginName, "--grant-all-permissions")
+	out, err := s.d.Cmd("plugin", "install", pName, "--grant-all-permissions")
 	if err != nil {
 		c.Fatalf("Could not install plugin: %v %s", err, out)
 	}
 	defer func() {
-		if out, err := s.d.Cmd("plugin", "disable", pluginName); err != nil {
+		if out, err := s.d.Cmd("plugin", "disable", pName); err != nil {
 			c.Fatalf("Could not disable plugin: %v %s", err, out)
 		}
-		if out, err := s.d.Cmd("plugin", "remove", pluginName); err != nil {
+		if out, err := s.d.Cmd("plugin", "remove", pName); err != nil {
 			c.Fatalf("Could not remove plugin: %v %s", err, out)
 		}
 	}()
 
-	out, err = s.d.Cmd("volume", "create", "-d", pluginName, volName)
+	out, err = s.d.Cmd("volume", "create", "-d", pName, volName)
 	if err != nil {
 		c.Fatalf("Could not create volume: %v %s", err, out)
 	}
@@ -223,7 +221,7 @@ func (s *DockerDaemonSuite) TestVolumePlugin(c *check.C) {
 		c.Fatalf("Could not list volume: %v %s", err, out)
 	}
 	c.Assert(out, checker.Contains, volName)
-	c.Assert(out, checker.Contains, pluginName)
+	c.Assert(out, checker.Contains, pName)
 
 	mountPoint, err := s.d.Cmd("volume", "inspect", volName, "--format", "{{.Mountpoint}}")
 	if err != nil {

+ 4 - 5
integration-cli/docker_cli_events_test.go

@@ -278,12 +278,11 @@ func (s *DockerSuite) TestEventsImageLoad(c *check.C) {
 func (s *DockerSuite) TestEventsPluginOps(c *check.C) {
 	testRequires(c, DaemonIsLinux)
 
-	pluginName := "tiborvass/no-remove:latest"
 	since := daemonUnixTime(c)
 
-	dockerCmd(c, "plugin", "install", pluginName, "--grant-all-permissions")
-	dockerCmd(c, "plugin", "disable", pluginName)
-	dockerCmd(c, "plugin", "remove", pluginName)
+	dockerCmd(c, "plugin", "install", pNameWithTag, "--grant-all-permissions")
+	dockerCmd(c, "plugin", "disable", pNameWithTag)
+	dockerCmd(c, "plugin", "remove", pNameWithTag)
 
 	out, _ := dockerCmd(c, "events", "--since", since, "--until", daemonUnixTime(c))
 	events := strings.Split(out, "\n")
@@ -292,7 +291,7 @@ func (s *DockerSuite) TestEventsPluginOps(c *check.C) {
 	nEvents := len(events)
 	c.Assert(nEvents, checker.GreaterOrEqualThan, 4)
 
-	pluginEvents := eventActionsByIDAndType(c, events, pluginName, "plugin")
+	pluginEvents := eventActionsByIDAndType(c, events, pNameWithTag, "plugin")
 	c.Assert(pluginEvents, checker.HasLen, 4, check.Commentf("events: %v", events))
 
 	c.Assert(pluginEvents[0], checker.Equals, "pull", check.Commentf(out))

+ 1 - 1
integration-cli/docker_cli_network_unix_test.go

@@ -774,7 +774,7 @@ func (s *DockerNetworkSuite) TestDockerPluginV2NetworkDriver(c *check.C) {
 	testRequires(c, DaemonIsLinux, Network, IsAmd64)
 
 	var (
-		npName        = "mavenugo/test-docker-netplugin"
+		npName        = "tiborvass/test-docker-netplugin"
 		npTag         = "latest"
 		npNameWithTag = npName + ":" + npTag
 	)

+ 4 - 3
integration-cli/docker_cli_plugins_test.go

@@ -10,9 +10,10 @@ import (
 )
 
 var (
-	pName        = "tiborvass/no-remove"
-	pTag         = "latest"
-	pNameWithTag = pName + ":" + pTag
+	pluginProcessName = "no-remove"
+	pName             = "tiborvass/no-remove"
+	pTag              = "latest"
+	pNameWithTag      = pName + ":" + pTag
 )
 
 func (s *DockerSuite) TestPluginBasicOps(c *check.C) {

+ 86 - 0
oci/devices_linux.go

@@ -0,0 +1,86 @@
+package oci
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/opencontainers/runc/libcontainer/configs"
+	"github.com/opencontainers/runc/libcontainer/devices"
+	specs "github.com/opencontainers/runtime-spec/specs-go"
+)
+
+// Device transforms a libcontainer configs.Device to a specs.Device object.
+func Device(d *configs.Device) specs.Device {
+	return specs.Device{
+		Type:     string(d.Type),
+		Path:     d.Path,
+		Major:    d.Major,
+		Minor:    d.Minor,
+		FileMode: fmPtr(int64(d.FileMode)),
+		UID:      u32Ptr(int64(d.Uid)),
+		GID:      u32Ptr(int64(d.Gid)),
+	}
+}
+
+func deviceCgroup(d *configs.Device) specs.DeviceCgroup {
+	t := string(d.Type)
+	return specs.DeviceCgroup{
+		Allow:  true,
+		Type:   &t,
+		Major:  &d.Major,
+		Minor:  &d.Minor,
+		Access: &d.Permissions,
+	}
+}
+
+// DevicesFromPath computes a list of devices and device permissions from paths (pathOnHost and pathInContainer) and cgroup permissions.
+func DevicesFromPath(pathOnHost, pathInContainer, cgroupPermissions string) (devs []specs.Device, devPermissions []specs.DeviceCgroup, err error) {
+	resolvedPathOnHost := pathOnHost
+
+	// check if it is a symbolic link
+	if src, e := os.Lstat(pathOnHost); e == nil && src.Mode()&os.ModeSymlink == os.ModeSymlink {
+		if linkedPathOnHost, e := filepath.EvalSymlinks(pathOnHost); e == nil {
+			resolvedPathOnHost = linkedPathOnHost
+		}
+	}
+
+	device, err := devices.DeviceFromPath(resolvedPathOnHost, cgroupPermissions)
+	// if there was no error, return the device
+	if err == nil {
+		device.Path = pathInContainer
+		return append(devs, Device(device)), append(devPermissions, deviceCgroup(device)), nil
+	}
+
+	// if the device is not a device node
+	// try to see if it's a directory holding many devices
+	if err == devices.ErrNotADevice {
+
+		// check if it is a directory
+		if src, e := os.Stat(resolvedPathOnHost); e == nil && src.IsDir() {
+
+			// mount the internal devices recursively
+			filepath.Walk(resolvedPathOnHost, func(dpath string, f os.FileInfo, e error) error {
+				childDevice, e := devices.DeviceFromPath(dpath, cgroupPermissions)
+				if e != nil {
+					// ignore the device
+					return nil
+				}
+
+				// add the device to userSpecified devices
+				childDevice.Path = strings.Replace(dpath, resolvedPathOnHost, pathInContainer, 1)
+				devs = append(devs, Device(childDevice))
+				devPermissions = append(devPermissions, deviceCgroup(childDevice))
+
+				return nil
+			})
+		}
+	}
+
+	if len(devs) > 0 {
+		return devs, devPermissions, nil
+	}
+
+	return devs, devPermissions, fmt.Errorf("error gathering device information while adding custom device %q: %s", pathOnHost, err)
+}

+ 20 - 0
oci/devices_unsupported.go

@@ -0,0 +1,20 @@
+// +build !linux
+
+package oci
+
+import (
+	"errors"
+
+	"github.com/opencontainers/runc/libcontainer/configs"
+	specs "github.com/opencontainers/runtime-spec/specs-go"
+)
+
+// Device transforms a libcontainer configs.Device to a specs.Device object.
+// Not implemented
+func Device(d *configs.Device) specs.Device { return specs.Device{} }
+
+// DevicesFromPath computes a list of devices and device permissions from paths (pathOnHost and pathInContainer) and cgroup permissions.
+// Not implemented
+func DevicesFromPath(pathOnHost, pathInContainer, cgroupPermissions string) (devs []specs.Device, devPermissions []specs.DeviceCgroup, err error) {
+	return nil, nil, errors.New("oci/devices: unsupported platform")
+}

+ 16 - 0
oci/namespaces.go

@@ -0,0 +1,16 @@
+package oci
+
+import specs "github.com/opencontainers/runtime-spec/specs-go"
+
+// RemoveNamespace removes the `nsType` namespace from OCI spec `s`
+func RemoveNamespace(s *specs.Spec, nsType specs.NamespaceType) {
+	idx := -1
+	for i, n := range s.Linux.Namespaces {
+		if n.Type == nsType {
+			idx = i
+		}
+	}
+	if idx >= 0 {
+		s.Linux.Namespaces = append(s.Linux.Namespaces[:idx], s.Linux.Namespaces[idx+1:]...)
+	}
+}

+ 8 - 3
plugin/store/store.go

@@ -126,7 +126,7 @@ func (ps *Store) updatePluginDB() error {
 	return nil
 }
 
-// Get returns a plugin matching the given name and capability.
+// Get returns an enabled plugin matching the given name and capability.
 func (ps *Store) Get(name, capability string, mode int) (plugingetter.CompatPlugin, error) {
 	var (
 		p   *v2.Plugin
@@ -151,7 +151,12 @@ func (ps *Store) Get(name, capability string, mode int) (plugingetter.CompatPlug
 			p.Lock()
 			p.RefCount += mode
 			p.Unlock()
-			return p.FilterByCap(capability)
+			if p.IsEnabled() {
+				return p.FilterByCap(capability)
+			}
+			// Plugin was found but it is disabled, so we should not fall back to legacy plugins
+			// but we should error out right away
+			return nil, ErrNotFound(fullName)
 		}
 		if _, ok := err.(ErrNotFound); !ok {
 			return nil, err
@@ -170,7 +175,7 @@ func (ps *Store) Get(name, capability string, mode int) (plugingetter.CompatPlug
 	return nil, err
 }
 
-// GetAllByCap returns a list of plugins matching the given capability.
+// GetAllByCap returns a list of enabled plugins matching the given capability.
 func (ps *Store) GetAllByCap(capability string) ([]plugingetter.CompatPlugin, error) {
 	result := make([]plugingetter.CompatPlugin, 0, 1)
 

+ 79 - 19
plugin/v2/plugin.go

@@ -9,6 +9,7 @@ import (
 	"sync"
 
 	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/oci"
 	"github.com/docker/docker/pkg/plugins"
 	"github.com/docker/docker/pkg/system"
 	specs "github.com/opencontainers/runtime-spec/specs-go"
@@ -104,6 +105,8 @@ func (p *Plugin) InitPlugin() error {
 		p.PluginObj.Settings.Mounts[i] = mount
 	}
 	p.PluginObj.Settings.Env = make([]string, 0, len(p.PluginObj.Config.Env))
+	p.PluginObj.Settings.Devices = make([]types.PluginDevice, 0, len(p.PluginObj.Config.Linux.Devices))
+	copy(p.PluginObj.Settings.Devices, p.PluginObj.Config.Linux.Devices)
 	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))
@@ -176,7 +179,7 @@ next:
 		}
 
 		// range over all the devices in the config
-		for _, device := range p.PluginObj.Config.Devices {
+		for _, device := range p.PluginObj.Config.Linux.Devices {
 			// found the device in the config
 			if device.Name == s.name {
 				// is it settable ?
@@ -216,38 +219,45 @@ next:
 // ComputePrivileges takes the config file and computes the list of access necessary
 // for the plugin on the host.
 func (p *Plugin) ComputePrivileges() types.PluginPrivileges {
-	m := p.PluginObj.Config
+	c := p.PluginObj.Config
 	var privileges types.PluginPrivileges
-	if m.Network.Type != "null" && m.Network.Type != "bridge" {
+	if c.Network.Type != "null" && c.Network.Type != "bridge" {
 		privileges = append(privileges, types.PluginPrivilege{
 			Name:        "network",
-			Description: "",
-			Value:       []string{m.Network.Type},
+			Description: "permissions to access a network",
+			Value:       []string{c.Network.Type},
 		})
 	}
-	for _, mount := range m.Mounts {
+	for _, mount := range c.Mounts {
 		if mount.Source != nil {
 			privileges = append(privileges, types.PluginPrivilege{
 				Name:        "mount",
-				Description: "",
+				Description: "host path to mount",
 				Value:       []string{*mount.Source},
 			})
 		}
 	}
-	for _, device := range m.Devices {
+	for _, device := range c.Linux.Devices {
 		if device.Path != nil {
 			privileges = append(privileges, types.PluginPrivilege{
 				Name:        "device",
-				Description: "",
+				Description: "host device to access",
 				Value:       []string{*device.Path},
 			})
 		}
 	}
-	if len(m.Capabilities) > 0 {
+	if c.Linux.DeviceCreation {
+		privileges = append(privileges, types.PluginPrivilege{
+			Name:        "device-creation",
+			Description: "allow creating devices inside plugin",
+			Value:       []string{"true"},
+		})
+	}
+	if len(c.Linux.Capabilities) > 0 {
 		privileges = append(privileges, types.PluginPrivilege{
 			Name:        "capabilities",
-			Description: "",
-			Value:       m.Capabilities,
+			Description: "list of additional capabilities required",
+			Value:       c.Linux.Capabilities,
 		})
 	}
 	return privileges
@@ -293,12 +303,40 @@ func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
 		Readonly: false, // TODO: all plugins should be readonly? settable in config?
 	}
 
-	mounts := append(p.PluginObj.Settings.Mounts, types.PluginMount{
+	userMounts := make(map[string]struct{}, len(p.PluginObj.Config.Mounts))
+	for _, m := range p.PluginObj.Config.Mounts {
+		userMounts[m.Destination] = struct{}{}
+	}
+
+	mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{
 		Source:      &p.RuntimeSourcePath,
 		Destination: defaultPluginRuntimeDestination,
 		Type:        "bind",
 		Options:     []string{"rbind", "rshared"},
 	})
+
+	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" {
+			oci.RemoveNamespace(&s, specs.NamespaceType("network"))
+		}
+		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"},
+			})
+	}
+
 	for _, mount := range mounts {
 		m := specs.Mount{
 			Destination: mount.Destination,
@@ -323,6 +361,28 @@ func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
 		s.Mounts = append(s.Mounts, m)
 	}
 
+	for i, m := range s.Mounts {
+		if strings.HasPrefix(m.Destination, "/dev/") {
+			if _, ok := userMounts[m.Destination]; ok {
+				s.Mounts = append(s.Mounts[:i], s.Mounts[i+1:]...)
+			}
+		}
+	}
+
+	if p.PluginObj.Config.Linux.DeviceCreation {
+		rwm := "rwm"
+		s.Linux.Resources.Devices = []specs.DeviceCgroup{{Allow: true, Access: &rwm}}
+	}
+	for _, dev := range p.PluginObj.Config.Linux.Devices {
+		path := *dev.Path
+		d, dPermissions, err := oci.DevicesFromPath(path, path, "rwm")
+		if err != nil {
+			return nil, err
+		}
+		s.Linux.Devices = append(s.Linux.Devices, d...)
+		s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, dPermissions...)
+	}
+
 	envs := make([]string, 1, len(p.PluginObj.Settings.Env)+1)
 	envs[0] = "PATH=" + system.DefaultPathEnv
 	envs = append(envs, p.PluginObj.Settings.Env...)
@@ -332,12 +392,12 @@ func (p *Plugin) InitSpec(s specs.Spec, libRoot string) (*specs.Spec, error) {
 	if len(cwd) == 0 {
 		cwd = "/"
 	}
-	s.Process = specs.Process{
-		Terminal: false,
-		Args:     args,
-		Cwd:      cwd,
-		Env:      envs,
-	}
+	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...)
 
 	return &s, nil
 }

+ 1 - 1
vendor.conf

@@ -17,7 +17,7 @@ github.com/vdemeester/shakers 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
 golang.org/x/net 2beffdc2e92c8a3027590f898fe88f69af48a3f8 https://github.com/tonistiigi/net.git
 golang.org/x/sys 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9
 github.com/docker/go-units 8a7beacffa3009a9ac66bad506b18ffdd110cf97
-github.com/docker/go-connections f512407a188ecb16f31a33dbc9c4e4814afc1b03
+github.com/docker/go-connections 4ccf312bf1d35e5dbda654e57a9be4c3f3cd0366
 
 github.com/RackSec/srslog 456df3a81436d29ba874f3590eeeee25d666f8a5
 github.com/imdario/mergo 0.2.1

+ 6 - 10
vendor/github.com/docker/go-connections/sockets/sockets.go

@@ -2,6 +2,7 @@
 package sockets
 
 import (
+	"errors"
 	"net"
 	"net/http"
 	"time"
@@ -10,6 +11,9 @@ import (
 // Why 32? See https://github.com/docker/docker/pull/8035.
 const defaultTimeout = 32 * time.Second
 
+// ErrProtocolNotAvailable is returned when a given transport protocol is not provided by the operating system.
+var ErrProtocolNotAvailable = errors.New("protocol not available")
+
 // ConfigureTransport configures the specified Transport according to the
 // specified proto and addr.
 // If the proto is unix (using a unix socket to communicate) or npipe the
@@ -17,17 +21,9 @@ const defaultTimeout = 32 * time.Second
 func ConfigureTransport(tr *http.Transport, proto, addr string) error {
 	switch proto {
 	case "unix":
-		// No need for compression in local communications.
-		tr.DisableCompression = true
-		tr.Dial = func(_, _ string) (net.Conn, error) {
-			return net.DialTimeout(proto, addr, defaultTimeout)
-		}
+		return configureUnixTransport(tr, proto, addr)
 	case "npipe":
-		// No need for compression in local communications.
-		tr.DisableCompression = true
-		tr.Dial = func(_, _ string) (net.Conn, error) {
-			return DialPipe(addr, defaultTimeout)
-		}
+		return configureNpipeTransport(tr, proto, addr)
 	default:
 		tr.Proxy = http.ProxyFromEnvironment
 		dialer, err := DialerFromEnvironment(&net.Dialer{

+ 20 - 0
vendor/github.com/docker/go-connections/sockets/sockets_unix.go

@@ -3,11 +3,31 @@
 package sockets
 
 import (
+	"fmt"
 	"net"
+	"net/http"
 	"syscall"
 	"time"
 )
 
+const maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path)
+
+func configureUnixTransport(tr *http.Transport, proto, addr string) error {
+	if len(addr) > maxUnixSocketPathSize {
+		return fmt.Errorf("Unix socket path %q is too long", addr)
+	}
+	// No need for compression in local communications.
+	tr.DisableCompression = true
+	tr.Dial = func(_, _ string) (net.Conn, error) {
+		return net.DialTimeout(proto, addr, defaultTimeout)
+	}
+	return nil
+}
+
+func configureNpipeTransport(tr *http.Transport, proto, addr string) error {
+	return ErrProtocolNotAvailable
+}
+
 // DialPipe connects to a Windows named pipe.
 // This is not supported on other OSes.
 func DialPipe(_ string, _ time.Duration) (net.Conn, error) {

+ 14 - 0
vendor/github.com/docker/go-connections/sockets/sockets_windows.go

@@ -2,11 +2,25 @@ package sockets
 
 import (
 	"net"
+	"net/http"
 	"time"
 
 	"github.com/Microsoft/go-winio"
 )
 
+func configureUnixTransport(tr *http.Transport, proto, addr string) error {
+	return ErrProtocolNotAvailable
+}
+
+func configureNpipeTransport(tr *http.Transport, proto, addr string) error {
+	// No need for compression in local communications.
+	tr.DisableCompression = true
+	tr.Dial = func(_, _ string) (net.Conn, error) {
+		return DialPipe(addr, defaultTimeout)
+	}
+	return nil
+}
+
 // DialPipe connects to a Windows named pipe.
 func DialPipe(addr string, timeout time.Duration) (net.Conn, error) {
 	return winio.DialPipe(addr, &timeout)