| 
 
 | 
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"] 
 |