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
This commit is contained in:
David Shrewsbury 2014-09-10 15:35:19 -04:00
parent a21659d31e
commit 0339a34458
6 changed files with 59 additions and 25 deletions

View File

@ -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

View File

@ -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):

View File

@ -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', [])

View File

@ -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()

View File

@ -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")),
]

View File

@ -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'))