From 0339a34458823a1fe6e25fd8a5c2b218a63ac0d7 Mon Sep 17 00:00:00 2001 From: David Shrewsbury Date: Wed, 10 Sep 2014 15:35:19 -0400 Subject: [PATCH] Better support for multiple commands This changes the 'commands' YAML portion to be an array. E.g.: commands: - ls - ps - echo 'Have you tried turning it off and on again?' Docker doesn't really support issuing multiple commands to a container on the command line very well. You have to either munge it with something like: sh -c 'ls && ps' or place all of the commands to execute into a master script, then execute that script. This change proposes the second option and creates a master script if the number of commands is more than one. The master script file will be created in a .dox subdirectory of the current directory, and named master_script.sh. Right now, it is created as a Bourne shell script. We could easily add support later for different shells. If the -c option is used, then the command specified is the only command that will run. If -c is NOT used, but extra arguments are supplied, these extra arguments will be used to replace the {posargs} placeholders in tox.ini files. The extra args are not currently used elsewhere. This change REMOVES the ability to add extra_args to the end of the command when {posargs} is NOT present. That does not make sense in a multi-command situation. Change-Id: I78dbc0613f5db22cea0ed714f5f30ef0207fac24 --- dox/cmd.py | 5 ++-- dox/commands.py | 41 ++++++++++++++++++++++++++----- dox/config/dox_yaml.py | 2 +- dox/config/tox_ini.py | 21 ++++++++++------ dox/tests/config/test_commands.py | 10 ++++---- dox/tests/config/test_tox_ini.py | 5 +--- 6 files changed, 59 insertions(+), 25 deletions(-) diff --git a/dox/cmd.py b/dox/cmd.py index 88a659b..10c021f 100644 --- a/dox/cmd.py +++ b/dox/cmd.py @@ -89,8 +89,10 @@ def run_dox(args): # Get Command if args.command: command = dox.config.cmdline.CommandLine(args.extra_args) + logger.debug("Command source is the command line") else: command = dox.commands.Commands(args.extra_args) + logger.debug("Command source is %s" % command.source.source_name()) # Run try: @@ -98,6 +100,5 @@ def run_dox(args): command=command) map(run, images) except Exception: - logger.error( - "Operation failed, aborting dox.", exc_info=args.debug) + logger.error("Operation failed, aborting dox.", exc_info=args.debug) return 1 diff --git a/dox/commands.py b/dox/commands.py index f1cd558..54846f8 100644 --- a/dox/commands.py +++ b/dox/commands.py @@ -19,12 +19,12 @@ __all__ = [ ] import logging +import os import dox.config.dox_yaml import dox.config.tox_ini import dox.config.travis_yaml - logger = logging.getLogger(__name__) @@ -37,7 +37,6 @@ def get_commands(): for source in (dox_yaml, tox_ini, travis_yaml): if source.exists(): - logger.debug("Command source is: %s" % source.source_name()) return source raise Exception("dox cannot figure out what command to run") @@ -49,12 +48,42 @@ class Commands(object): self.args = [] self.extra_args = extra_args + def _test_command_as_script(self, commands, shell='/bin/sh'): + """Combine test commands into a master script file. + + The script, using the given shell, will be created in the .dox + subdirectory of the current directory. + + :param commands: A list of commands to execute. + :param shell: Path to the OS shell to run the commands. + """ + dox_dir = '.dox' + master_script = os.path.join(dox_dir, 'master_script.sh') + + if not os.path.exists(dox_dir): + os.mkdir(dox_dir, 0o755) + + with open(master_script, "w") as f: + f.write("#!" + shell + "\n") + f.write("\n".join(commands)) + f.write("\n") + + os.chmod(master_script, 0o700) + return master_script + def test_command(self): + """Return the command to execute in the container. + + If there is more than one command, we combine them into a master + script to execute. Otherwise, we just issue the command normally + on the docker command line. + """ commands = self.source.get_commands(self.extra_args) - if hasattr(commands, 'append'): - ret = "\n".join(commands) - else: - ret = commands + ' ' + ' '.join(self.args) + + if len(commands) > 1: + return self._test_command_as_script(commands) + + ret = commands[0] + ' ' + ' '.join(self.args) return ret.strip() def prep_commands(self): diff --git a/dox/config/dox_yaml.py b/dox/config/dox_yaml.py index db5099c..e66c753 100644 --- a/dox/config/dox_yaml.py +++ b/dox/config/dox_yaml.py @@ -54,7 +54,7 @@ class DoxYaml(base.ConfigBase): return self._open_dox_yaml().get('images', []) def get_commands(self, extra_args): - return " ".join([self._open_dox_yaml().get('commands')] + extra_args) + return self._open_dox_yaml().get('commands', []) def get_prep_commands(self): return self._open_dox_yaml().get('prep', []) diff --git a/dox/config/tox_ini.py b/dox/config/tox_ini.py index 0d5f615..aed83f0 100644 --- a/dox/config/tox_ini.py +++ b/dox/config/tox_ini.py @@ -56,15 +56,22 @@ class ToxIni(base.ConfigBase): return ini.get('docker', 'images', '').split(',') def get_commands(self, extra_args, section='testenv'): + """Get commands to run from the config file. + + If any of the commands contain the string '{posargs}', then this + is replaced with the extra_args value. + """ ini = self._open_tox_ini() - commands = ini.get(section, 'commands') + commands = ini.get(section, 'commands').split("\n") extra_args = " ".join(extra_args) - if '{posargs}' in commands: - commands = commands.replace('{posargs}', extra_args) - else: - commands += " " - commands += extra_args - return commands + + scrubbed = [] + for cmd in commands: + if '{posargs}' in cmd: + scrubbed.append(cmd.replace('{posargs}', extra_args)) + else: + scrubbed.append(cmd) + return scrubbed def get_prep_commands(self): ini = self._open_tox_ini() diff --git a/dox/tests/config/test_commands.py b/dox/tests/config/test_commands.py index 2602379..2d99e19 100644 --- a/dox/tests/config/test_commands.py +++ b/dox/tests/config/test_commands.py @@ -39,20 +39,20 @@ class TestCommands(base.TestCase): scenarios = [ ('dox_yaml', dict( dox_yaml=True, tox_ini=False, travis_yaml=False, - dox_value="testr run", tox_value=None, travis_value=None, + dox_value=["testr run"], tox_value=None, travis_value=None, commands="testr run")), ('dox_yaml_ignore_others', dict( dox_yaml=True, tox_ini=True, travis_yaml=True, - dox_value="testr run", tox_value="setup.py test", - travis_value="gem test", + dox_value=["testr run"], tox_value=["setup.py test"], + travis_value=["gem test"], commands="testr run")), ('tox_ini', dict( dox_yaml=False, tox_ini=True, travis_yaml=False, - dox_value=None, tox_value="setup.py test", travis_value=None, + dox_value=None, tox_value=["setup.py test"], travis_value=None, commands="setup.py test")), ('travis_yaml', dict( dox_yaml=False, tox_ini=False, travis_yaml=True, - dox_value="testr run", tox_value=None, travis_value="ruby", + dox_value=["testr run"], tox_value=None, travis_value=["ruby"], commands="ruby")), ] diff --git a/dox/tests/config/test_tox_ini.py b/dox/tests/config/test_tox_ini.py index 037c5c7..2bd7168 100644 --- a/dox/tests/config/test_tox_ini.py +++ b/dox/tests/config/test_tox_ini.py @@ -51,10 +51,7 @@ class TestToxIni(base.TestCase): self.toxini.get_images()) def test_get_commands(self): - self.assertEqual('foobar -c', - self.toxini.get_commands(['-c'])) - - self.assertEqual('foobar -c blah', + self.assertEqual(['foobar -c blah'], self.toxini.get_commands( ['-c'], section='testenv2'))