summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Thursfield <sam.thursfield@codethink.co.uk>2015-05-27 16:46:47 +0000
committerSam Thursfield <sam.thursfield@codethink.co.uk>2015-06-10 18:21:42 +0100
commit46158b0a486223eb31b6fb329ac348940f27c310 (patch)
tree2a2846fbf05559241c751c0ab584ea9ffc8e5fe4
parent7a81a02b3a533a00740a3932b2e10d6377f45ede (diff)
downloadmorph-sam/sandboxlib.tar.gz
WIP: Use 'sandboxlib' library where possible.sam/sandboxlib
This doesn't quite work because of the need to use 'tee' in a new way. Really sandboxlib needs to handle tee-ing stdout and stderr itself. Change-Id: Ia5c9182beb486f6223670eab8dc429e30cd4f94a
-rw-r--r--morphlib/builder.py52
-rw-r--r--morphlib/extensions.py11
-rw-r--r--morphlib/stagingarea.py94
-rw-r--r--morphlib/util.py3
4 files changed, 91 insertions, 69 deletions
diff --git a/morphlib/builder.py b/morphlib/builder.py
index 8caaaf39..66d980bb 100644
--- a/morphlib/builder.py
+++ b/morphlib/builder.py
@@ -17,6 +17,7 @@ import json
import logging
import os
from os.path import relpath
+import pipes
import shutil
import stat
import tarfile
@@ -26,10 +27,10 @@ import subprocess
import tempfile
import cliapp
+import sandboxlib
import morphlib
from morphlib.artifactcachereference import ArtifactCacheReference
-from morphlib.util import error_message_for_containerised_commandline
import morphlib.gitversion
SYSTEM_INTEGRATION_PATH = os.path.join('baserock', 'system-integration')
@@ -365,12 +366,28 @@ class ChunkBuilder(BuilderBase):
if stdout:
stdout.flush()
- self.runcmd(['sh', '-c', cmd],
+ # Nasty hack to allow both capturing stdout and writing
+ # it to a file. It'd be better to do this in-process.
+ # Adding the equivalent of 'tee' to sandboxlib would be
+ # the best option.
+ #
+ # Just writing stdout and stderr to the log file
+ # after the command finishes isn't ideal because the
+ # command might take a long time, and it's good to be
+ # able to watch the output 'live'.
+ #
+ # The 'pipefail' option is available in Busybox sh,
+ # GNU Bash, ksh and zsh, but is not in POSIX. It is
+ # needed so that we get the exit code of the actual
+ # command, not the 'tee' that follows it.
+ argv = ['sh', '-o', 'pipefail', '-c', "%s | tee -a %s" %
+ (cmd, pipes.quote(logfilepath))]
+
+ self.runcmd(argv,
extra_env=extra_env,
cwd=relative_builddir,
stdout=stdout or subprocess.PIPE,
stderr=subprocess.STDOUT,
- logfile=logfilepath,
ccache_dir=ccache_dir)
if stdout:
@@ -655,23 +672,28 @@ class SystemBuilder(BuilderBase): # pragma: no cover
self.app.status(msg='Running the system integration commands')
to_mount = (
- ('dev/shm', 'tmpfs', 'none'),
- ('tmp', 'tmpfs', 'none'),
+ (None, '/dev/shm', 'tmpfs'),
+ (None, '/proc', 'proc'),
+ (None, '/tmp', 'tmpfs'),
)
try:
for bin in sorted(os.listdir(sys_integration_dir)):
argv = [os.path.join(SYSTEM_INTEGRATION_PATH, bin)]
- container_config = dict(
- root=rootdir, mounts=to_mount, mount_proc=True)
- cmdline = morphlib.util.containerised_cmdline(
- argv, **container_config)
- exit, out, err = self.app.runcmd_unchecked(
- cmdline, env=env)
+
+ sandbox_config = dict(
+ env=env,
+ filesystem_root=rootdir,
+ mounts='isolated',
+ network='isolated',
+ extra_mounts=to_mount)
+
+ exit, out, err = sandboxlib.linux_user_chroot.run_sandbox(
+ argv, **sandbox_config)
+
if exit != 0:
- logging.debug('Command returned code %i', exit)
- msg = error_message_for_containerised_commandline(
- argv, err, container_config)
- raise cliapp.AppException(msg)
+ message = ('Command %s returned code %i, error output %s' %
+ (argv, exit, err))
+ raise cliapp.AppException(message)
except BaseException as e:
self.app.status(
msg='Error while running system integration commands',
diff --git a/morphlib/extensions.py b/morphlib/extensions.py
index 59281d66..dc2acc15 100644
--- a/morphlib/extensions.py
+++ b/morphlib/extensions.py
@@ -227,8 +227,15 @@ class ExtensionSubprocess(object):
cmdline = [filename] + list(args)
if separate_mount_namespace:
- cmdline = morphlib.util.unshared_cmdline(cmdline)
-
+ cmdline = morphlib.util.separate_mount_namespace_cmdline(
+ cmdline)
+
+ # FIXME: It would be good to use 'sandboxlib' to run the extensions
+ # in a sandbox. However, this might break some of them, because
+ # they may assume that stuff from the host system can be accessed
+ # in certain ways. It needs to be done carefully. Probably defining
+ # 'a system in which to run the deployment' would be a good first
+ # step, so tools aren't needed to be included in the host system.
p = subprocess.Popen(
cmdline,
cwd=cwd, env=new_env,
diff --git a/morphlib/stagingarea.py b/morphlib/stagingarea.py
index 76bb3a18..416c46a4 100644
--- a/morphlib/stagingarea.py
+++ b/morphlib/stagingarea.py
@@ -17,10 +17,12 @@ import logging
import os
import shutil
import stat
-import cliapp
from urlparse import urlparse
import tempfile
+import cliapp
+import sandboxlib
+
import morphlib
@@ -44,7 +46,6 @@ class StagingArea(object):
self.dirname = dirname
self.builddirname = None
self.destdirname = None
- self._bind_readonly_mount = None
self.use_chroot = use_chroot
self.env = build_env.env
@@ -187,7 +188,8 @@ class StagingArea(object):
shutil.rmtree(self.dirname)
to_mount_in_staging = (
- ('dev/shm', 'tmpfs', 'none'),
+ (None, '/dev/shm', 'tmpfs'),
+ (None, '/proc', 'proc')
)
to_mount_in_bootstrap = ()
@@ -241,69 +243,59 @@ class StagingArea(object):
# No cleanup is currently required
pass
- def runcmd(self, argv, **kwargs): # pragma: no cover
+ def runcmd(self, argv, cwd=None, extra_env=None, ccache_dir=None,
+ stdout=sandboxlib.CAPTURE,
+ stderr=sandboxlib.CAPTURE): # pragma: no cover
'''Run a command in a chroot in the staging area.'''
- assert 'env' not in kwargs
- kwargs['env'] = dict(self.env)
- if 'extra_env' in kwargs:
- kwargs['env'].update(kwargs['extra_env'])
- del kwargs['extra_env']
-
- ccache_dir = kwargs.pop('ccache_dir', None)
+ env = dict(self.env)
+ if extra_env is not None:
+ env.update(extra_env)
chroot_dir = self.dirname if self.use_chroot else '/'
- temp_dir = kwargs["env"].get("TMPDIR", "/tmp")
- staging_dirs = [self.builddirname, self.destdirname]
+ temp_dir = env.get("TMPDIR", "/tmp")
+
if self.use_chroot:
- staging_dirs += ["dev", "proc", temp_dir.lstrip('/')]
- do_not_mount_dirs = [os.path.join(self.dirname, d)
- for d in staging_dirs]
- if not self.use_chroot:
- do_not_mount_dirs += [temp_dir]
- logging.debug("Not mounting dirs %r" % do_not_mount_dirs)
+ writable_paths = [
+ self.relative(self.builddirname),
+ self.relative(self.destdirname),
+ temp_dir, "dev", "proc"]
+ else:
+ writable_paths = [self.builddirname, self.destdirname, temp_dir]
+
+ logging.debug(
+ "The following directories will be writable in the sandbox: %r" %
+ writable_paths)
if self.use_chroot:
- mounts = self.to_mount_in_staging
+ mounts = list(self.to_mount_in_staging)
else:
- mounts = [(os.path.join(self.dirname, target), type, source)
- for target, type, source in self.to_mount_in_bootstrap]
- mount_proc = self.use_chroot
+ mounts = list(self.to_mount_in_bootstrap)
if ccache_dir and not self._app.settings['no-ccache']:
- ccache_target = os.path.join(
- self.dirname, kwargs['env']['CCACHE_DIR'].lstrip('/'))
- binds = ((ccache_dir, ccache_target),)
- else:
- binds = ()
-
- container_config=dict(
- cwd=kwargs.pop('cwd', '/'),
- root=chroot_dir,
- mounts=mounts,
- mount_proc=mount_proc,
- binds=binds,
- writable_paths=do_not_mount_dirs)
-
- cmdline = morphlib.util.containerised_cmdline(
- argv, **container_config)
-
- if kwargs.get('logfile') != None:
- logfile = kwargs.pop('logfile')
- teecmd = ['tee', '-a', logfile]
- exit, out, err = self._app.runcmd_unchecked(
- cmdline, teecmd, **kwargs)
- else:
- exit, out, err = self._app.runcmd_unchecked(cmdline, **kwargs)
+ ccache_target = env['CCACHE_DIR']
+ mounts.append([ccache_dir, ccache_target, None, 'bind'])
+
+ sandbox_config=dict(
+ cwd=cwd,
+ env=env,
+ filesystem_root=chroot_dir,
+ filesystem_writable_paths=writable_paths,
+ mounts='isolated',
+ network='isolated',
+ extra_mounts=mounts,
+ )
+
+ exit, out, err = sandboxlib.linux_user_chroot.run_sandbox(
+ argv, stdout=stdout, stderr=stderr, **sandbox_config)
if exit == 0:
return out
else:
- logging.debug('Command returned code %i', exit)
- msg = morphlib.util.error_message_for_containerised_commandline(
- argv, err, container_config)
+ logging.debug('Command %s returned code %i', argv, exit)
raise cliapp.AppException(
- 'In staging area %s: %s' % (self._failed_location(), msg))
+ 'In staging area %s: command %s failed. Error output: %s' %
+ (self._failed_location(), argv, err or out))
def _failed_location(self): # pragma: no cover
'''Path this staging area will be moved to if an error occurs.'''
diff --git a/morphlib/util.py b/morphlib/util.py
index fb9fba0a..652e6508 100644
--- a/morphlib/util.py
+++ b/morphlib/util.py
@@ -535,7 +535,8 @@ def get_data(relative_path): # pragma: no cover
return f.read()
-def unshared_cmdline(args, root='/', mounts=()): # pragma: no cover
+def separate_mount_namespace_cmdline(args, root='/',
+ mounts=()): # pragma: no cover
'''Describe how to run 'args' inside a separate mount namespace.
This function wraps 'args' in a rather long commandline that ensures