def sh(self, args, stdin=b'', shell=False, env=None, check=True, watcher=None, short_args=None, stdout=None, stderr=None): """run a shell command""" start = time.time() env_ = os.environ.copy() env_.update(LC_ALL='C', LANG='C') if env: env_.update(env) kwargs = dict(self.stds, shell=shell) if stdout is not None: kwargs['stdout'] = stdout if stderr is not None: kwargs['stderr'] = stderr res = {'stdout': '', 'stderr': ''} try: p = subprocess.Popen(args, env=env_, **kwargs) except Exception: rc = res['rc'] = 1 exc = self.format_exception() else: self.current_cmd = short_args or args self.current_process = p if watcher is not None: self.current_process_watcher = safe_iterator(watcher(self, p)) else: self.current_process_watcher = safe_iterator() if stdin and not isinstance(stdin, bytes): stdin = stdin.encode('utf8') stdout, stderr = p.communicate(stdin) self.current_process = None utils._next(self.current_process_watcher) self.current_process_watcher = None if stdout is None: stdout = kwargs['stdout'] with open(stdout.name) as fd: try: stdout = fd.read() except OSError: stdout = '' if isinstance(stdout, bytes): stdout = stdout.decode('utf8') if stderr is None: stderr = kwargs['stderr'] with open(stderr.name) as fd: try: stderr = fd.read() except OSError: stderr = '' if isinstance(stderr, bytes): stderr = stderr.decode('utf8') rc = p.returncode res.update(rc=rc, stdout=stdout, stderr=stderr) exc = None t = time.time() - start self.remote_calls.append(dict(res, cmd=args, start=start, time=t, exc=exc)) if check: return self.check(res) return res
def safe_iterator(iterator=None): while True: if iterator is not None: try: utils._next(iterator) except Exception: logging.exception(iterator) yield
def on_alarm(self, *args): self.alarm_count += 1 ready = select.select([sys.stdin], [], [], .0)[0] if ready: try: data = utils.proto_loads_std(self.stdin) except ValueError: pass else: sig = data.get('signal') if sig is not None: self.on_sigint() if self.alarm_count % self.process_watcher_delay == 0: if self.current_process_watcher is not None: utils._next(self.current_process_watcher) signal.alarm(self.alarm_delay)
def on_sigint(self, *args): res = dict(rc=1, signal='SIGINT', current_process=None) if self.current_process is not None: # forward signal to current process if any p = self.current_process p.send_signal(signal.SIGINT) # let the watcher know that we no longer have a process if self.current_process_watcher is not None: self.current_process = None utils._next(self.current_process_watcher) p.wait() res['current_process'] = { 'cmd': self.current_cmd, 'pid': p.pid, 'rc': p.returncode} # clean exit self.exit(res)
def exit(self, res): # tel the watcher that the process is ended if self.current_process_watcher is not None: self.current_process = None utils._next(self.current_process_watcher) res.setdefault('rc', 0) res.setdefault('message_type', 'exit') res.setdefault('signal', None) res['meta'] = dict(remote_calls=self.remote_calls, remote_time=time.time() - self.remote_start) self.logfile.seek(0) logs = self.logfile.read() if logs.strip(): res['log'] = logs if 'diff' in res: res.setdefault('changed', bool(res['diff'])) utils.proto_dumps_std_threadsafe(res, sys.stdout) sys.exit(0)