Skip to content

Commit 57b79cb

Browse files
committed
Merge pull request #889 from docker/725-devices-format
Improve host devices support
2 parents 2f2d50d + f9b04c1 commit 57b79cb

File tree

5 files changed

+94
-20
lines changed

5 files changed

+94
-20
lines changed

docker/utils/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
kwargs_from_env, convert_filters, datetime_to_timestamp, create_host_config,
55
create_container_config, parse_bytes, ping_registry, parse_env_file,
66
version_lt, version_gte, decode_json_header, split_command,
7-
create_ipam_config, create_ipam_pool,
7+
create_ipam_config, create_ipam_pool, parse_devices
88
) # flake8: noqa
99

1010
from .types import Ulimit, LogConfig # flake8: noqa

docker/utils/utils.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ def parse_host(addr, platform=None):
400400
port = int(port)
401401
except Exception:
402402
raise errors.DockerException(
403-
"Invalid port: %s", addr
403+
"Invalid port: {0}".format(addr)
404404
)
405405

406406
elif proto in ("http", "https") and ':' not in addr:
@@ -417,7 +417,14 @@ def parse_host(addr, platform=None):
417417
def parse_devices(devices):
418418
device_list = []
419419
for device in devices:
420-
device_mapping = device.split(":")
420+
if isinstance(device, dict):
421+
device_list.append(device)
422+
continue
423+
if not isinstance(device, six.string_types):
424+
raise errors.DockerException(
425+
'Invalid device type {0}'.format(type(device))
426+
)
427+
device_mapping = device.split(':')
421428
if device_mapping:
422429
path_on_host = device_mapping[0]
423430
if len(device_mapping) > 1:
@@ -428,9 +435,11 @@ def parse_devices(devices):
428435
permissions = device_mapping[2]
429436
else:
430437
permissions = 'rwm'
431-
device_list.append({"PathOnHost": path_on_host,
432-
"PathInContainer": path_in_container,
433-
"CgroupPermissions": permissions})
438+
device_list.append({
439+
'PathOnHost': path_on_host,
440+
'PathInContainer': path_in_container,
441+
'CgroupPermissions': permissions
442+
})
434443
return device_list
435444

436445

docs/host-devices.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,18 @@ cli.create_container(
1212
)
1313
```
1414

15-
Each string is a single mapping using the colon (':') as the separator. So the
16-
above example essentially allow the container to have read write permissions to
17-
access the host's /dev/sda via a node named /dev/xvda in the container. The
18-
devices parameter is a list to allow multiple devices to be mapped.
15+
Each string is a single mapping using the following format:
16+
`<path_on_host>:<path_in_container>:<cgroup_permissions>`
17+
The above example allows the container to have read-write access to
18+
the host's `/dev/sda` via a node named `/dev/xvda` inside the container.
19+
20+
As a more verbose alternative, each host device definition can be specified as
21+
a dictionary with the following keys:
22+
23+
```python
24+
{
25+
'PathOnHost': '/dev/sda1',
26+
'PathInContainer': '/dev/xvda',
27+
'CgroupPermissions': 'rwm'
28+
}
29+
```

docs/hostconfig.md

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -104,17 +104,12 @@ for example:
104104
* mem_swappiness (int): Tune a container's memory swappiness behavior.
105105
Accepts number between 0 and 100.
106106
* cpu_group (int): The length of a CPU period in microseconds.
107-
* cpu_period (int): Microseconds of CPU time that the container can get in a CPU period.
107+
* cpu_period (int): Microseconds of CPU time that the container can get in a
108+
CPU period.
108109
* group_add (list): List of additional group names and/or IDs that the
109110
container process will run as.
110-
* devices (list): A list of devices to add to the container specified as dicts
111-
in the form:
112-
```
113-
{ "PathOnHost": "/dev/deviceName",
114-
"PathInContainer": "/dev/deviceName",
115-
"CgroupPermissions": "mrw"
116-
}
117-
```
111+
* devices (list): Host device bindings. See [host devices](host-devices.md)
112+
for more information.
118113

119114
**Returns** (dict) HostConfig dictionary
120115

tests/unit/utils_test.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
parse_repository_tag, parse_host, convert_filters, kwargs_from_env,
1919
create_host_config, Ulimit, LogConfig, parse_bytes, parse_env_file,
2020
exclude_paths, convert_volume_binds, decode_json_header, tar,
21-
split_command, create_ipam_config, create_ipam_pool,
21+
split_command, create_ipam_config, create_ipam_pool, parse_devices,
2222
)
2323
from docker.utils.utils import create_endpoint_config
2424
from docker.utils.ports import build_port_bindings, split_port
@@ -406,6 +406,65 @@ def test_private_reg_image_sha(self):
406406
)
407407

408408

409+
class ParseDeviceTest(base.BaseTestCase):
410+
def test_dict(self):
411+
devices = parse_devices([{
412+
'PathOnHost': '/dev/sda1',
413+
'PathInContainer': '/dev/mnt1',
414+
'CgroupPermissions': 'r'
415+
}])
416+
self.assertEqual(devices[0], {
417+
'PathOnHost': '/dev/sda1',
418+
'PathInContainer': '/dev/mnt1',
419+
'CgroupPermissions': 'r'
420+
})
421+
422+
def test_partial_string_definition(self):
423+
devices = parse_devices(['/dev/sda1'])
424+
self.assertEqual(devices[0], {
425+
'PathOnHost': '/dev/sda1',
426+
'PathInContainer': '/dev/sda1',
427+
'CgroupPermissions': 'rwm'
428+
})
429+
430+
def test_permissionless_string_definition(self):
431+
devices = parse_devices(['/dev/sda1:/dev/mnt1'])
432+
self.assertEqual(devices[0], {
433+
'PathOnHost': '/dev/sda1',
434+
'PathInContainer': '/dev/mnt1',
435+
'CgroupPermissions': 'rwm'
436+
})
437+
438+
def test_full_string_definition(self):
439+
devices = parse_devices(['/dev/sda1:/dev/mnt1:r'])
440+
self.assertEqual(devices[0], {
441+
'PathOnHost': '/dev/sda1',
442+
'PathInContainer': '/dev/mnt1',
443+
'CgroupPermissions': 'r'
444+
})
445+
446+
def test_hybrid_list(self):
447+
devices = parse_devices([
448+
'/dev/sda1:/dev/mnt1:rw',
449+
{
450+
'PathOnHost': '/dev/sda2',
451+
'PathInContainer': '/dev/mnt2',
452+
'CgroupPermissions': 'r'
453+
}
454+
])
455+
456+
self.assertEqual(devices[0], {
457+
'PathOnHost': '/dev/sda1',
458+
'PathInContainer': '/dev/mnt1',
459+
'CgroupPermissions': 'rw'
460+
})
461+
self.assertEqual(devices[1], {
462+
'PathOnHost': '/dev/sda2',
463+
'PathInContainer': '/dev/mnt2',
464+
'CgroupPermissions': 'r'
465+
})
466+
467+
409468
class UtilsTest(base.BaseTestCase):
410469
longMessage = True
411470

0 commit comments

Comments
 (0)