11
11
import typing
12
12
import uuid
13
13
from collections .abc import Mapping , Sequence
14
+ from dataclasses import dataclass
14
15
from pathlib import Path , PurePath , PurePosixPath
15
16
from types import TracebackType
16
17
from typing import IO , Dict
17
18
18
19
from ._compat .typing import Literal
19
20
from .typing import PathOrStr , PopenBytes
20
- from .util import CIProvider , detect_ci_provider
21
+ from .util import CIProvider , detect_ci_provider , parse_key_value_string
21
22
22
- ContainerEngine = Literal ["docker" , "podman" ]
23
+ ContainerEngineName = Literal ["docker" , "podman" ]
24
+
25
+
26
+ @dataclass (frozen = True )
27
+ class OCIContainerEngineConfig :
28
+ name : ContainerEngineName
29
+ create_args : Sequence [str ] = ()
30
+
31
+ @staticmethod
32
+ def from_config_string (config_string : str ) -> OCIContainerEngineConfig :
33
+ config_dict = parse_key_value_string (config_string , ["name" ])
34
+ name = " " .join (config_dict ["name" ])
35
+ if name not in {"docker" , "podman" }:
36
+ msg = f"unknown container engine { name } "
37
+ raise ValueError (msg )
38
+
39
+ name = typing .cast (ContainerEngineName , name )
40
+ # some flexibility in the option name to cope with TOML conventions
41
+ create_args = config_dict .get ("create_args" ) or config_dict .get ("create-args" ) or []
42
+ return OCIContainerEngineConfig (name = name , create_args = create_args )
43
+
44
+ def options_summary (self ) -> str | dict [str , str ]:
45
+ if not self .create_args :
46
+ return self .name
47
+ else :
48
+ return {"name" : self .name , "create_args" : repr (self .create_args )}
49
+
50
+
51
+ DEFAULT_ENGINE = OCIContainerEngineConfig ("docker" )
23
52
24
53
25
54
class OCIContainer :
@@ -57,7 +86,7 @@ def __init__(
57
86
image : str ,
58
87
simulate_32_bit : bool = False ,
59
88
cwd : PathOrStr | None = None ,
60
- engine : ContainerEngine = "docker" ,
89
+ engine : OCIContainerEngineConfig = DEFAULT_ENGINE ,
61
90
):
62
91
if not image :
63
92
msg = "Must have a non-empty image to run."
@@ -84,13 +113,14 @@ def __enter__(self) -> OCIContainer:
84
113
85
114
subprocess .run (
86
115
[
87
- self .engine ,
116
+ self .engine . name ,
88
117
"create" ,
89
118
"--env=CIBUILDWHEEL" ,
90
119
f"--name={ self .name } " ,
91
120
"--interactive" ,
92
121
"--volume=/:/host" , # ignored on CircleCI
93
122
* network_args ,
123
+ * self .engine .create_args ,
94
124
self .image ,
95
125
* shell_args ,
96
126
],
@@ -99,7 +129,7 @@ def __enter__(self) -> OCIContainer:
99
129
100
130
self .process = subprocess .Popen (
101
131
[
102
- self .engine ,
132
+ self .engine . name ,
103
133
"start" ,
104
134
"--attach" ,
105
135
"--interactive" ,
@@ -137,7 +167,7 @@ def __exit__(
137
167
self .bash_stdin .close ()
138
168
self .bash_stdout .close ()
139
169
140
- if self .engine == "podman" :
170
+ if self .engine . name == "podman" :
141
171
# This works around what seems to be a race condition in the podman
142
172
# backend. The full reason is not understood. See PR #966 for a
143
173
# discussion on possible causes and attempts to remove this line.
@@ -147,7 +177,7 @@ def __exit__(
147
177
assert isinstance (self .name , str )
148
178
149
179
subprocess .run (
150
- [self .engine , "rm" , "--force" , "-v" , self .name ],
180
+ [self .engine . name , "rm" , "--force" , "-v" , self .name ],
151
181
stdout = subprocess .DEVNULL ,
152
182
check = False ,
153
183
)
@@ -162,7 +192,7 @@ def copy_into(self, from_path: Path, to_path: PurePath) -> None:
162
192
if from_path .is_dir ():
163
193
self .call (["mkdir" , "-p" , to_path ])
164
194
subprocess .run (
165
- f"tar cf - . | { self .engine } exec -i { self .name } tar --no-same-owner -xC { shell_quote (to_path )} -f -" ,
195
+ f"tar cf - . | { self .engine . name } exec -i { self .name } tar --no-same-owner -xC { shell_quote (to_path )} -f -" ,
166
196
shell = True ,
167
197
check = True ,
168
198
cwd = from_path ,
@@ -171,7 +201,7 @@ def copy_into(self, from_path: Path, to_path: PurePath) -> None:
171
201
exec_process : subprocess .Popen [bytes ]
172
202
with subprocess .Popen (
173
203
[
174
- self .engine ,
204
+ self .engine . name ,
175
205
"exec" ,
176
206
"-i" ,
177
207
str (self .name ),
@@ -198,29 +228,29 @@ def copy_out(self, from_path: PurePath, to_path: Path) -> None:
198
228
# note: we assume from_path is a dir
199
229
to_path .mkdir (parents = True , exist_ok = True )
200
230
201
- if self .engine == "podman" :
231
+ if self .engine . name == "podman" :
202
232
subprocess .run (
203
233
[
204
- self .engine ,
234
+ self .engine . name ,
205
235
"cp" ,
206
236
f"{ self .name } :{ from_path } /." ,
207
237
str (to_path ),
208
238
],
209
239
check = True ,
210
240
cwd = to_path ,
211
241
)
212
- elif self .engine == "docker" :
242
+ elif self .engine . name == "docker" :
213
243
# There is a bug in docker that prevents a simple 'cp' invocation
214
244
# from working https://github.com/moby/moby/issues/38995
215
- command = f"{ self .engine } exec -i { self .name } tar -cC { shell_quote (from_path )} -f - . | tar -xf -"
245
+ command = f"{ self .engine . name } exec -i { self .name } tar -cC { shell_quote (from_path )} -f - . | tar -xf -"
216
246
subprocess .run (
217
247
command ,
218
248
shell = True ,
219
249
check = True ,
220
250
cwd = to_path ,
221
251
)
222
252
else :
223
- raise KeyError (self .engine )
253
+ raise KeyError (self .engine . name )
224
254
225
255
def glob (self , path : PurePosixPath , pattern : str ) -> list [PurePosixPath ]:
226
256
glob_pattern = path .joinpath (pattern )
@@ -338,10 +368,10 @@ def environment_executor(self, command: Sequence[str], environment: dict[str, st
338
368
return self .call (command , env = environment , capture_output = True )
339
369
340
370
def debug_info (self ) -> str :
341
- if self .engine == "podman" :
342
- command = f"{ self .engine } info --debug"
371
+ if self .engine . name == "podman" :
372
+ command = f"{ self .engine . name } info --debug"
343
373
else :
344
- command = f"{ self .engine } info"
374
+ command = f"{ self .engine . name } info"
345
375
completed = subprocess .run (
346
376
command ,
347
377
shell = True ,
0 commit comments