22and bind mounts.
33
44"""
5-
6- from subprocess import check_output
7- from types import TracebackType
8- from typing import Iterable , List , Optional , Type , Union , overload
9- from os .path import exists
10- from os .path import isabs
11- from typing_extensions import TypedDict
125import enum
13- import tempfile
146import sys
15- from dataclasses import KW_ONLY , dataclass
7+ import tempfile
8+ from dataclasses import dataclass
9+ from dataclasses import field
10+ from os .path import exists
11+ from os .path import isabs
12+ from subprocess import check_output
13+ from types import TracebackType
14+ from typing import Callable
15+ from typing import List
16+ from typing import Optional
17+ from typing import overload
18+ from typing import Sequence
19+ from typing import Type
20+ from typing import Union
1621
17- from pytest_container .runtime import OciRuntimeBase
1822from pytest_container .logging import _logger
23+ from pytest_container .runtime import OciRuntimeBase
24+ from typing_extensions import TypedDict
1925
2026
2127@enum .unique
@@ -60,16 +66,15 @@ class ContainerVolumeBase:
6066 #: Path inside the container where this volume will be mounted
6167 container_path : str
6268
63- _ : KW_ONLY
64-
6569 #: Flags for mounting this volume.
6670 #:
6771 #: Note that some flags are mutually exclusive and potentially not supported
6872 #: by all container runtimes.
6973 #:
70- #: The :py:attr:`VolumeFlag.SELINUX_PRIVATE` flag will be added by default
74+ #: The :py:attr:`VolumeFlag.SELINUX_PRIVATE` flag will be used by default
7175 #: if flags is ``None``, unless :py:attr:`ContainerVolumeBase.shared` is
72- #: ``True``, then :py:attr:`VolumeFlag.SELINUX_SHARED` is added.
76+ #: ``True``, then :py:attr:`VolumeFlag.SELINUX_SHARED` is added to the list
77+ #: of flags to use.
7378 #:
7479 #: If flags is a list (even an empty one), then no flags are added.
7580 flags : Optional [List [VolumeFlag ]] = None
@@ -81,10 +86,6 @@ class ContainerVolumeBase:
8186 #: :py:attr:`~ContainerVolumeBase.flags`.
8287 shared : bool = False
8388
84- #: internal volume name via which the volume can be mounted, e.g. the
85- #: volume's ID or the path on the host
86- # _vol_name: str = ""
87-
8889 def __post_init__ (self ) -> None :
8990
9091 for mutually_exclusive_flags in (
@@ -102,20 +103,28 @@ def __post_init__(self) -> None:
102103 )
103104
104105 @property
105- def _flags (self ) -> Iterable [VolumeFlag ]:
106- if self .flags :
106+ def _flags (self ) -> Sequence [VolumeFlag ]:
107+ """Internal sequence of flags to be used to mount this volume. If the
108+ user supplied no flags, then this property gives you one of the SELinux
109+ flags.
110+
111+ """
112+ if self .flags is not None :
107113 return self .flags
108114
109115 if self .shared :
110116 return (VolumeFlag .SELINUX_SHARED ,)
111- else :
112- return (VolumeFlag .SELINUX_PRIVATE ,)
113117
118+ return (VolumeFlag .SELINUX_PRIVATE ,)
114119
115- def container_volume_cli_arg (
120+
121+ def _container_volume_cli_arg (
116122 container_volume : ContainerVolumeBase , volume_name : str
117123) -> str :
118- """Command line argument to mount the supplied ``container_volume`` volume."""
124+ """Command line argument to mount the supplied ``container_volume`` volume
125+ via the "name" `volume_name` (can be a volume ID or a path on the host).
126+
127+ """
119128 res = f"-v={ volume_name } :{ container_volume .container_path } "
120129 res += ":" + "," .join (str (f ) for f in container_volume ._flags )
121130 return res
@@ -129,10 +138,29 @@ class ContainerVolume(ContainerVolumeBase):
129138 """
130139
131140
141+ def _create_required_parameter_factory (
142+ parameter_name : str ,
143+ ) -> Callable [[], str ]:
144+ def _required_parameter () -> str :
145+ raise ValueError (f"Parameter { parameter_name } is required" )
146+
147+ return _required_parameter
148+
149+
132150@dataclass (frozen = True )
133151class CreatedContainerVolume (ContainerVolume ):
152+ """A container volume that exists and has a volume id assigned to it."""
153+
154+ #: The hash/ID of the volume in the container runtime.
155+ #: This parameter is required
156+ volume_id : str = field (
157+ default_factory = _create_required_parameter_factory ("volume_id" )
158+ )
134159
135- volume_id : str
160+ @property
161+ def cli_arg (self ) -> str :
162+ """The command line argument to mount this volume."""
163+ return _container_volume_cli_arg (self , self .volume_id )
136164
137165
138166@dataclass (frozen = True )
@@ -148,15 +176,29 @@ class BindMount(ContainerVolumeBase):
148176
149177 """
150178
151- #: Path on the host that will be mounted if absolute. if relative,
179+ #: Path on the host that will be mounted if absolute. If relative,
152180 #: it refers to a volume to be auto-created. When omitted, a temporary
153181 #: directory will be created and the path will be saved in this attribute.
154182 host_path : Optional [str ] = None
155183
156184
157185@dataclass (frozen = True )
158- class CreatedBindMount (ContainerVolumeBase ):
159- host_path : str
186+ class CreatedBindMount (BindMount ):
187+ """An established bind mount of the directory :py:attr:`host_path` on the
188+ host to :py:attr:`~ContainerVolumeBase.container_path` in the container.
189+
190+ """
191+
192+ #: Path on the host that is bind mounted into the container.
193+ #: This parameter must be provided.
194+ host_path : str = field (
195+ default_factory = _create_required_parameter_factory ("host_path" )
196+ )
197+
198+ @property
199+ def cli_arg (self ) -> str :
200+ """The command line argument to mount this volume."""
201+ return _container_volume_cli_arg (self , self .host_path )
160202
161203
162204class _ContainerVolumeKWARGS (TypedDict , total = False ):
@@ -174,8 +216,13 @@ class VolumeCreator:
174216 """Context Manager to create and remove a :py:class:`ContainerVolume`.
175217
176218 This context manager creates a volume using the supplied
177- :py:attr:`container_runtime` When the ``with`` block is entered and removes
219+ :py:attr:`container_runtime` when the ``with`` block is entered and removes
178220 it once it is exited.
221+
222+ The :py:class:`ContainerVolume` in :py:attr:`volume` is used as the
223+ blueprint to create the volume, the actually created container volume is
224+ saved in :py:attr:`created_volume`.
225+
179226 """
180227
181228 #: The volume to be created
@@ -184,7 +231,9 @@ class VolumeCreator:
184231 #: The container runtime, via which the volume is created & destroyed
185232 container_runtime : OciRuntimeBase
186233
187- _created_volume : Optional [CreatedContainerVolume ] = None
234+ #: The created container volume once it has been setup & created. It's
235+ #: ``None`` until then.
236+ created_volume : Optional [CreatedContainerVolume ] = None
188237
189238 def __enter__ (self ) -> "VolumeCreator" :
190239 """Creates the container volume"""
@@ -195,7 +244,7 @@ def __enter__(self) -> "VolumeCreator":
195244 .decode ()
196245 .strip ()
197246 )
198- self ._created_volume = CreatedContainerVolume (
247+ self .created_volume = CreatedContainerVolume (
199248 container_path = self .volume .container_path ,
200249 flags = self .volume .flags ,
201250 shared = self .volume .shared ,
@@ -211,11 +260,11 @@ def __exit__(
211260 __traceback : Optional [TracebackType ],
212261 ) -> None :
213262 """Cleans up the container volume."""
214- assert self ._created_volume and self ._created_volume .volume_id
263+ assert self .created_volume and self .created_volume .volume_id
215264
216265 _logger .debug (
217266 "cleaning up volume %s via %s" ,
218- self ._created_volume .volume_id ,
267+ self .created_volume .volume_id ,
219268 self .container_runtime .runner_binary ,
220269 )
221270
@@ -226,7 +275,7 @@ def __exit__(
226275 "volume" ,
227276 "rm" ,
228277 "-f" ,
229- self ._created_volume .volume_id ,
278+ self .created_volume .volume_id ,
230279 ],
231280 )
232281
@@ -244,7 +293,7 @@ class BindMountCreator:
244293 #: internal temporary directory
245294 _tmpdir : Optional [TEMPDIR_T ] = None
246295
247- _created_volume : Optional [CreatedBindMount ] = None
296+ created_volume : Optional [CreatedBindMount ] = None
248297
249298 def __post__init__ (self ) -> None :
250299 # the tempdir must not be set accidentally by the user
@@ -281,7 +330,7 @@ def __enter__(self) -> "BindMountCreator":
281330 "was requested but the directory does not exist"
282331 )
283332
284- self ._created_volume = CreatedBindMount (** kwargs )
333+ self .created_volume = CreatedBindMount (** kwargs )
285334
286335 return self
287336
@@ -292,13 +341,13 @@ def __exit__(
292341 __traceback : Optional [TracebackType ],
293342 ) -> None :
294343 """Cleans up the temporary host directory or the container volume."""
295- assert self ._created_volume and self . _created_volume . host_path
344+ assert self .created_volume
296345
297346 if self ._tmpdir :
298347 _logger .debug (
299348 "cleaning up directory %s for the container volume %s" ,
300- self .volume .host_path ,
301- self .volume .container_path ,
349+ self .created_volume .host_path ,
350+ self .created_volume .container_path ,
302351 )
303352 self ._tmpdir .cleanup ()
304353
0 commit comments