diff options
-rw-r--r-- | morphlib/app.py | 27 | ||||
-rw-r--r-- | morphlib/builder2.py | 17 | ||||
-rwxr-xr-x | morphlib/exts/nfsboot.write | 2 | ||||
-rwxr-xr-x | morphlib/exts/openstack.check | 2 | ||||
-rw-r--r-- | morphlib/stagingarea.py | 50 | ||||
-rw-r--r-- | morphlib/util.py | 17 |
6 files changed, 78 insertions, 37 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/exts/nfsboot.write b/morphlib/exts/nfsboot.write index 9fa6fc84..49d71174 100755 --- a/morphlib/exts/nfsboot.write +++ b/morphlib/exts/nfsboot.write @@ -21,7 +21,7 @@ - This was written before 'proper' deployment mechanisms were in place It is unlikely to work at all and will not work correctly -Use the pxeboot werite extension instead +Use the pxeboot write extension instead *** diff --git a/morphlib/exts/openstack.check b/morphlib/exts/openstack.check index a6856c31..3850d481 100755 --- a/morphlib/exts/openstack.check +++ b/morphlib/exts/openstack.check @@ -81,7 +81,7 @@ class OpenStackCheckExtension(morphlib.writeexts.WriteExtension): exit, out, err = cliapp.runcmd_unchecked(cmdline) if exit != 0: - if err.startswith('The request you have made requires ' \ + if err.startswith('The request you have made requires ' 'authentication. (HTTP 401)'): raise cliapp.AppException('Invalid OpenStack credentials.') else: 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) |