Source code for wiz.spawn

# :coding: utf-8

from __future__ import absolute_import
import os
import sys
import select
import subprocess
import tempfile
import termios
import tty
import pty
import signal
import logging

import wiz.utility
import wiz.symbol


[docs]def shell(environment, command=None): """Spawn a sub-shell with an *environment* mapping. :param environment: Environment mapping to spawn the shell with. :param command: Mapping of command aliases which should be available in the shell. .. note:: Default shell is :term:`Bash`. """ logger = logging.getLogger(__name__ + ".shell") if command is None: command = {} # TODO: Improve default shell to make it cross platform... executable = "/bin/bash" logger.info("Spawn shell: {}".format(executable)) # save original tty setting then set it to raw mode old_tty = termios.tcgetattr(sys.stdin) tty.setraw(sys.stdin.fileno()) # open pseudo-terminal to interact with subprocess master_fd, slave_fd = pty.openpty() # Create temporary rc file for shell aliases for commands rcfile = tempfile.NamedTemporaryFile() for alias, value in command.items(): line = "alias {0}='{1}'\n".format(alias, value) rcfile.write(line.encode("utf-8")) rcfile.seek(0) rcfile.read() if os.path.exists(rcfile.name): executable = [executable, "--rcfile", rcfile.name] # Run in a new process group to enable job control process = subprocess.Popen( executable, preexec_fn=os.setsid, stdin=slave_fd, stdout=slave_fd, stderr=slave_fd, universal_newlines=True, env=environment ) # Register the cleanup function as handler for SIGINT and SIGTERM. signal.signal(signal.SIGINT, _cleanup) signal.signal(signal.SIGTERM, _cleanup) while process.poll() is None: read_list, write_list, _ = select.select([sys.stdin, master_fd], [], []) if sys.stdin in read_list: message = os.read(sys.stdin.fileno(), 10240) os.write(master_fd, message) elif master_fd in read_list: message = os.read(master_fd, 10240) if message: os.write(sys.stdout.fileno(), message) # Restore tty settings back termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_tty) # Remove temporary rc file for shell aliases rcfile.close()
[docs]def execute(elements, environment): """Run command *elements* within a specific *environment*. :param elements: List of strings constituting the command line to execute (e.g. ["app_exe", "--option", "value"]) :param environment: Environment mapping to execute command with. :return: :attr:`~subprocess.Popen.returncode` attribute. """ logger = logging.getLogger(__name__ + ".execute") logger.info( "Start command: {}".format(wiz.utility.combine_command(elements)) ) # Substitute environment variables from command line elements. elements = [ wiz.environ.substitute(element, environment) for element in elements ] # Register the cleanup function as handler for SIGINT and SIGTERM. signal.signal(signal.SIGINT, _cleanup) signal.signal(signal.SIGTERM, _cleanup) try: return subprocess.call(elements, env=environment) except OSError: logger.error( "Executable can not be found within resolved " "environment [{}]".format(elements[0]) ) # A negative value -N indicates that the child was terminated by signal N # on POSIX architecture. return -1
def _cleanup(signum, frame): """Exit from python if a process is terminated or interrupted.""" sys.exit(0)