def execute(cmd, process_input=None, check_exit_code=True, cwd=None, shell=False, env_overrides=None, stdout_fh=subprocess.PIPE, stderr_fh=subprocess.PIPE): """Helper method to execute a command through subprocess. :param cmd: Command passed to subprocess.Popen. :param process_input: Input send to opened process. :param check_exit_code: Specifies whether to check process return code. If return code is other then `0` - exception will be raised. :param cwd: The child's current directory will be changed to `cwd` before it is executed. :param shell: Specifies whether to use the shell as the program to execute. :param env_overrides: Process environment parameters to override. :param stdout_fh: Stdout file handler. :param stderr_fh: Stderr file handler. :returns: A tuple, (stdout, stderr) from the spawned process. :raises: :class:`exceptions.ProcessExecutionError` when process ends with other then `0` return code. """ # Ensure all string args (i.e. for those that send ints, etc.). cmd = map(str, cmd) # NOTE(skudriashev): If shell is True, it is recommended to pass args as a # string rather than as a sequence. str_cmd = subprocess.list2cmdline(cmd) if shell: cmd = str_cmd LOG.debug('Running shell cmd: %r' % cmd) else: LOG.debug('Running cmd: %r' % cmd) if process_input is not None: process_input = str(process_input) LOG.debug('Process input: %s' % process_input) if cwd: LOG.debug('Process working directory: %r' % cwd) # Override process environment in needed. process_env = None if env_overrides and len(env_overrides): process_env = env.get() for k, v in env_overrides.items(): process_env[k] = str(v) # Run command process. exec_kwargs = { 'stdin': subprocess.PIPE, 'stdout': stdout_fh, 'stderr': stderr_fh, 'close_fds': True, 'shell': shell, 'cwd': cwd, 'env': process_env, } result = ("", "") try: obj = subprocess.Popen(cmd, **exec_kwargs) result = obj.communicate(process_input) except OSError as e: raise excp.ProcessExecutionError(str_cmd, exec_kwargs=exec_kwargs, description="%s: [%s, %s]" % (e, e.errno, e.strerror)) else: rc = obj.returncode # Handle process exit code. stdout = result[0] or "" stderr = result[1] or "" if rc != 0 and check_exit_code: # Raise exception if return code is not `0`. e = excp.ProcessExecutionError(str_cmd, exec_kwargs=exec_kwargs, stdout=stdout, stderr=stderr, exit_code=rc, where_output="debug log") LOG.debug("Stdout: %s", e.stdout) LOG.debug("Stderr: %s", e.stderr) raise e return stdout, stderr
def execute(*cmd, **kwargs): process_input = kwargs.pop('process_input', None) check_exit_code = kwargs.pop('check_exit_code', [0]) cwd = kwargs.pop('cwd', None) env_overrides = kwargs.pop('env_overrides', None) close_stdin = kwargs.pop('close_stdin', False) ignore_exit_code = kwargs.pop('ignore_exit_code', False) if isinstance(check_exit_code, bool): ignore_exit_code = not check_exit_code check_exit_code = [0] elif isinstance(check_exit_code, int): check_exit_code = [check_exit_code] run_as_root = kwargs.pop('run_as_root', False) shell = kwargs.pop('shell', False) # Ensure all string args execute_cmd = [] for c in cmd: execute_cmd.append(str(c)) # From the docs it seems a shell command must be a string?? # TODO(harlowja) this might not really be needed? str_cmd = " ".join(execute_cmd) if shell: execute_cmd = str_cmd.strip() stdin_fh = subprocess.PIPE stdout_fh = subprocess.PIPE stderr_fh = subprocess.PIPE close_file_descriptors = True if 'stdout_fh' in kwargs.keys(): stdout_fh = kwargs.get('stdout_fh') if 'stdin_fh' in kwargs.keys(): stdin_fh = kwargs.get('stdin_fh') process_input = None if 'stderr_fh' in kwargs.keys(): stderr_fh = kwargs.get('stderr_fh') if not shell: LOG.debug('Running cmd: %r' % (execute_cmd)) else: LOG.debug('Running shell cmd: %r' % (execute_cmd)) if process_input is not None: LOG.debug('With stdin: %s' % (process_input)) if cwd: LOG.debug("In working directory: %r" % (cwd)) process_env = None if env_overrides and len(env_overrides): process_env = env.get() for (k, v) in env_overrides.items(): process_env[k] = str(v) else: process_env = env.get() # LOG.debug("With environment %s", process_env) demoter = None def demoter_functor(user_uid, user_gid): def doit(): os.setregid(user_gid, user_gid) os.setreuid(user_uid, user_uid) return doit if not run_as_root: (user_uid, user_gid) = get_suids() if user_uid is None or user_gid is None: pass else: demoter = demoter_functor(user_uid=user_uid, user_gid=user_gid) rc = None result = None with Rooted(run_as_root): if is_dry_run(): rc = 0 result = ('', '') else: try: obj = subprocess.Popen(execute_cmd, stdin=stdin_fh, stdout=stdout_fh, stderr=stderr_fh, close_fds=close_file_descriptors, cwd=cwd, shell=shell, preexec_fn=demoter, env=process_env) if process_input is not None: result = obj.communicate(str(process_input)) else: result = obj.communicate() except OSError as e: raise excp.ProcessExecutionError(description="%s: [%s, %s]" % (e, e.errno, e.strerror), cmd=str_cmd) if (stdin_fh != subprocess.PIPE and obj.stdin and close_stdin): obj.stdin.close() rc = obj.returncode if not result: result = ("", "") (stdout, stderr) = result if stdout is None: stdout = '' if stderr is None: stderr = '' if (not ignore_exit_code) and (rc not in check_exit_code): raise excp.ProcessExecutionError(exit_code=rc, stdout=stdout, stderr=stderr, cmd=str_cmd) else: # Log it anyway if rc not in check_exit_code: LOG.debug( "A failure may of just happened when running command %r [%s] (%s, %s)", str_cmd, rc, stdout, stderr) # See if a requested storage place was given for stderr/stdout trace_writer = kwargs.get('trace_writer') stdout_fn = kwargs.get('stdout_fn') if stdout_fn: write_file(stdout_fn, stdout) if trace_writer: trace_writer.file_touched(stdout_fn) stderr_fn = kwargs.get('stderr_fn') if stderr_fn: write_file(stderr_fn, stderr) if trace_writer: trace_writer.file_touched(stderr_fn) return (stdout, stderr)
def execute(cmd, process_input=None, check_exit_code=True, cwd=None, shell=False, env_overrides=None, stdout_fh=None, stderr_fh=None, stdout_fn=None, stderr_fn=None, trace_writer=None): """Helper method to execute command. :param cmd: Passed to subprocess.Popen :param process_input: Send to opened process :param check_exit_code: Single `bool`, `int`, or `list` of allowed exit codes. By default, only 0 exit code is allowed. Raise :class:`exceptions.ProcessExecutionError` unless program exits with one of these code :returns: a tuple, (stdout, stderr) from the spawned process, or None if the command fails """ if isinstance(check_exit_code, (bool)): ignore_exit_code = not check_exit_code check_exit_code = [0] elif isinstance(check_exit_code, (int)): check_exit_code = [check_exit_code] # Ensure all string args (ie for those that send ints and such...) execute_cmd = [str(c) for c in cmd] # From the docs it seems a shell command must be a string?? # TODO(harlowja) this might not really be needed? str_cmd = " ".join(shellquote(word) for word in cmd) if shell: execute_cmd = str_cmd if not shell: LOG.debug('Running cmd: %r' % (execute_cmd)) else: LOG.debug('Running shell cmd: %r' % (execute_cmd)) if process_input is not None: LOG.debug('With stdin: %s' % (process_input)) if cwd: LOG.debug("In working directory: %r" % (cwd)) if stdout_fn is not None and stdout_fh is not None: LOG.warn("Stdout file handles and stdout file names can not be used simultaneously!") if stderr_fn is not None and stderr_fh is not None: LOG.warn("Stderr file handles and stderr file names can not be used simultaneously!") process_env = None if env_overrides and len(env_overrides): process_env = env.get() for (k, v) in env_overrides.items(): process_env[k] = str(v) rc = None result = ("", "") if is_dry_run(): rc = 0 else: stdin_fh = subprocess.PIPE if stdout_fn or (stdout_fh is None): stdout_fh = subprocess.PIPE if stderr_fn or (stderr_fh is None): stderr_fh = subprocess.PIPE try: obj = subprocess.Popen(execute_cmd, stdin=stdin_fh, stdout=stdout_fh, stderr=stderr_fh, close_fds=True, cwd=cwd, shell=shell, env=process_env) if process_input is not None: result = obj.communicate(str(process_input)) else: result = obj.communicate() except OSError as e: raise excp.ProcessExecutionError(description="%s: [%s, %s]" % (e, e.errno, e.strerror), cmd=str_cmd) rc = obj.returncode if stdout_fh != subprocess.PIPE: stdout = "<redirected to %s>" % (stdout_fn or stdout_fh) else: stdout = result[0] or "" if stderr_fh != subprocess.PIPE: stderr = "<redirected to %s>" % (stderr_fn or stderr_fh) else: stderr = result[1] or "" if (not ignore_exit_code) and (rc not in check_exit_code): raise excp.ProcessExecutionError(exit_code=rc, stdout=stdout, stderr=stderr, cmd=str_cmd) else: # Log it anyway if rc not in check_exit_code: LOG.debug("A failure may of just happened when running command %r [%s] (%s, %s)", str_cmd, rc, stdout, stderr) # See if a requested storage place was given for stderr/stdout for name, handle in ((stdout_fn, stdout), (stderr_fn, stderr)): if name: write_file(name, handle) if trace_writer: trace_writer.file_touched(name) return (stdout, stderr)
def test_exit_code_not_valid(self): err = exc.ProcessExecutionError(self.cmd, exit_code='code') self.assertExceptionMessage(err, self.cmd, exit_code='-') err = exc.ProcessExecutionError(self.cmd, exit_code=0.0) self.assertExceptionMessage(err, self.cmd, exit_code='-')
def test_description(self): description = 'custom description' err = exc.ProcessExecutionError(self.cmd, description=description) self.assertExceptionMessage(err, self.cmd, description=description)
def test_exit_code_long(self): err = exc.ProcessExecutionError(self.cmd, exit_code=0L) self.assertExceptionMessage(err, self.cmd, exit_code=0L)
def test_stderr_none(self): err = exc.ProcessExecutionError(self.cmd, stderr=None) self.assertExceptionMessage(err, cmd=self.cmd, stderr=None)
def test_stderr(self): err = exc.ProcessExecutionError(self.cmd, stderr=self.stderr) self.assertExceptionMessage(err, cmd=self.cmd, stderr=self.stderr) self.assertEqual(self.stderr, err.stderr)
def test_stdout_empty(self): err = exc.ProcessExecutionError(self.cmd, stdout='') self.assertExceptionMessage(err, cmd=self.cmd, stdout='') self.assertEqual('', err.stdout)
def test_default(self): err = exc.ProcessExecutionError(self.cmd) self.assertExceptionMessage(err, cmd=self.cmd)