]> git.proxmox.com Git - mirror_qemu.git/blame - tests/docker/docker.py
tests/docker: update test-mingw to run single build
[mirror_qemu.git] / tests / docker / docker.py
CommitLineData
4112aff7 1#!/usr/bin/env python3
4485b04b
FZ
2#
3# Docker controlling module
4#
5# Copyright (c) 2016 Red Hat Inc.
6#
7# Authors:
8# Fam Zheng <famz@redhat.com>
9#
10# This work is licensed under the terms of the GNU GPL, version 2
11# or (at your option) any later version. See the COPYING file in
12# the top-level directory.
13
14import os
15import sys
16import subprocess
17import json
18import hashlib
19import atexit
20import uuid
ae68fdab 21import argparse
9459f754 22import enum
4485b04b 23import tempfile
504ca3c2 24import re
97cba1a1 25import signal
6e733da6 26from tarfile import TarFile, TarInfo
e336cec3 27from io import StringIO, BytesIO
a9f8d038 28from shutil import copy, rmtree
414a8ce5 29from pwd import getpwuid
432d8ad5 30from datetime import datetime, timedelta
4485b04b 31
c9772570 32
06cc3551
PMD
33FILTERED_ENV_NAMES = ['ftp_proxy', 'http_proxy', 'https_proxy']
34
35
c9772570
SS
36DEVNULL = open(os.devnull, 'wb')
37
9459f754
MAL
38class EngineEnum(enum.IntEnum):
39 AUTO = 1
40 DOCKER = 2
41 PODMAN = 3
42
43 def __str__(self):
44 return self.name.lower()
45
46 def __repr__(self):
47 return str(self)
48
49 @staticmethod
50 def argparse(s):
51 try:
52 return EngineEnum[s.upper()]
53 except KeyError:
54 return s
55
56
57USE_ENGINE = EngineEnum.AUTO
c9772570 58
af509738
PB
59def _bytes_checksum(bytes):
60 """Calculate a digest string unique to the text content"""
61 return hashlib.sha1(bytes).hexdigest()
62
4485b04b
FZ
63def _text_checksum(text):
64 """Calculate a digest string unique to the text content"""
af509738 65 return _bytes_checksum(text.encode('utf-8'))
4485b04b 66
4112aff7
AB
67def _read_dockerfile(path):
68 return open(path, 'rt', encoding='utf-8').read()
432d8ad5 69
438d1168 70def _file_checksum(filename):
af509738 71 return _bytes_checksum(open(filename, 'rb').read())
438d1168 72
432d8ad5 73
9459f754
MAL
74def _guess_engine_command():
75 """ Guess a working engine command or raise exception if not found"""
76 commands = []
77
78 if USE_ENGINE in [EngineEnum.AUTO, EngineEnum.PODMAN]:
79 commands += [["podman"]]
80 if USE_ENGINE in [EngineEnum.AUTO, EngineEnum.DOCKER]:
81 commands += [["docker"], ["sudo", "-n", "docker"]]
4485b04b 82 for cmd in commands:
0679f98b 83 try:
83405c45
AB
84 # docker version will return the client details in stdout
85 # but still report a status of 1 if it can't contact the daemon
86 if subprocess.call(cmd + ["version"],
0679f98b
EH
87 stdout=DEVNULL, stderr=DEVNULL) == 0:
88 return cmd
89 except OSError:
90 pass
4485b04b 91 commands_txt = "\n".join([" " + " ".join(x) for x in commands])
9459f754 92 raise Exception("Cannot find working engine command. Tried:\n%s" %
4485b04b
FZ
93 commands_txt)
94
432d8ad5 95
3971c70f 96def _copy_with_mkdir(src, root_dir, sub_path='.', name=None):
504ca3c2
AB
97 """Copy src into root_dir, creating sub_path as needed."""
98 dest_dir = os.path.normpath("%s/%s" % (root_dir, sub_path))
99 try:
100 os.makedirs(dest_dir)
101 except OSError:
102 # we can safely ignore already created directories
103 pass
104
3971c70f 105 dest_file = "%s/%s" % (dest_dir, name if name else os.path.basename(src))
dffccf3d
AB
106
107 try:
108 copy(src, dest_file)
109 except FileNotFoundError:
110 print("Couldn't copy %s to %s" % (src, dest_file))
111 pass
504ca3c2
AB
112
113
114def _get_so_libs(executable):
115 """Return a list of libraries associated with an executable.
116
117 The paths may be symbolic links which would need to be resolved to
5e33f7fe 118 ensure the right data is copied."""
504ca3c2
AB
119
120 libs = []
5e33f7fe 121 ldd_re = re.compile(r"(?:\S+ => )?(\S*) \(:?0x[0-9a-f]+\)")
504ca3c2 122 try:
eea2153e 123 ldd_output = subprocess.check_output(["ldd", executable]).decode('utf-8')
504ca3c2
AB
124 for line in ldd_output.split("\n"):
125 search = ldd_re.search(line)
5e33f7fe
AB
126 if search:
127 try:
4d8f6309 128 libs.append(search.group(1))
5e33f7fe
AB
129 except IndexError:
130 pass
504ca3c2 131 except subprocess.CalledProcessError:
f03868bd 132 print("%s had no associated libraries (static build?)" % (executable))
504ca3c2
AB
133
134 return libs
135
432d8ad5 136
d10404b1
AB
137def _copy_binary_with_libs(src, bin_dest, dest_dir):
138 """Maybe copy a binary and all its dependent libraries.
139
140 If bin_dest isn't set we only copy the support libraries because
141 we don't need qemu in the docker path to run (due to persistent
142 mapping). Indeed users may get confused if we aren't running what
143 is in the image.
504ca3c2
AB
144
145 This does rely on the host file-system being fairly multi-arch
d10404b1
AB
146 aware so the file don't clash with the guests layout.
147 """
504ca3c2 148
d10404b1
AB
149 if bin_dest:
150 _copy_with_mkdir(src, dest_dir, os.path.dirname(bin_dest))
151 else:
152 print("only copying support libraries for %s" % (src))
504ca3c2
AB
153
154 libs = _get_so_libs(src)
155 if libs:
156 for l in libs:
157 so_path = os.path.dirname(l)
3971c70f 158 name = os.path.basename(l)
5e33f7fe 159 real_l = os.path.realpath(l)
3971c70f 160 _copy_with_mkdir(real_l, dest_dir, so_path, name)
504ca3c2 161
15352dec
AB
162
163def _check_binfmt_misc(executable):
164 """Check binfmt_misc has entry for executable in the right place.
165
166 The details of setting up binfmt_misc are outside the scope of
167 this script but we should at least fail early with a useful
d10404b1
AB
168 message if it won't work.
169
170 Returns the configured binfmt path and a valid flag. For
171 persistent configurations we will still want to copy and dependent
172 libraries.
173 """
15352dec
AB
174
175 binary = os.path.basename(executable)
176 binfmt_entry = "/proc/sys/fs/binfmt_misc/%s" % (binary)
177
178 if not os.path.exists(binfmt_entry):
179 print ("No binfmt_misc entry for %s" % (binary))
d10404b1 180 return None, False
15352dec
AB
181
182 with open(binfmt_entry) as x: entry = x.read()
183
43c898b7 184 if re.search("flags:.*F.*\n", entry):
432d8ad5 185 print("binfmt_misc for %s uses persistent(F) mapping to host binary" %
43c898b7 186 (binary))
d10404b1 187 return None, True
43c898b7 188
7e81d198
AB
189 m = re.search("interpreter (\S+)\n", entry)
190 interp = m.group(1)
191 if interp and interp != executable:
192 print("binfmt_misc for %s does not point to %s, using %s" %
193 (binary, executable, interp))
15352dec 194
d10404b1
AB
195 return interp, True
196
15352dec 197
c1958e9d 198def _read_qemu_dockerfile(img_name):
547cb45e
AB
199 # special case for Debian linux-user images
200 if img_name.startswith("debian") and img_name.endswith("user"):
201 img_name = "debian-bootstrap"
202
c1958e9d
FZ
203 df = os.path.join(os.path.dirname(__file__), "dockerfiles",
204 img_name + ".docker")
4112aff7 205 return _read_dockerfile(df)
c1958e9d 206
432d8ad5 207
07056db1
AB
208def _dockerfile_verify_flat(df):
209 "Verify we do not include other qemu/ layers"
c1958e9d
FZ
210 for l in df.splitlines():
211 if len(l.strip()) == 0 or l.startswith("#"):
212 continue
767b6bd2 213 from_pref = "FROM qemu/"
c1958e9d 214 if l.startswith(from_pref):
07056db1
AB
215 print("We no longer support multiple QEMU layers.")
216 print("Dockerfiles should be flat, ideally created by lcitool")
217 return False
218 return True
c1958e9d 219
432d8ad5 220
4485b04b
FZ
221class Docker(object):
222 """ Running Docker commands """
223 def __init__(self):
9459f754 224 self._command = _guess_engine_command()
e6f1306b 225
6ddc3dc7
DB
226 if ("docker" in self._command and
227 "TRAVIS" not in os.environ and
228 "GITLAB_CI" not in os.environ):
e6f1306b
AB
229 os.environ["DOCKER_BUILDKIT"] = "1"
230 self._buildkit = True
231 else:
232 self._buildkit = False
233
529994e2 234 self._instance = None
4485b04b 235 atexit.register(self._kill_instances)
97cba1a1
FZ
236 signal.signal(signal.SIGTERM, self._kill_instances)
237 signal.signal(signal.SIGHUP, self._kill_instances)
4485b04b 238
58bf7b6d 239 def _do(self, cmd, quiet=True, **kwargs):
4485b04b 240 if quiet:
c9772570 241 kwargs["stdout"] = DEVNULL
4485b04b
FZ
242 return subprocess.call(self._command + cmd, **kwargs)
243
0b95ff72
FZ
244 def _do_check(self, cmd, quiet=True, **kwargs):
245 if quiet:
246 kwargs["stdout"] = DEVNULL
247 return subprocess.check_call(self._command + cmd, **kwargs)
248
4485b04b
FZ
249 def _do_kill_instances(self, only_known, only_active=True):
250 cmd = ["ps", "-q"]
251 if not only_active:
252 cmd.append("-a")
529994e2
AB
253
254 filter = "--filter=label=com.qemu.instance.uuid"
255 if only_known:
256 if self._instance:
257 filter += "=%s" % (self._instance)
258 else:
259 # no point trying to kill, we finished
260 return
261
262 print("filter=%s" % (filter))
263 cmd.append(filter)
4485b04b 264 for i in self._output(cmd).split():
529994e2 265 self._do(["rm", "-f", i])
4485b04b
FZ
266
267 def clean(self):
268 self._do_kill_instances(False, False)
269 return 0
270
97cba1a1 271 def _kill_instances(self, *args, **kwargs):
4485b04b
FZ
272 return self._do_kill_instances(True)
273
274 def _output(self, cmd, **kwargs):
2d110c11 275 try:
884fcafc
AB
276 return subprocess.check_output(self._command + cmd,
277 stderr=subprocess.STDOUT,
278 encoding='utf-8',
279 **kwargs)
2d110c11
JS
280 except TypeError:
281 # 'encoding' argument was added in 3.6+
884fcafc
AB
282 return subprocess.check_output(self._command + cmd,
283 stderr=subprocess.STDOUT,
284 **kwargs).decode('utf-8')
285
4485b04b 286
f97da1f7
AB
287 def inspect_tag(self, tag):
288 try:
289 return self._output(["inspect", tag])
290 except subprocess.CalledProcessError:
291 return None
292
7b882245
AB
293 def get_image_creation_time(self, info):
294 return json.loads(info)[0]["Created"]
295
4485b04b 296 def get_image_dockerfile_checksum(self, tag):
f97da1f7 297 resp = self.inspect_tag(tag)
4485b04b
FZ
298 labels = json.loads(resp)[0]["Config"].get("Labels", {})
299 return labels.get("com.qemu.dockerfile-checksum", "")
300
414a8ce5 301 def build_image(self, tag, docker_dir, dockerfile,
e6f1306b
AB
302 quiet=True, user=False, argv=None, registry=None,
303 extra_files_cksum=[]):
432d8ad5 304 if argv is None:
4485b04b 305 argv = []
4485b04b 306
07056db1
AB
307 if not _dockerfile_verify_flat(dockerfile):
308 return -1
e6f1306b 309
07056db1 310 checksum = _text_checksum(dockerfile)
e6f1306b 311
4112aff7
AB
312 tmp_df = tempfile.NamedTemporaryFile(mode="w+t",
313 encoding='utf-8',
314 dir=docker_dir, suffix=".docker")
4485b04b
FZ
315 tmp_df.write(dockerfile)
316
414a8ce5
AB
317 if user:
318 uid = os.getuid()
319 uname = getpwuid(uid).pw_name
320 tmp_df.write("\n")
321 tmp_df.write("RUN id %s 2>/dev/null || useradd -u %d -U %s" %
322 (uname, uid, uname))
323
4485b04b 324 tmp_df.write("\n")
e405a3eb 325 tmp_df.write("LABEL com.qemu.dockerfile-checksum=%s\n" % (checksum))
f9172822 326 for f, c in extra_files_cksum:
e405a3eb 327 tmp_df.write("LABEL com.qemu.%s-checksum=%s\n" % (f, c))
f9172822 328
4485b04b 329 tmp_df.flush()
a9f8d038 330
e6f1306b
AB
331 build_args = ["build", "-t", tag, "-f", tmp_df.name]
332 if self._buildkit:
333 build_args += ["--build-arg", "BUILDKIT_INLINE_CACHE=1"]
334
335 if registry is not None:
f73e4852
AB
336 pull_args = ["pull", "%s/%s" % (registry, tag)]
337 self._do(pull_args, quiet=quiet)
e6f1306b
AB
338 cache = "%s/%s" % (registry, tag)
339 build_args += ["--cache-from", cache]
340 build_args += argv
341 build_args += [docker_dir]
342
343 self._do_check(build_args,
0b95ff72 344 quiet=quiet)
4485b04b 345
6e733da6
AB
346 def update_image(self, tag, tarball, quiet=True):
347 "Update a tagged image using "
348
0b95ff72 349 self._do_check(["build", "-t", tag, "-"], quiet=quiet, stdin=tarball)
6e733da6 350
4485b04b
FZ
351 def image_matches_dockerfile(self, tag, dockerfile):
352 try:
353 checksum = self.get_image_dockerfile_checksum(tag)
354 except Exception:
355 return False
07056db1 356 return checksum == _text_checksum(dockerfile)
4485b04b 357
71ebbe09 358 def run(self, cmd, keep, quiet, as_user=False):
529994e2 359 label = uuid.uuid4().hex
4485b04b 360 if not keep:
529994e2 361 self._instance = label
71ebbe09
AB
362
363 if as_user:
364 uid = os.getuid()
365 cmd = [ "-u", str(uid) ] + cmd
366 # podman requires a bit more fiddling
367 if self._command[0] == "podman":
b3a790be 368 cmd.insert(0, '--userns=keep-id')
71ebbe09 369
17cd6e2b 370 ret = self._do_check(["run", "--rm", "--label",
0b95ff72
FZ
371 "com.qemu.instance.uuid=" + label] + cmd,
372 quiet=quiet)
4485b04b 373 if not keep:
529994e2 374 self._instance = None
4485b04b
FZ
375 return ret
376
4b08af60
FZ
377 def command(self, cmd, argv, quiet):
378 return self._do([cmd] + argv, quiet=quiet)
379
432d8ad5 380
4485b04b
FZ
381class SubCommand(object):
382 """A SubCommand template base class"""
432d8ad5
AB
383 name = None # Subcommand name
384
4485b04b
FZ
385 def shared_args(self, parser):
386 parser.add_argument("--quiet", action="store_true",
e50a6121 387 help="Run quietly unless an error occurred")
4485b04b
FZ
388
389 def args(self, parser):
390 """Setup argument parser"""
391 pass
432d8ad5 392
4485b04b
FZ
393 def run(self, args, argv):
394 """Run command.
395 args: parsed argument by argument parser.
396 argv: remaining arguments from sys.argv.
397 """
398 pass
399
432d8ad5 400
4485b04b
FZ
401class RunCommand(SubCommand):
402 """Invoke docker run and take care of cleaning up"""
403 name = "run"
432d8ad5 404
4485b04b
FZ
405 def args(self, parser):
406 parser.add_argument("--keep", action="store_true",
407 help="Don't remove image when command completes")
2461d80e
MAL
408 parser.add_argument("--run-as-current-user", action="store_true",
409 help="Run container using the current user's uid")
432d8ad5 410
4485b04b 411 def run(self, args, argv):
71ebbe09
AB
412 return Docker().run(argv, args.keep, quiet=args.quiet,
413 as_user=args.run_as_current_user)
4485b04b 414
432d8ad5 415
4485b04b 416class BuildCommand(SubCommand):
432d8ad5 417 """ Build docker image out of a dockerfile. Arg: <tag> <dockerfile>"""
4485b04b 418 name = "build"
432d8ad5 419
4485b04b 420 def args(self, parser):
504ca3c2
AB
421 parser.add_argument("--include-executable", "-e",
422 help="""Specify a binary that will be copied to the
423 container together with all its dependent
424 libraries""")
ddd5ed83
AB
425 parser.add_argument("--skip-binfmt",
426 action="store_true",
427 help="""Skip binfmt entry check (used for testing)""")
dfae6284 428 parser.add_argument("--extra-files", nargs='*',
4c84f662
PMD
429 help="""Specify files that will be copied in the
430 Docker image, fulfilling the ADD directive from the
431 Dockerfile""")
414a8ce5
AB
432 parser.add_argument("--add-current-user", "-u", dest="user",
433 action="store_true",
434 help="Add the current user to image's passwd")
e6f1306b
AB
435 parser.add_argument("--registry", "-r",
436 help="cache from docker registry")
dfae6284 437 parser.add_argument("-t", dest="tag",
4485b04b 438 help="Image Tag")
dfae6284 439 parser.add_argument("-f", dest="dockerfile",
4485b04b
FZ
440 help="Dockerfile name")
441
442 def run(self, args, argv):
4112aff7 443 dockerfile = _read_dockerfile(args.dockerfile)
4485b04b
FZ
444 tag = args.tag
445
446 dkr = Docker()
6fe3ae3f
AB
447 if "--no-cache" not in argv and \
448 dkr.image_matches_dockerfile(tag, dockerfile):
4485b04b 449 if not args.quiet:
f03868bd 450 print("Image is up to date.")
a9f8d038
AB
451 else:
452 # Create a docker context directory for the build
453 docker_dir = tempfile.mkdtemp(prefix="docker_build")
454
15352dec 455 # Validate binfmt_misc will work
ddd5ed83
AB
456 if args.skip_binfmt:
457 qpath = args.include_executable
458 elif args.include_executable:
d10404b1
AB
459 qpath, enabled = _check_binfmt_misc(args.include_executable)
460 if not enabled:
15352dec
AB
461 return 1
462
920776ea
AB
463 # Is there a .pre file to run in the build context?
464 docker_pre = os.path.splitext(args.dockerfile)[0]+".pre"
465 if os.path.exists(docker_pre):
f8042dea 466 stdout = DEVNULL if args.quiet else None
920776ea 467 rc = subprocess.call(os.path.realpath(docker_pre),
f8042dea 468 cwd=docker_dir, stdout=stdout)
920776ea 469 if rc == 3:
f03868bd 470 print("Skip")
920776ea
AB
471 return 0
472 elif rc != 0:
f03868bd 473 print("%s exited with code %d" % (docker_pre, rc))
920776ea
AB
474 return 1
475
4c84f662
PMD
476 # Copy any extra files into the Docker context. These can be
477 # included by the use of the ADD directive in the Dockerfile.
438d1168 478 cksum = []
504ca3c2 479 if args.include_executable:
438d1168
PMD
480 # FIXME: there is no checksum of this executable and the linked
481 # libraries, once the image built any change of this executable
482 # or any library won't trigger another build.
d10404b1
AB
483 _copy_binary_with_libs(args.include_executable,
484 qpath, docker_dir)
485
4c84f662
PMD
486 for filename in args.extra_files or []:
487 _copy_with_mkdir(filename, docker_dir)
f9172822 488 cksum += [(filename, _file_checksum(filename))]
504ca3c2 489
06cc3551 490 argv += ["--build-arg=" + k.lower() + "=" + v
4112aff7 491 for k, v in os.environ.items()
432d8ad5 492 if k.lower() in FILTERED_ENV_NAMES]
a9f8d038 493 dkr.build_image(tag, docker_dir, dockerfile,
e6f1306b
AB
494 quiet=args.quiet, user=args.user,
495 argv=argv, registry=args.registry,
438d1168 496 extra_files_cksum=cksum)
a9f8d038
AB
497
498 rmtree(docker_dir)
4485b04b 499
4485b04b
FZ
500 return 0
501
c3ad9043
AB
502class FetchCommand(SubCommand):
503 """ Fetch a docker image from the registry. Args: <tag> <registry>"""
504 name = "fetch"
505
506 def args(self, parser):
507 parser.add_argument("tag",
508 help="Local tag for image")
509 parser.add_argument("registry",
510 help="Docker registry")
511
512 def run(self, args, argv):
513 dkr = Docker()
514 dkr.command(cmd="pull", quiet=args.quiet,
515 argv=["%s/%s" % (args.registry, args.tag)])
516 dkr.command(cmd="tag", quiet=args.quiet,
517 argv=["%s/%s" % (args.registry, args.tag), args.tag])
518
432d8ad5 519
6e733da6 520class UpdateCommand(SubCommand):
bf46c0ee 521 """ Update a docker image. Args: <tag> <actions>"""
6e733da6 522 name = "update"
432d8ad5 523
6e733da6
AB
524 def args(self, parser):
525 parser.add_argument("tag",
526 help="Image Tag")
8d628d07 527 parser.add_argument("--executable",
6e733da6 528 help="Executable to copy")
bf46c0ee
AB
529 parser.add_argument("--add-current-user", "-u", dest="user",
530 action="store_true",
531 help="Add the current user to image's passwd")
6e733da6
AB
532
533 def run(self, args, argv):
534 # Create a temporary tarball with our whole build context and
535 # dockerfile for the update
536 tmp = tempfile.NamedTemporaryFile(suffix="dckr.tar.gz")
537 tmp_tar = TarFile(fileobj=tmp, mode='w')
538
6e733da6
AB
539 # Create a Docker buildfile
540 df = StringIO()
e336cec3 541 df.write(u"FROM %s\n" % args.tag)
8d628d07
AB
542
543 if args.executable:
544 # Add the executable to the tarball, using the current
545 # configured binfmt_misc path. If we don't get a path then we
546 # only need the support libraries copied
547 ff, enabled = _check_binfmt_misc(args.executable)
548
549 if not enabled:
550 print("binfmt_misc not enabled, update disabled")
551 return 1
552
553 if ff:
554 tmp_tar.add(args.executable, arcname=ff)
555
556 # Add any associated libraries
557 libs = _get_so_libs(args.executable)
558 if libs:
559 for l in libs:
560 so_path = os.path.dirname(l)
561 name = os.path.basename(l)
562 real_l = os.path.realpath(l)
563 try:
564 tmp_tar.add(real_l, arcname="%s/%s" % (so_path, name))
565 except FileNotFoundError:
566 print("Couldn't add %s/%s to archive" % (so_path, name))
567 pass
568
569 df.write(u"ADD . /\n")
e336cec3 570
bf46c0ee
AB
571 if args.user:
572 uid = os.getuid()
573 uname = getpwuid(uid).pw_name
574 df.write("\n")
575 df.write("RUN id %s 2>/dev/null || useradd -u %d -U %s" %
576 (uname, uid, uname))
577
e336cec3 578 df_bytes = BytesIO(bytes(df.getvalue(), "UTF-8"))
6e733da6
AB
579
580 df_tar = TarInfo(name="Dockerfile")
e336cec3
AB
581 df_tar.size = df_bytes.getbuffer().nbytes
582 tmp_tar.addfile(df_tar, fileobj=df_bytes)
6e733da6
AB
583
584 tmp_tar.close()
585
586 # reset the file pointers
587 tmp.flush()
588 tmp.seek(0)
589
590 # Run the build with our tarball context
591 dkr = Docker()
592 dkr.update_image(args.tag, tmp, quiet=args.quiet)
593
594 return 0
595
432d8ad5 596
4485b04b
FZ
597class CleanCommand(SubCommand):
598 """Clean up docker instances"""
599 name = "clean"
432d8ad5 600
4485b04b
FZ
601 def run(self, args, argv):
602 Docker().clean()
603 return 0
604
432d8ad5 605
4b08af60
FZ
606class ImagesCommand(SubCommand):
607 """Run "docker images" command"""
608 name = "images"
432d8ad5 609
4b08af60
FZ
610 def run(self, args, argv):
611 return Docker().command("images", argv, args.quiet)
612
15df9d37
AB
613
614class ProbeCommand(SubCommand):
615 """Probe if we can run docker automatically"""
616 name = "probe"
617
618 def run(self, args, argv):
619 try:
620 docker = Docker()
621 if docker._command[0] == "docker":
8480517d 622 print("docker")
15df9d37 623 elif docker._command[0] == "sudo":
8480517d 624 print("sudo docker")
9459f754
MAL
625 elif docker._command[0] == "podman":
626 print("podman")
15df9d37 627 except Exception:
f03868bd 628 print("no")
15df9d37
AB
629
630 return
631
632
5e03c2d8
AB
633class CcCommand(SubCommand):
634 """Compile sources with cc in images"""
635 name = "cc"
636
637 def args(self, parser):
638 parser.add_argument("--image", "-i", required=True,
639 help="The docker image in which to run cc")
99cfdb86
AB
640 parser.add_argument("--cc", default="cc",
641 help="The compiler executable to call")
5e03c2d8
AB
642 parser.add_argument("--source-path", "-s", nargs="*", dest="paths",
643 help="""Extra paths to (ro) mount into container for
644 reading sources""")
645
646 def run(self, args, argv):
647 if argv and argv[0] == "--":
648 argv = argv[1:]
649 cwd = os.getcwd()
17cd6e2b 650 cmd = ["-w", cwd,
5e03c2d8
AB
651 "-v", "%s:%s:rw" % (cwd, cwd)]
652 if args.paths:
653 for p in args.paths:
654 cmd += ["-v", "%s:%s:ro,z" % (p, p)]
99cfdb86 655 cmd += [args.image, args.cc]
5e03c2d8 656 cmd += argv
71ebbe09
AB
657 return Docker().run(cmd, False, quiet=args.quiet,
658 as_user=True)
5e03c2d8
AB
659
660
4485b04b 661def main():
9459f754
MAL
662 global USE_ENGINE
663
4485b04b 664 parser = argparse.ArgumentParser(description="A Docker helper",
432d8ad5
AB
665 usage="%s <subcommand> ..." %
666 os.path.basename(sys.argv[0]))
9459f754
MAL
667 parser.add_argument("--engine", type=EngineEnum.argparse, choices=list(EngineEnum),
668 help="specify which container engine to use")
4485b04b
FZ
669 subparsers = parser.add_subparsers(title="subcommands", help=None)
670 for cls in SubCommand.__subclasses__():
671 cmd = cls()
672 subp = subparsers.add_parser(cmd.name, help=cmd.__doc__)
673 cmd.shared_args(subp)
674 cmd.args(subp)
675 subp.set_defaults(cmdobj=cmd)
676 args, argv = parser.parse_known_args()
8480517d
AB
677 if args.engine:
678 USE_ENGINE = args.engine
4485b04b
FZ
679 return args.cmdobj.run(args, argv)
680
432d8ad5 681
4485b04b
FZ
682if __name__ == "__main__":
683 sys.exit(main())