|
1
|
+import os
|
|
2
|
+import sys
|
|
3
|
+import stat
|
|
4
|
+import signal
|
|
5
|
+import subprocess
|
|
6
|
+from contextlib import contextmanager, ExitStack
|
|
7
|
+import psutil
|
|
8
|
+import tempfile
|
|
9
|
+
|
|
10
|
+import docker
|
|
11
|
+
|
|
12
|
+from .._exceptions import SandboxError
|
|
13
|
+from .. import utils
|
|
14
|
+from .. import _signals
|
|
15
|
+from ._mounter import Mounter
|
|
16
|
+from ._mount import MountMap
|
|
17
|
+from . import Sandbox, SandboxFlags
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+DOCKERFILE = """
|
|
21
|
+FROM scratch
|
|
22
|
+
|
|
23
|
+ADD . /
|
|
24
|
+""".strip()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+class SandboxDocker(Sandbox):
|
|
28
|
+
|
|
29
|
+ def run(self, command, flags, *, cwd=None, env=None):
|
|
30
|
+ client = docker.from_env()
|
|
31
|
+ stdout, stderr = self._get_output()
|
|
32
|
+
|
|
33
|
+ # Fallback to the sandbox default settings for
|
|
34
|
+ # the cwd and env.
|
|
35
|
+ #
|
|
36
|
+ cwd = self._get_work_directory(cwd=cwd)
|
|
37
|
+ env = self._get_environment(cwd=cwd, env=env)
|
|
38
|
+
|
|
39
|
+ # Convert single-string argument to a list
|
|
40
|
+ if isinstance(command, str):
|
|
41
|
+ command = [command]
|
|
42
|
+
|
|
43
|
+ if not self._has_command(command[0], env):
|
|
44
|
+ raise SandboxError("Staged artifacts do not provide command "
|
|
45
|
+ "'{}'".format(command[0]),
|
|
46
|
+ reason='missing-command')
|
|
47
|
+
|
|
48
|
+ # Create the mount map, this will tell us where
|
|
49
|
+ # each mount point needs to be mounted from and to
|
|
50
|
+ mount_map = MountMap(self, flags & SandboxFlags.ROOT_READ_ONLY)
|
|
51
|
+ root_mount_source = mount_map.get_mount_source("/")
|
|
52
|
+
|
|
53
|
+ with open(os.path.join(root_mount_source, "Dockerfile"), "w") as fp:
|
|
54
|
+ fp.write(DOCKERFILE)
|
|
55
|
+
|
|
56
|
+ image, _ = client.images.build(path=root_mount_source)
|
|
57
|
+
|
|
58
|
+ volumes = {}
|
|
59
|
+
|
|
60
|
+ mount_source_overrides = self._get_mount_sources()
|
|
61
|
+ for mark in self._get_marked_directories():
|
|
62
|
+ mount_point = mark["directory"]
|
|
63
|
+ if mount_point in mount_source_overrides:
|
|
64
|
+ mount_source = mount_source_overrides[mount_point]
|
|
65
|
+ else:
|
|
66
|
+ mount_source = mount_map.get_mount_source(mount_point)
|
|
67
|
+
|
|
68
|
+ volumes[mount_source] = {"bind": mount_point}
|
|
69
|
+
|
|
70
|
+ # TODO: we need to handle root that is RO
|
|
71
|
+ # TODO: we need to handle cwd
|
|
72
|
+ # TODO: we need to handle env
|
|
73
|
+ # TODO: we need to support specific user uid/gid
|
|
74
|
+ # TODO: we need to support interactive mode
|
|
75
|
+ args = {
|
|
76
|
+ "image": image,
|
|
77
|
+ "command": command,
|
|
78
|
+ "detach": True,
|
|
79
|
+ "volumes": volumes,
|
|
80
|
+ }
|
|
81
|
+
|
|
82
|
+ container = client.containers.run(**args)
|
|
83
|
+ # TODO: we need to handle signals and react accordingly
|
|
84
|
+ status = container.wait()
|
|
85
|
+
|
|
86
|
+ self._vdir._mark_changed()
|
|
87
|
+ return status["StatusCode"]
|