summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Thursfield <sam.thursfield@codethink.co.uk>2014-12-18 10:26:04 +0000
committerSam Thursfield <sam.thursfield@codethink.co.uk>2014-12-18 10:26:04 +0000
commitc57952ef44a0f1f161441970fcf2f27a39b0de7c (patch)
treeb4fee53259020cb4d3da21e352041fac3edf93f5
parent70858749d82afe841d06f6a96350168bc3ba51f7 (diff)
parent9449dbfe1bb1800dfb15de025b87e1846e25e74a (diff)
downloadmorph-c57952ef44a0f1f161441970fcf2f27a39b0de7c.tar.gz
Merge branch 'sam/improve-command-failure-errors'
Reviewed-By: Richard Ipsum <richard.ipsum@codethink.co.uk> Reviewed-By: Richard Maw <richard.maw@codethink.co.uk> Reviewed-By: Daniel Silverstone <daniel.silverstone@codethink.co.uk> Reviewed-By: Mike Smith <mike.smith@codethink.co.uk>
-rw-r--r--morphlib/app.py27
-rw-r--r--morphlib/builder2.py17
-rw-r--r--morphlib/stagingarea.py50
-rw-r--r--morphlib/util.py17
4 files changed, 76 insertions, 35 deletions
diff --git a/morphlib/app.py b/morphlib/app.py
index 930e023d..eb0ff3b7 100644
--- a/morphlib/app.py
+++ b/morphlib/app.py
@@ -17,6 +17,7 @@
import cliapp
import logging
import os
+import pipes
import sys
import time
import urlparse
@@ -348,7 +349,7 @@ class Morph(cliapp.Application):
self.output.write('%s %s\n' % (timestamp, text))
self.output.flush()
- def runcmd(self, argv, *args, **kwargs):
+ def _prepare_for_runcmd(self, argv, args, kwargs):
if 'env' not in kwargs:
kwargs['env'] = dict(os.environ)
@@ -358,16 +359,15 @@ class Morph(cliapp.Application):
else:
print_command = True
- # convert the command line arguments into a string
- commands = [argv] + list(args)
- for command in commands:
- if isinstance(command, list):
- for i in xrange(0, len(command)):
- command[i] = str(command[i])
- commands = [' '.join(command) for command in commands]
-
- # print the command line
if print_command:
+ # Print the command line
+ commands = [argv] + list(args)
+ for command in commands:
+ if isinstance(command, list):
+ for i in xrange(0, len(command)):
+ command[i] = str(command[i])
+ commands = ' '.join(map(pipes.quote, command))
+
self.status(msg='# %(cmdline)s',
cmdline=' | '.join(commands),
chatty=True)
@@ -377,9 +377,14 @@ class Morph(cliapp.Application):
morphlib.util.log_environment_changes(self, kwargs['env'], prev)
self.prev_env = kwargs['env']
- # run the command line
+ def runcmd(self, argv, *args, **kwargs):
+ self._prepare_for_runcmd(argv, args, kwargs)
return cliapp.Application.runcmd(self, argv, *args, **kwargs)
+ def runcmd_unchecked(self, argv, *args, **kwargs):
+ self._prepare_for_runcmd(argv, args, kwargs)
+ return cliapp.Application.runcmd_unchecked(self, argv, *args, **kwargs)
+
def parse_args(self, args, configs_only=False):
return self.settings.parse_args(args,
configs_only=configs_only,
diff --git a/morphlib/builder2.py b/morphlib/builder2.py
index f71f21db..9e158ea1 100644
--- a/morphlib/builder2.py
+++ b/morphlib/builder2.py
@@ -665,11 +665,18 @@ class SystemBuilder(BuilderBase): # pragma: no cover
)
try:
for bin in sorted(os.listdir(sys_integration_dir)):
- self.app.runcmd(
- morphlib.util.containerised_cmdline(
- [os.path.join(SYSTEM_INTEGRATION_PATH, bin)],
- root=rootdir, mounts=to_mount, mount_proc=True),
- env=env)
+ 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)
+ if exit != 0:
+ logging.debug('Command returned code %i', exit)
+ msg = error_message_for_containerised_commandline(
+ argv, err, container_config)
+ raise cliapp.AppException(msg)
except BaseException, e:
self.app.status(
msg='Error while running system integration commands',
diff --git a/morphlib/stagingarea.py b/morphlib/stagingarea.py
index b676d4db..e7783890 100644
--- a/morphlib/stagingarea.py
+++ b/morphlib/stagingarea.py
@@ -274,22 +274,38 @@ class StagingArea(object):
else:
binds = ()
+ container_config=dict(
+ cwd=kwargs.pop('cwd', '/'),
+ root=chroot_dir,
+ mounts=self.to_mount,
+ binds=binds,
+ mount_proc=mount_proc,
+ writable_paths=do_not_mount_dirs)
+
cmdline = morphlib.util.containerised_cmdline(
- argv, cwd=kwargs.pop('cwd', '/'),
- root=chroot_dir, mounts=self.to_mount,
- binds=binds, mount_proc=mount_proc,
- writable_paths=do_not_mount_dirs)
- try:
- if kwargs.get('logfile') != None:
- logfile = kwargs.pop('logfile')
- teecmd = ['tee', '-a', logfile]
- return self._app.runcmd(cmdline, teecmd, **kwargs)
- else:
- return self._app.runcmd(cmdline, **kwargs)
- except cliapp.AppException as e:
- raise cliapp.AppException('In staging area %s: running '
- 'command \'%s\' failed.' %
- (self.dirname, ' '.join(argv)))
+ 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)
+
+ 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)
+ raise cliapp.AppException(
+ 'In staging area %s: %s' % (self._failed_location(), msg))
+
+ def _failed_location(self): # pragma: no cover
+ '''Path this staging area will be moved to if an error occurs.'''
+ return os.path.join(self._app.settings['tempdir'], 'failed',
+ os.path.basename(self.dirname))
def abort(self): # pragma: no cover
'''Handle what to do with a staging area in the case of failure.
@@ -298,9 +314,7 @@ class StagingArea(object):
# TODO: when we add the option to throw away failed builds,
# hook it up here
-
- dest_dir = os.path.join(self._app.settings['tempdir'],
- 'failed', os.path.basename(self.dirname))
+ dest_dir = self._failed_location()
os.rename(self.dirname, dest_dir)
self.dirname = dest_dir
diff --git a/morphlib/util.py b/morphlib/util.py
index 6f735387..e7a8a50e 100644
--- a/morphlib/util.py
+++ b/morphlib/util.py
@@ -16,6 +16,7 @@
import contextlib
import itertools
import os
+import pipes
import re
import subprocess
import textwrap
@@ -41,7 +42,6 @@ try:
from multiprocessing import cpu_count
except NotImplementedError: # pragma: no cover
cpu_count = lambda: 1
-import os
def indent(string, spaces=4):
@@ -626,3 +626,18 @@ def containerised_cmdline(args, cwd='.', root='/', binds=(),
cmdargs.append(root)
cmdargs.extend(args)
return unshared_cmdline(cmdargs, root=root, **kwargs)
+
+
+def error_message_for_containerised_commandline(
+ argv, err, container_kwargs): # pragma: no cover
+ '''Return a semi-readable error message for a containerised command.'''
+
+ # This function should do some formatting of the container_kwargs dict,
+ # rather than just dumping it in the error message, but that is better than
+ # nothing.
+
+ argv_string = ' '.join(map(pipes.quote, argv))
+ return 'Command failed: %s:\n' \
+ 'Containerisation settings: %s\n' \
+ 'Error output:\n%s' \
+ % (argv_string, container_kwargs, err)