mirror of https://github.com/docker/cli.git
Add --device support for Windows
Adds support for --device in Windows. This must take the form of: --device='class/clsid'. See this post for more information: https://blogs.technet.microsoft.com/virtualization/2018/08/13/bringing-device-support-to-windows-server-containers/ Signed-off-by: John Howard <jhoward@microsoft.com>
This commit is contained in:
parent
896ff57b30
commit
593acf077b
|
@ -72,7 +72,7 @@ func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, options *createOptio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
copts.env = *opts.NewListOptsRef(&newEnv, nil)
|
copts.env = *opts.NewListOptsRef(&newEnv, nil)
|
||||||
containerConfig, err := parse(flags, copts)
|
containerConfig, err := parse(flags, copts, dockerCli.ServerInfo().OSType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
reportError(dockerCli.Err(), "create", err.Error(), true)
|
reportError(dockerCli.Err(), "create", err.Error(), true)
|
||||||
return cli.StatusError{StatusCode: 125}
|
return cli.StatusError{StatusCode: 125}
|
||||||
|
|
|
@ -141,7 +141,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
|
||||||
deviceReadIOps: opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice),
|
deviceReadIOps: opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice),
|
||||||
deviceWriteBps: opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice),
|
deviceWriteBps: opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice),
|
||||||
deviceWriteIOps: opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice),
|
deviceWriteIOps: opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice),
|
||||||
devices: opts.NewListOpts(validateDevice),
|
devices: opts.NewListOpts(nil), // devices can only be validated after we know the server OS
|
||||||
env: opts.NewListOpts(opts.ValidateEnv),
|
env: opts.NewListOpts(opts.ValidateEnv),
|
||||||
envFile: opts.NewListOpts(nil),
|
envFile: opts.NewListOpts(nil),
|
||||||
expose: opts.NewListOpts(nil),
|
expose: opts.NewListOpts(nil),
|
||||||
|
@ -299,7 +299,7 @@ type containerConfig struct {
|
||||||
// a HostConfig and returns them with the specified command.
|
// a HostConfig and returns them with the specified command.
|
||||||
// If the specified args are not valid, it will return an error.
|
// If the specified args are not valid, it will return an error.
|
||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, error) {
|
func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*containerConfig, error) {
|
||||||
var (
|
var (
|
||||||
attachStdin = copts.attach.Get("stdin")
|
attachStdin = copts.attach.Get("stdin")
|
||||||
attachStdout = copts.attach.Get("stdout")
|
attachStdout = copts.attach.Get("stdout")
|
||||||
|
@ -417,10 +417,22 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse device mappings
|
// validate and parse device mappings. Note we do late validation of the
|
||||||
|
// device path (as opposed to during flag parsing), as at the time we are
|
||||||
|
// parsing flags, we haven't yet sent a _ping to the daemon to determine
|
||||||
|
// what operating system it is.
|
||||||
deviceMappings := []container.DeviceMapping{}
|
deviceMappings := []container.DeviceMapping{}
|
||||||
for _, device := range copts.devices.GetAll() {
|
for _, device := range copts.devices.GetAll() {
|
||||||
deviceMapping, err := parseDevice(device)
|
var (
|
||||||
|
validated string
|
||||||
|
deviceMapping container.DeviceMapping
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
validated, err = validateDevice(device, serverOS)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
deviceMapping, err = parseDevice(validated, serverOS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -747,7 +759,19 @@ func parseStorageOpts(storageOpts []string) (map[string]string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseDevice parses a device mapping string to a container.DeviceMapping struct
|
// parseDevice parses a device mapping string to a container.DeviceMapping struct
|
||||||
func parseDevice(device string) (container.DeviceMapping, error) {
|
func parseDevice(device, serverOS string) (container.DeviceMapping, error) {
|
||||||
|
switch serverOS {
|
||||||
|
case "linux":
|
||||||
|
return parseLinuxDevice(device)
|
||||||
|
case "windows":
|
||||||
|
return parseWindowsDevice(device)
|
||||||
|
}
|
||||||
|
return container.DeviceMapping{}, errors.Errorf("unknown server OS: %s", serverOS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseLinuxDevice parses a device mapping string to a container.DeviceMapping struct
|
||||||
|
// knowing that the target is a Linux daemon
|
||||||
|
func parseLinuxDevice(device string) (container.DeviceMapping, error) {
|
||||||
src := ""
|
src := ""
|
||||||
dst := ""
|
dst := ""
|
||||||
permissions := "rwm"
|
permissions := "rwm"
|
||||||
|
@ -781,6 +805,12 @@ func parseDevice(device string) (container.DeviceMapping, error) {
|
||||||
return deviceMapping, nil
|
return deviceMapping, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseWindowsDevice parses a device mapping string to a container.DeviceMapping struct
|
||||||
|
// knowing that the target is a Windows daemon
|
||||||
|
func parseWindowsDevice(device string) (container.DeviceMapping, error) {
|
||||||
|
return container.DeviceMapping{PathOnHost: device}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// validateDeviceCgroupRule validates a device cgroup rule string format
|
// validateDeviceCgroupRule validates a device cgroup rule string format
|
||||||
// It will make sure 'val' is in the form:
|
// It will make sure 'val' is in the form:
|
||||||
// 'type major:minor mode'
|
// 'type major:minor mode'
|
||||||
|
@ -813,14 +843,23 @@ func validDeviceMode(mode string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateDevice validates a path for devices
|
// validateDevice validates a path for devices
|
||||||
|
func validateDevice(val string, serverOS string) (string, error) {
|
||||||
|
switch serverOS {
|
||||||
|
case "linux":
|
||||||
|
return validateLinuxPath(val, validDeviceMode)
|
||||||
|
case "windows":
|
||||||
|
// Windows does validation entirely server-side
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
return "", errors.Errorf("unknown server OS: %s", serverOS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateLinuxPath is the implementation of validateDevice knowing that the
|
||||||
|
// target server operating system is a Linux daemon.
|
||||||
// It will make sure 'val' is in the form:
|
// It will make sure 'val' is in the form:
|
||||||
// [host-dir:]container-path[:mode]
|
// [host-dir:]container-path[:mode]
|
||||||
// It also validates the device mode.
|
// It also validates the device mode.
|
||||||
func validateDevice(val string) (string, error) {
|
func validateLinuxPath(val string, validator func(string) bool) (string, error) {
|
||||||
return validatePath(val, validDeviceMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func validatePath(val string, validator func(string) bool) (string, error) {
|
|
||||||
var containerPath string
|
var containerPath string
|
||||||
var mode string
|
var mode string
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"gotest.tools/assert"
|
"gotest.tools/assert"
|
||||||
is "gotest.tools/assert/cmp"
|
is "gotest.tools/assert/cmp"
|
||||||
|
"gotest.tools/skip"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestValidateAttach(t *testing.T) {
|
func TestValidateAttach(t *testing.T) {
|
||||||
|
@ -48,7 +49,7 @@ func parseRun(args []string) (*container.Config, *container.HostConfig, *network
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
// TODO: fix tests to accept ContainerConfig
|
// TODO: fix tests to accept ContainerConfig
|
||||||
containerConfig, err := parse(flags, copts)
|
containerConfig, err := parse(flags, copts, runtime.GOOS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -351,6 +352,7 @@ func TestParseWithExpose(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseDevice(t *testing.T) {
|
func TestParseDevice(t *testing.T) {
|
||||||
|
skip.If(t, runtime.GOOS == "windows") // Windows validates server-side
|
||||||
valids := map[string]container.DeviceMapping{
|
valids := map[string]container.DeviceMapping{
|
||||||
"/dev/snd": {
|
"/dev/snd": {
|
||||||
PathOnHost: "/dev/snd",
|
PathOnHost: "/dev/snd",
|
||||||
|
@ -393,7 +395,7 @@ func TestParseModes(t *testing.T) {
|
||||||
flags, copts := setupRunFlags()
|
flags, copts := setupRunFlags()
|
||||||
args := []string{"--pid=container:", "img", "cmd"}
|
args := []string{"--pid=container:", "img", "cmd"}
|
||||||
assert.NilError(t, flags.Parse(args))
|
assert.NilError(t, flags.Parse(args))
|
||||||
_, err := parse(flags, copts)
|
_, err := parse(flags, copts, runtime.GOOS)
|
||||||
assert.ErrorContains(t, err, "--pid: invalid PID mode")
|
assert.ErrorContains(t, err, "--pid: invalid PID mode")
|
||||||
|
|
||||||
// pid ok
|
// pid ok
|
||||||
|
@ -615,6 +617,7 @@ func TestParseEntryPoint(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateDevice(t *testing.T) {
|
func TestValidateDevice(t *testing.T) {
|
||||||
|
skip.If(t, runtime.GOOS == "windows") // Windows validates server-side
|
||||||
valid := []string{
|
valid := []string{
|
||||||
"/home",
|
"/home",
|
||||||
"/home:/home",
|
"/home:/home",
|
||||||
|
@ -649,13 +652,13 @@ func TestValidateDevice(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, path := range valid {
|
for _, path := range valid {
|
||||||
if _, err := validateDevice(path); err != nil {
|
if _, err := validateDevice(path, runtime.GOOS); err != nil {
|
||||||
t.Fatalf("ValidateDevice(`%q`) should succeed: error %q", path, err)
|
t.Fatalf("ValidateDevice(`%q`) should succeed: error %q", path, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for path, expectedError := range invalid {
|
for path, expectedError := range invalid {
|
||||||
if _, err := validateDevice(path); err == nil {
|
if _, err := validateDevice(path, runtime.GOOS); err == nil {
|
||||||
t.Fatalf("ValidateDevice(`%q`) should have failed validation", path)
|
t.Fatalf("ValidateDevice(`%q`) should have failed validation", path)
|
||||||
} else {
|
} else {
|
||||||
if err.Error() != expectedError {
|
if err.Error() != expectedError {
|
||||||
|
|
|
@ -78,7 +78,7 @@ func runRun(dockerCli command.Cli, flags *pflag.FlagSet, ropts *runOptions, copt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
copts.env = *opts.NewListOptsRef(&newEnv, nil)
|
copts.env = *opts.NewListOptsRef(&newEnv, nil)
|
||||||
containerConfig, err := parse(flags, copts)
|
containerConfig, err := parse(flags, copts, dockerCli.ServerInfo().OSType)
|
||||||
// just in case the parse does not exit
|
// just in case the parse does not exit
|
||||||
if err != nil {
|
if err != nil {
|
||||||
reportError(dockerCli.Err(), "run", err.Error(), true)
|
reportError(dockerCli.Err(), "run", err.Error(), true)
|
||||||
|
|
Loading…
Reference in New Issue