diff --git a/readthedocs/doc_builder/backends/sphinx.py b/readthedocs/doc_builder/backends/sphinx.py index d4e8bdd692a..0efb7c33fc0 100644 --- a/readthedocs/doc_builder/backends/sphinx.py +++ b/readthedocs/doc_builder/backends/sphinx.py @@ -242,16 +242,17 @@ def venv_sphinx_version(self): In this case, the output will be ``2.0.0``. """ - command = [ self.python_env.venv_bin(filename='python'), '-c' - '"import sphinx;print(sphinx.__version__)"', + '"import sphinx; print(sphinx.__version__)"', ] + cmd_ret = self.run( *command, - cwd=self.project.checkout_path(self.version.slug), bin_path=self.python_env.venv_bin(), + cwd=self.project.checkout_path(self.version.slug), + escape_command=False, ) return cmd_ret.output diff --git a/readthedocs/doc_builder/environments.py b/readthedocs/doc_builder/environments.py index cda73492dea..782a15f21f8 100644 --- a/readthedocs/doc_builder/environments.py +++ b/readthedocs/doc_builder/environments.py @@ -296,14 +296,20 @@ class DockerBuildCommand(BuildCommand): Build command to execute in docker container """ - def run(self): + def __init__(self, *args, escape_command=True, **kwargs): """ - Execute command in existing Docker container. + Override default to extend behavior. - :param cmd_input: input to pass to command in STDIN - :type cmd_input: str - :param combine_output: combine STDERR into STDOUT + :param escape_command: whether escape special chars the command before + executing it in the container. This should only be disabled on + trusted or internal commands. + :type escape_command: bool """ + self.escape_command = escape_command + super(DockerBuildCommand, self).__init__(*args, **kwargs) + + def run(self): + """Execute command in existing Docker container.""" log.info( "Running in container %s: '%s' [%s]", self.build_env.container_id, @@ -352,13 +358,15 @@ def run(self): def get_wrapped_command(self): """ - Escape special bash characters in command to wrap in shell. + Wrap command in a shell and optionally escape special bash characters. In order to set the current working path inside a docker container, we - need to wrap the command in a shell call manually. Some characters will - be interpreted as shell characters without escaping, such as: ``pip - install requests<0.8``. This escapes a good majority of those - characters. + need to wrap the command in a shell call manually. + + Some characters will be interpreted as shell characters without + escaping, such as: ``pip install requests<0.8``. When passing + ``escape_command=True`` in the init method this escapes a good majority + of those characters. """ bash_escape_re = re.compile( r"([\t\ \!\"\#\$\&\'\(\)\*\:\;\<\>\?\@" @@ -367,16 +375,18 @@ def get_wrapped_command(self): prefix = '' if self.bin_path: prefix += 'PATH={}:$PATH '.format(self.bin_path) + + command = ( + ' '.join([ + bash_escape_re.sub(r'\\\1', part) if self.escape_command else part + for part in self.command + ]) + ) return ( "/bin/sh -c 'cd {cwd} && {prefix}{cmd}'".format( cwd=self.cwd, prefix=prefix, - cmd=( - ' '.join([ - bash_escape_re.sub(r'\\\1', part) - for part in self.command - ]) - ), + cmd=command, ) )