From 58b38f32f304623b6ed5544fedca4b4d56fdbad8 Mon Sep 17 00:00:00 2001 From: Benjamin Schubert <ben.c.schubert@gmail.com> Date: Tue, 20 Nov 2018 17:54:07 +0000 Subject: [PATCH] First draft for docker sandbox --- buildstream/_platform/linux.py | 4 ++ buildstream/sandbox/_sandboxdocker.py | 87 +++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100755 buildstream/sandbox/_sandboxdocker.py diff --git a/buildstream/_platform/linux.py b/buildstream/_platform/linux.py index 702059a5d4..ac24880959 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 0000000000..d7342a9f5a --- /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"] -- GitLab