Skip to content

Commit 753c354

Browse files
author
Игорь Деордиев
committed
Add support for tmpfs mounts.
1 parent c6cf91d commit 753c354

File tree

5 files changed

+76
-27
lines changed

5 files changed

+76
-27
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ version.txt
66
tox_docker.egg-info
77
build/
88
dist/
9+
.idea

README.rst

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,12 @@ The ``[docker:container-name]`` section may contain the following directives:
109109
``volumes``
110110
A multi-line list of `volumes
111111
<https://docs.docker.com/storage/volumes/>`__ to make available to the
112-
container, as ``<type>:<options>:<outside_path_or_name>:<inside_path>``.
113-
The ``type`` must be ``bind``, and the only supported options are ``rw``
114-
(read-write) or ``ro`` (read-only). The ``outside_path_or_name`` must
115-
be a path that exists on the host system. Both the ``outside_path``
116-
and ``inside_path`` must be absolute paths.
112+
container, as ``<type>:<options>:[<outside_path_or_name>:]<inside_path>``.
113+
The ``type`` must be ``bind`` or ``tmpfs``. For ``bind`` type the only supported options are ``rw``
114+
(read-write) or ``ro`` (read-only). The ``tmpfs`` type additionally supports ``size`` and ``mode`` options with ``;`` as delimiter.
115+
The ``outside_path`` is required for the ``bind`` type and must be a path that exists on the host system.
116+
For the ``tmpfs`` type ``outside_path`` should NOT be specified.
117+
Both the ``outside_path`` and ``inside_path`` must be absolute paths.
117118

118119
``healthcheck_cmd``, ``healthcheck_interval``, ``healthcheck_retries``, ``healthcheck_start_period``, ``healthcheck_timeout``
119120
These set or customize parameters of the container `health check
@@ -183,12 +184,14 @@ Example
183184
healthcheck_retries = 30
184185
healthcheck_interval = 1
185186
healthcheck_start_period = 1
186-
# Configure a bind-mounted volume on the host to store Postgres' data
187+
# Configure a bind-mounted volume on the host to store Postgres' data and tmpfs as /tmp/
187188
# NOTE: this is included for demonstration purposes of tox-docker's
188-
# volume capability; you probably _don't_ want to do this for real
189+
# volume capability; you probably _don't_ want to use bind mounts for real
189190
# testing use cases, as this could persist data between test runs
190191
volumes =
191192
bind:rw:/my/own/datadir:/var/lib/postgresql/data
193+
tmpfs:rw;size=64m;mode=1777:/tmp/
194+
192195
193196
[docker:appserv]
194197
# You can use any value that `docker run` would accept as the image

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ healthcheck_timeout = 1
7070
healthcheck_start_period = 1
7171
volumes =
7272
bind:rw:{toxworkdir}:/healthcheck/web
73+
tmpfs:rw;size=1m;mode=1777:/tmp/
7374

7475
# do NOT add this env to the envlist; it is supposed to fail,
7576
# and the CI scripts run it directly with this expectation

tox_docker/config.py

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from pathlib import Path
2-
from typing import Collection, Dict, List, Mapping, Optional
2+
from typing import Collection, Dict, List, Mapping, Optional, Union
33
import os
44
import os.path
55
import re
@@ -118,25 +118,69 @@ def __init__(self, config_line: str) -> None:
118118
class Volume:
119119
def __init__(self, config_line: str) -> None:
120120
parts = config_line.split(":")
121-
if len(parts) != 4:
121+
if len(parts) < 3 or len(parts) > 4:
122122
raise ValueError(f"Volume {config_line!r} is malformed")
123-
if parts[0] != "bind":
124-
raise ValueError(f"Volume {config_line!r} type must be 'bind:'")
125-
if parts[1] not in ("ro", "rw"):
126-
raise ValueError(f"Volume {config_line!r} options must be 'ro' or 'rw'")
127-
128-
volume_type, mode, outside, inside = parts
129-
if not os.path.isabs(outside):
130-
raise ValueError(f"Volume source {outside!r} must be an absolute path")
131-
if not os.path.isabs(inside):
132-
raise ValueError(f"Mount point {inside!r} must be an absolute path")
133-
134-
self.docker_mount = Mount(
135-
source=outside,
136-
target=inside,
137-
type=volume_type,
138-
read_only=bool(mode == "ro"),
139-
)
123+
124+
volume_type, options_str, *_outside_path, inside_path = parts
125+
126+
if not os.path.isabs(inside_path):
127+
raise ValueError(f"Mount point {inside_path!r} must be an absolute path")
128+
129+
mount_params = {
130+
"target": inside_path,
131+
"type": volume_type,
132+
**self._parse_options(config_line, volume_type, options_str),
133+
}
134+
135+
# bind-specific checks and setup
136+
if volume_type == "bind":
137+
if len(_outside_path) != 1:
138+
raise ValueError(
139+
f"Volume {config_line!r} of type 'bind' must have an outside path"
140+
)
141+
outside = _outside_path[0]
142+
143+
if not os.path.isabs(outside):
144+
raise ValueError(f"Volume source {outside!r} must be an absolute path")
145+
mount_params["source"] = outside
146+
# tmpfs-specific setup
147+
elif volume_type == "tmpfs":
148+
# tmpfs does not have source, so emtpy string
149+
mount_params["source"] = ""
150+
else:
151+
raise ValueError(f"Volume {config_line!r} type must be 'bind' or 'tmpfs'")
152+
153+
self.docker_mount = Mount(**mount_params)
154+
155+
def _parse_options(
156+
self, config_line: str, volume_type: str, options_str: str
157+
) -> dict:
158+
"""Parse volume options into `Mount()` params."""
159+
result: Dict[str, Union[str, int, bool]] = {}
160+
(
161+
access_mode,
162+
*other_options,
163+
) = options_str.split(";")
164+
165+
# parsing access mode
166+
if access_mode not in ("ro", "rw"):
167+
raise ValueError(f"Volume {config_line!r} access mode must be 'ro' or 'rw'")
168+
result["read_only"] = bool(access_mode == "ro")
169+
170+
# parsing tmpfs-specific options
171+
if volume_type == "tmpfs":
172+
for other_option in other_options:
173+
key, value = other_option.split("=")
174+
if key == "size": # volume size, such as 64m
175+
result["tmpfs_size"] = value
176+
elif key == "mode": # permissions, such as 1777
177+
result["tmpfs_mode"] = int(value)
178+
else:
179+
raise ValueError(
180+
f"'{other_option!r}' is not a valid option for volume of type '{volume_type}'"
181+
)
182+
183+
return {}
140184

141185

142186
class ContainerConfig:

tox_docker/plugin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def docker_run(
190190

191191
for mount in container_config.mounts:
192192
source = mount["Source"]
193-
if not os.path.exists(source):
193+
if mount["Type"] != "tmpfs" and not os.path.exists(source):
194194
raise ValueError(f"Volume source {source!r} does not exist")
195195

196196
assert container_config.runnable_image

0 commit comments

Comments
 (0)