Source code for runway.cfngin.hooks.command

"""Command hook."""
# pylint: disable=unused-argument
import logging
import os
from subprocess import PIPE, Popen

from ..exceptions import ImproperlyConfigured

LOGGER = logging.getLogger(__name__)


def _devnull():
    return open(os.devnull, "wb")


[docs]def run_command( provider, context, command, capture=False, interactive=False, ignore_status=False, quiet=False, stdin=None, env=None, **kwargs ): """Run a custom command as a hook. Args: provider (:class:`runway.cfngin.providers.base.BaseProvider`): Provider instance. (passed in by CFNgin) context (:class:`runway.cfngin.context.Context`): Context instance. (passed in by CFNgin) Keyword Args: command (Union[str, List[str]]): Command(s) to run. capture (bool): If enabled, capture the command's stdout and stderr, and return them in the hook result. (*default:* ``False``) interactive (bool): If enabled, allow the command to interact with stdin. Otherwise, stdin will be set to the null device. (*default:* ``False``) ignore_status (bool): Don't fail the hook if the command returns a non-zero status. (*default:* ``False``) quiet (bool): Redirect the command's stdout and stderr to the null device, silencing all output. Should not be enabled if ``capture`` is also enabled. (*default:* ``False``) stdin (Optional[str]): String to send to the stdin of the command. Implicitly disables ``interactive``. env (Optional[Dict[str, str]]): Dictionary of environment variable overrides for the command context. Will be merged with the current environment. **kwargs (Any): Any other arguments will be forwarded to the ``subprocess.Popen`` function. Interesting ones include: ``cwd`` and ``shell``. Examples: .. code-block:: yaml pre_build: command_copy_environment: path: runway.cfngin.hooks.command.run_command required: true enabled: true data_key: copy_env args: command: ['cp', 'environment.template', 'environment'] command_git_rev_parse: path: runway.cfngin.hooks.command.run_command required: true enabled: true data_key: get_git_commit args: command: ['git', 'rev-parse', 'HEAD'] cwd: ./my-git-repo capture: true command_npm_install: path: runway.cfngin.hooks.command.run_command args: command: '`cd $PROJECT_DIR/project; npm install`' env: PROJECT_DIR: ./my-project shell: true """ if quiet and capture: raise ImproperlyConfigured( __name__ + ".run_command", "Cannot enable `quiet` and `capture` options simultaneously", ) if quiet: out_err_type = _devnull() elif capture: out_err_type = PIPE else: out_err_type = None if interactive: in_type = None elif stdin: in_type = PIPE else: in_type = _devnull() if env: full_env = os.environ.copy() full_env.update(env) env = full_env LOGGER.info("running command: %s", command) proc = Popen( command, stdin=in_type, stdout=out_err_type, stderr=out_err_type, env=env, **kwargs ) try: out, err = proc.communicate(stdin) status = proc.wait() if status == 0 or ignore_status: return {"returncode": proc.returncode, "stdout": out, "stderr": err} # Don't print the command line again if we already did earlier if LOGGER.isEnabledFor(logging.INFO): LOGGER.warning("command failed with returncode %d", status) else: LOGGER.warning("command failed with returncode %d: %s", status, command) return None finally: if proc.returncode is None: proc.kill()