1
+ from __future__ import annotations
2
+
1
3
import io
2
4
import os
5
+ from collections .abc import Iterable
3
6
from shlex import split
4
7
from subprocess import DEVNULL , CalledProcessError , run
5
8
from sys import stdin
6
- from typing import Any , List , Optional , Type , TypeVar , Union , overload
9
+ from typing import Any , Type , TypeVar , overload
7
10
11
+ from ._bind_mount import BindMount
8
12
from ._exceptions import CommandNotFoundError , DockerBuildError , ImageNotFoundError
9
13
10
14
@@ -16,7 +20,7 @@ class Image:
16
20
an interface by which to interact with that image.
17
21
18
22
Capabilities include:
19
- - Building Docker images from dockerfiles or Dockerfile-formatted strings
23
+ - Building Docker images from Dockerfiles or Dockerfile-formatted strings
20
24
via :func:`~wigwam.Image.build`.
21
25
- Running commands in containers built from the image using
22
26
:func:`~wigwam.Image.run`.
@@ -42,8 +46,8 @@ def build(
42
46
cls : Type [Self ],
43
47
tag : str ,
44
48
* ,
45
- dockerfile : Union [ str , os .PathLike [str ]] ,
46
- context : Union [ str , os .PathLike [str ]] = ...,
49
+ dockerfile : os .PathLike [str ] | str ,
50
+ context : os .PathLike [str ] | str = ...,
47
51
stdout : Any = ...,
48
52
stderr : Any = ...,
49
53
network : str = ...,
@@ -60,8 +64,7 @@ def build(
60
64
tag : str
61
65
A name for the image.
62
66
dockerfile : os.PathLike
63
- The path of the Dockerfile to build
64
- directory.
67
+ The path of the Dockerfile to build directory.
65
68
context : os.PathLike, optional
66
69
The build context. Defaults to ".".
67
70
stdout : io.TextIOBase or special value, optional
@@ -95,7 +98,7 @@ def build(
95
98
tag : str ,
96
99
* ,
97
100
dockerfile_string : str ,
98
- context : Union [ str , os .PathLike [str ]] = ...,
101
+ context : os .PathLike [str ] | str = ...,
99
102
stdout : Any = ...,
100
103
stderr : Any = ...,
101
104
network : str = ...,
@@ -132,7 +135,7 @@ def build(
132
135
DockerBuildError
133
136
If the Docker build command fails.
134
137
ValueError
135
- If both `Dockerfile ` and `dockerfile_string` are defined.
138
+ If both `dockerfile ` and `dockerfile_string` are defined.
136
139
"""
137
140
...
138
141
@@ -194,14 +197,14 @@ def build(
194
197
195
198
return cls (tag )
196
199
197
- def _inspect (self , format : Optional [ str ] = None ) -> str :
200
+ def _inspect (self , format : str | None = None ) -> str :
198
201
"""
199
202
Use 'docker inspect' to retrieve a piece of information about the
200
203
image.
201
204
202
205
Parameters
203
206
----------
204
- format : str, optional
207
+ format : str or None , optional
205
208
The value to be requested by the --format argument, or None.
206
209
Defaults to None.
207
210
@@ -224,11 +227,13 @@ def run(
224
227
self ,
225
228
command : str ,
226
229
* ,
227
- stdout : Optional [ Union [ io .TextIOBase , int ]] = None ,
228
- stderr : Optional [ Union [ io .TextIOBase , int ]] = None ,
230
+ stdout : io .TextIOBase | int | None = None ,
231
+ stderr : io .TextIOBase | int | None = None ,
229
232
interactive : bool = False ,
230
233
network : str = "host" ,
231
234
check : bool = True ,
235
+ host_user : bool = False ,
236
+ bind_mounts : Iterable [BindMount ] | None = None ,
232
237
) -> str :
233
238
"""
234
239
Run the given command on a container.
@@ -253,6 +258,11 @@ def run(
253
258
check: bool, optional
254
259
If True, check for CalledProcessErrors on non-zero return codes. Defualts
255
260
to True.
261
+ host_user: bool, optional
262
+ If True, run the command as the user on the host machine, else run as the
263
+ default user. Defaults to False.
264
+ bind_mounts : Iterable[BindMount], optional
265
+ A list of bind mount descriptions to apply to the run command.
256
266
257
267
Returns
258
268
-------
@@ -264,7 +274,13 @@ def run(
264
274
CommandNotFoundError:
265
275
When a command is attempted that is not recognized on the image.
266
276
"""
277
+
267
278
cmd = ["docker" , "run" , f"--network={ network } " , "--rm" ]
279
+ if host_user :
280
+ cmd += ["-u" , f"{ os .getuid ()} :{ os .getgid ()} " ]
281
+ if bind_mounts is not None :
282
+ for mount in bind_mounts :
283
+ cmd += ["-v" , f"{ mount .mount_string ()} " ]
268
284
if interactive :
269
285
cmd += ["-i" ]
270
286
if stdin .isatty ():
@@ -288,7 +304,7 @@ def run(
288
304
raise
289
305
return result .stdout
290
306
291
- def drop_in (self , network : str = "host" ) -> None :
307
+ def drop_in (self , network : str = "host" , host_user : bool = True ) -> None :
292
308
"""
293
309
Start a drop-in session on a disposable container.
294
310
@@ -300,15 +316,18 @@ def drop_in(self, network: str = "host") -> None:
300
316
----------
301
317
network : str, optional
302
318
The name of the network. Defaults to "host".
319
+ host_user: bool, optional
320
+ If True, run as the current user on the host machine. Else, run as the
321
+ default user in the image. Defaults to True.
303
322
304
323
Raises
305
324
-------
306
325
CommandNotFoundError:
307
326
When bash is not recognized on the image.
308
327
"""
309
- self .run (
310
- "bash" , interactive = True , network = network , check = False
311
- ) # pragma: no cover
328
+ self .run ( # pragma: no cover
329
+ "bash" , interactive = True , network = network , check = False , host_user = host_user
330
+ )
312
331
313
332
def has_command (self , command : str ) -> bool :
314
333
"""
@@ -339,8 +358,8 @@ def has_command(self, command: str) -> bool:
339
358
return True
340
359
341
360
@property
342
- def tags (self ) -> List [str ]:
343
- """List [str]: The Repo Tags held on this Docker image."""
361
+ def tags (self ) -> list [str ]:
362
+ """list [str]: The Repo Tags held on this Docker image."""
344
363
return self ._inspect (format = "{{.RepoTags}}" ).strip ("][\n " ).split (", " )
345
364
346
365
@property
0 commit comments