diff --git a/buildstream/_platform/linux.py b/buildstream/_platform/linux.py index 702059a5d4b8ea08b3471ef156babf39eb5ce793..ac2488095982698cb88483418894e677e890a256 100644 --- a/buildstream/_platform/linux.py +++ b/buildstream/_platform/linux.py @@ -63,6 +63,10 @@ class Linux(Platform): self._linux32 = False def create_sandbox(self, *args, **kwargs): + # FIXME: we need a better way rather than hijacking + # the normal setup process + from ..sandbox._sandboxdocker import SandboxDocker + return SandboxDocker(*args, **kwargs) if not self._local_sandbox_available: return self._create_dummy_sandbox(*args, **kwargs) else: diff --git a/buildstream/sandbox/_sandboxdocker.py b/buildstream/sandbox/_sandboxdocker.py new file mode 100755 index 0000000000000000000000000000000000000000..d7342a9f5ad7182eb08824782263e50b19e9124e --- /dev/null +++ b/buildstream/sandbox/_sandboxdocker.py @@ -0,0 +1,87 @@ +import os +import sys +import stat +import signal +import subprocess +from contextlib import contextmanager, ExitStack +import psutil +import tempfile + +import docker + +from .._exceptions import SandboxError +from .. import utils +from .. import _signals +from ._mounter import Mounter +from ._mount import MountMap +from . import Sandbox, SandboxFlags + + +DOCKERFILE = """ +FROM scratch + +ADD . / +""".strip() + + +class SandboxDocker(Sandbox): + + def run(self, command, flags, *, cwd=None, env=None): + client = docker.from_env() + stdout, stderr = self._get_output() + + # Fallback to the sandbox default settings for + # the cwd and env. + # + cwd = self._get_work_directory(cwd=cwd) + env = self._get_environment(cwd=cwd, env=env) + + # Convert single-string argument to a list + if isinstance(command, str): + command = [command] + + if not self._has_command(command[0], env): + raise SandboxError("Staged artifacts do not provide command " + "'{}'".format(command[0]), + reason='missing-command') + + # Create the mount map, this will tell us where + # each mount point needs to be mounted from and to + mount_map = MountMap(self, flags & SandboxFlags.ROOT_READ_ONLY) + root_mount_source = mount_map.get_mount_source("/") + + with open(os.path.join(root_mount_source, "Dockerfile"), "w") as fp: + fp.write(DOCKERFILE) + + image, _ = client.images.build(path=root_mount_source) + + volumes = {} + + mount_source_overrides = self._get_mount_sources() + for mark in self._get_marked_directories(): + mount_point = mark["directory"] + if mount_point in mount_source_overrides: + mount_source = mount_source_overrides[mount_point] + else: + mount_source = mount_map.get_mount_source(mount_point) + + volumes[mount_source] = {"bind": mount_point} + + # TODO: we need to handle root that is RO + # TODO: we need to handle cwd + # TODO: we need to handle env + # TODO: we need to support specific user uid/gid + # TODO: we need to support interactive mode + args = { + "image": image, + "command": command, + "detach": True, + "volumes": volumes, + } + + container = client.containers.run(**args) + # TODO: we need to handle signals and react accordingly + status = container.wait() + + self._vdir._mark_changed() + return status["StatusCode"]