def input(label, is_password=False, answers=None, default=None): """ Input loop. (like raw_input, but nice colored.) 'answers' can be either None or a list of the accepted answers. """ def print_question(): answers_str = (' [%s]' % (','.join(answers)) if answers else '') default_str = (' (default=%s)' % default if default else '') sys.stdout.write( colored(' %s%s%s: ' % (label, answers_str, default_str), 'cyan')) sys.stdout.flush() def read_answer(): value = '' print_question() while True: c = sys.stdin.read(1) # Enter pressed if c in ('\r', '\n') and (value or default): sys.stdout.write('\r\n') break # Backspace pressed elif c == '\x7f' and value: sys.stdout.write('\b \b') value = value[:-1] # Valid character elif ord(c) in range(32, 127): sys.stdout.write( colored('*' if is_password else c, attrs=['bold'])) value += c elif c == '\x03': # Ctrl-C raise NoInput sys.stdout.flush() # Return result if not value and default: return default else: return value with std.raw_mode(sys.stdin): while True: value = read_answer() # Return if valid anwer if not answers or value in answers: return value # Otherwise, ask again. else: sys.stdout.write('Invalid answer.\r\n') sys.stdout.flush()
def input(label, is_password=False, answers=None, default=None): """ Input loop. (like raw_input, but nice colored.) 'answers' can be either None or a list of the accepted answers. """ def print_question(): answers_str = " [%s]" % (",".join(answers)) if answers else "" default_str = " (default=%s)" % default if default else "" sys.stdout.write(colored(" %s%s%s: " % (label, answers_str, default_str), "cyan")) sys.stdout.flush() def read_answer(): value = "" print_question() while True: c = sys.stdin.read(1) # Enter pressed if c in ("\r", "\n") and (value or default): sys.stdout.write("\r\n") break # Backspace pressed elif c == "\x7f" and value: sys.stdout.write("\b \b") value = value[:-1] # Valid character elif ord(c) in range(32, 127): sys.stdout.write(colored("*" if is_password else c, attrs=["bold"])) value += c elif c == "\x03": # Ctrl-C raise NoInput sys.stdout.flush() # Return result if not value and default: return default else: return value with std.raw_mode(sys.stdin): while True: value = read_answer() # Return if valid anwer if not answers or value in answers: return value # Otherwise, ask again. else: sys.stdout.write("Invalid answer.\r\n") sys.stdout.flush()
def cmdloop(self): try: while True: with raw_mode(self.stdin): result = self.read().strip() if result: self.lines_history.append(result) self.history_position = 0 self.handle([ p for p in result.split(' ') if p ]) except ExitCLILoop, e: print # Print newline return e.result
def lesspipe(self, line_iterator): """ Paginator for output. This will print one page at a time. When the user presses a key, the next page is printed. ``Ctrl-c`` or ``q`` will quit the paginator. :param line_iterator: A generator function that yields lines (without trailing newline) """ stdin = self._pty.stdin stdout = self._pty.stdout height = self._pty.get_size()[0] - 1 with std.raw_mode(stdin): lines = 0 for l in line_iterator: # Print next line stdout.write(l) stdout.write('\r\n') lines += 1 # When we are at the bottom of the terminal if lines == height: # Wait for the user to press enter. stdout.write(colored(' Press enter to continue...', 'cyan')) stdout.flush() try: c = stdin.read(1) # Control-C or 'q' will quit pager. if c in ('\x03', 'q'): stdout.write('\r\n') stdout.flush() return except IOError: # Interupted system call. pass # Move backwards and erase until the end of the line. stdout.write('\x1b[40D\x1b[K') lines = 0 stdout.flush()
def cmdloop(self): try: while True: # Set handler for resize terminal events. self.pty.set_ssh_channel_size = lambda: self.print_command() # Read command with raw_mode(self.stdin): result = self.read().strip() self.pty.set_ssh_channel_size = None # Handle result if result: self.lines_history.append(result) self.history_position = 0 self.handle([p for p in result.split(' ') if p]) except ExitCLILoop, e: print # Print newline return e.result
def cmdloop(self): try: while True: # Set handler for resize terminal events. self.pty.set_ssh_channel_size = lambda: self.print_command() # Read command with raw_mode(self.stdin): result = self.read().strip() self.pty.set_ssh_channel_size = None # Handle result if result: self.lines_history.append(result) self.history_position = 0 self.handle([ p for p in result.split(' ') if p ]) except ExitCLILoop, e: print # Print newline return e.result
def lesspipe(line_iterator, pty): """ Paginator for output. """ height = pty.get_size()[0] - 1 with std.raw_mode(sys.stdin): lines = 0 for l in line_iterator: # Print next line sys.stdout.write(l) sys.stdout.write('\r\n') lines += 1 # When we are at the bottom of the terminal if lines == height: # Wait for the user to press enter. sys.stdout.write( colored(' Press enter to continue...', 'cyan')) sys.stdout.flush() try: c = sys.stdin.read(1) # Control-C or 'q' will quit pager. if c in ('\x03', 'q'): sys.stdout.write('\r\n') sys.stdout.flush() return except IOError, e: # Interupted system call. pass # Move backwards and erase until the end of the line. sys.stdout.write('\x1b[40D\x1b[K') lines = 0 sys.stdout.flush()
def lesspipe(line_iterator, pty): """ Paginator for output. """ height = pty.get_size()[0] - 1 with std.raw_mode(sys.stdin): lines = 0 for l in line_iterator: # Print next line sys.stdout.write(l) sys.stdout.write("\r\n") lines += 1 # When we are at the bottom of the terminal if lines == height: # Wait for the user to press enter. sys.stdout.write(colored(" Press enter to continue...", "cyan")) sys.stdout.flush() try: c = sys.stdin.read(1) # Control-C or 'q' will quit pager. if c in ("\x03", "q"): sys.stdout.write("\r\n") sys.stdout.flush() return except IOError, e: # Interupted system call. pass # Move backwards and erase until the end of the line. sys.stdout.write("\x1b[40D\x1b[K") lines = 0 sys.stdout.flush()
def __call__(self, context): # Real stdin/out of the current logging session stdin = context.cli.stdin stdout = context.cli.stdout # Use a pseudo terminal to pass the logging IO from other deployment # sessions to this one. master, slave = os.openpty() slave_stdout = os.fdopen(slave, 'w', 0) master_stdin = os.fdopen(master, 'r', 0) fdesc.setNonBlocking(master_stdin) def now(): return datetime.datetime.now().strftime('%H:%I:%S') # Create monitor handler class M(Monitor): def __init__(self): self.line = 0 # Keep line numbers slave_stdout.write("Press 'q' or Ctrl-c to exit the monitor\n") slave_stdout.write(colored('%-12s' % 'Username', 'cyan')) slave_stdout.write(colored('%-9s' % 'Time', 'cyan')) slave_stdout.write(colored('%-17s' % 'Host', 'cyan')) slave_stdout.write(colored('%s' % 'Command', 'cyan')) slave_stdout.write('\n') slave_stdout.flush() def _print_username(self, username): slave_stdout.write(colored('%-12s' % username, 'magenta')) slave_stdout.write(colored('%-9s' % now())) def _print_user_and_event(self, username, event): self._print_username(username) slave_stdout.write(' '*17 + event) slave_stdout.flush() def _print_status(self, line, succeeded): slave_stdout.write('\0337') # ESC 7: Save cursor position slave_stdout.write('\033[%iA' % (self.line-line)) # Move cursor up slave_stdout.write('\033[1000C') # Move cursor to end of line slave_stdout.write('\033[8D') # Move cursor 8 chars back if succeeded: slave_stdout.write(colored('succeeded', 'green')) else: slave_stdout.write(colored('failed', 'red')) slave_stdout.write('\0338') # ESC 8: Restore cursor position def log_run(self, session, run): if not run.sandboxing: line = self.line self._print_username(session.username) slave_stdout.write(colored('%-17s' % run.host.slug, 'cyan')) slave_stdout.write(colored('%s' % (' (sudo) ' if run.use_sudo else ''), 'red')) slave_stdout.write(colored(run.command, 'green')) slave_stdout.write('\n') slave_stdout.flush() self.line += 1 # TODO: I guess these writes should be atomic (group in # one statement), because they can come from different # threads. def callback(): # TODO: Make more generic. We don't want to write to # this logger when it has been closed. It may be # possible that the logger has been disconnected and # removed from the globals loggers list, but the # callback still exists. if not slave_stdout.closed: self._print_status(line, run.succeeded) return callback else: return lambda:None def log_file_opened(self, session, file_entry): if not file_entry.sandboxing: line = self.line self._print_username(session.username) slave_stdout.write(colored('%-17s' % file_entry.host.slug, 'cyan')) slave_stdout.write(colored('%s' % (' (sudo) ' if file_entry.use_sudo else ''), 'red')) if file_entry.entry_type == Actions.Get: slave_stdout.write('Downloading: ') slave_stdout.write(colored(file_entry.remote_path, 'green')) elif file_entry.entry_type == Actions.Put: slave_stdout.write('Uploading: ') slave_stdout.write(colored(file_entry.remote_path, 'green')) elif file_entry.entry_type == Actions.Open: slave_stdout.write('Opening file: ') slave_stdout.write(colored(file_entry.remote_path, 'green')) slave_stdout.write(' mode=') slave_stdout.write(colored(file_entry.mode, 'green')) slave_stdout.write('\n') slave_stdout.flush() self.line += 1 def callback(): if not slave_stdout.closed: self._print_status(line, file_entry.succeeded) return callback else: return lambda:None def action_started(self, session, action_entry): line = self.line if action_entry.sandboxing: self._print_user_and_event(session.username, 'Calling action (in sandbox): %s\n' % colored(action_entry.command, 'green')) else: self._print_user_and_event(session.username, 'Calling action: %s\n' % colored(action_entry.command, 'green')) class Callbacks(object): def succeeded(s, result): if not slave_stdout.closed: self._print_status(line, True) def failed(s, exception): if not slave_stdout.closed: self._print_status(line, False) self.line += 1 return Callbacks() def session_created(self, session): self.line += 1 self._print_user_and_event(session.username, 'Started session host=%s id=%s\n' % (session.host, session.id)) def session_closed(self, session): self.line += 1 self._print_user_and_event(session.username, 'Closed session host=%s id=%s\n' % (session.host, session.id)) monitor = M() monitors.append(monitor) with std.raw_mode(stdin): while True: r, w, e = select.select([master_stdin, stdin], [], []) # Receive stream from monitor if master_stdin in r: stdout.write(master_stdin.read(4096)) if stdin in r: char = stdin.read(1) # Leave monitor when 'q' or Ctrl-C has been pressed. if char in ('q', '\x03'): # Cleanup monitors.remove(monitor) slave_stdout.close() master_stdin.close() return
def _posix_shell(self, pty, chan, raw=True, log_entry=None, initial_input=None): """ Create a loop which redirects sys.stdin/stdout into this channel. The loop ends when channel.recv() returns 0. Code inspired by the Paramiko interactive demo. """ result = [] password_sent = False # Make stdin non-blocking. (The select call will already # block for us, we want sys.stdin.read() to read as many # bytes as possible without blocking.) fdesc.setNonBlocking(pty.stdin) # Set terminal in raw mode if raw: context = raw_mode(pty.stdin) else: context = contextlib.nested() with context: try: chan.settimeout(0.0) # When initial input has been given, send this first if initial_input: time.sleep(0.2) # Wait a very short while for the channel to be initialized, before sending. chan.send(initial_input) reading_from_stdin = True # Read/write loop while True: # Don't wait for any input when an exit status code has been # set already. if chan.status_event.isSet(): break; if reading_from_stdin: r, w, e = select([pty.stdin, chan], [], []) else: r, w, e = select([chan], [], []) # Receive stream if chan in r: try: x = chan.recv(1024) # Received length 0 -> end of stream if len(x) == 0: break # Log received characters log_entry.log_io(x) # Write received characters to stdout and flush while True: try: pty.stdout.write(x) break except IOError, e: # Sometimes, when we have a lot of output, we get here: # IOError: [Errno 11] Resource temporarily unavailable # Just waiting a little, and retrying seems to work. # See also: deployer.run.socket_client for a similar issue. time.sleep(0.2) pty.stdout.flush() # Also remember received output. # We want to return what's written to stdout. result.append(x) # Do we need to send the sudo password? (It's when the # magic prompt has been detected in the stream) Note # that we only monitor the last part of 'result', it's # a bit fuzzy, but works. if not password_sent and self.magic_sudo_prompt in ''.join(result[-32:]): chan.send(self.password) chan.send('\n') password_sent = True except socket.timeout: pass # Send stream (one by one character) if pty.stdin in r: try: x = pty.stdin.read(1024) # We receive \n from stdin, but \r is required to # send. (Until now, the only place where the # difference became clear is in redis-cli, which # only accepts \r as confirmation.) x = x.replace('\n', '\r') except IOError, e: # What to do with IOError exceptions? # (we see what happens in the next select-call.) continue # Received length 0 # There's no more at stdin to read. if len(x) == 0: # However, we should go on processing the input # from the remote end, until the process finishes # there (because it was done or processed Ctrl-C or # Ctrl-D/EOF.) # # The end of the input stream happens when we are # using StringIO at the client side, and we're not # attached to a real pseudo terminal. (For # unit-testing, or background commands.) reading_from_stdin = False continue # Write to channel chan.send(x) # Not sure about this. Sometimes, when pasting large data # in the command line, the many concecutive read or write # commands will make Paramiko hang somehow... (This was # the case, when still using a blocking pty.stdin.read(1) # instead of a non-blocking readmany. time.sleep(0.01)
def input(self, label, is_password=False, answers=None, default=None): """ Ask for plain text input. (Similar to raw_input.) :param is_password: Show stars instead of the actual user input. :type is_password: bool :param answers: A list of the accepted answers or None. :param default: Default answer. """ def print_question(): answers_str = (' [%s]' % (','.join(answers)) if answers else '') default_str = (' (default=%s)' % default if default is not None else '') sys.stdout.write(colored(' %s%s%s: ' % (label, answers_str, default_str), 'cyan')) sys.stdout.flush() def read_answer(): value = '' print_question() while True: c = sys.stdin.read(1) # Enter pressed if c in ('\r', '\n') and (value or default): sys.stdout.write('\r\n') break # Backspace pressed elif c == '\x7f' and value: sys.stdout.write('\b \b') value = value[:-1] # Valid character elif ord(c) in range(32, 127): sys.stdout.write(colored('*' if is_password else c, attrs=['bold'])) value += c elif c == '\x03': # Ctrl-C raise NoInput sys.stdout.flush() # Return result if not value and default is not None: return default else: return value with std.raw_mode(sys.stdin): while True: if self._pty.interactive: value = read_answer() elif default is not None: print_question() sys.stdout.write('[non interactive] %r\r\n' % default) sys.stdout.flush() value = default else: # XXX: Asking for input in non-interactive session value = read_answer() # Return if valid anwer if not answers or value in answers: return value # Otherwise, ask again. else: sys.stdout.write('Invalid answer.\r\n') sys.stdout.flush()
def __call__(self, context): # Real stdin/out of the current logging session stdin = context.cli.stdin stdout = context.cli.stdout # Use a pseudo terminal to pass the logging IO from other deployment # sessions to this one. master, slave = os.openpty() slave_stdout = os.fdopen(slave, 'w', 0) master_stdin = os.fdopen(master, 'r', 0) fdesc.setNonBlocking(master_stdin) def now(): return datetime.datetime.now().strftime('%H:%I:%S') # Create monitor handler class M(Monitor): def __init__(self): self.line = 0 # Keep line numbers slave_stdout.write("Press 'q' or Ctrl-c to exit the monitor\n") slave_stdout.write(colored('%-12s' % 'Username', 'cyan')) slave_stdout.write(colored('%-9s' % 'Time', 'cyan')) slave_stdout.write(colored('%-17s' % 'Host', 'cyan')) slave_stdout.write(colored('%s' % 'Command', 'cyan')) slave_stdout.write('\n') slave_stdout.flush() def _print_username(self, username): slave_stdout.write(colored('%-12s' % username, 'magenta')) slave_stdout.write(colored('%-9s' % now())) def _print_user_and_event(self, username, event): self._print_username(username) slave_stdout.write(' ' * 17 + event) slave_stdout.flush() def _print_status(self, line, succeeded): slave_stdout.write('\0337') # ESC 7: Save cursor position slave_stdout.write('\033[%iA' % (self.line - line)) # Move cursor up slave_stdout.write('\033[1000C') # Move cursor to end of line slave_stdout.write('\033[8D') # Move cursor 8 chars back if succeeded: slave_stdout.write(colored('succeeded', 'green')) else: slave_stdout.write(colored('failed', 'red')) slave_stdout.write('\0338') # ESC 8: Restore cursor position def log_run(self, session, run): if not run.sandboxing: line = self.line self._print_username(session.username) slave_stdout.write(colored('%-17s' % run.host.slug, 'cyan')) slave_stdout.write( colored('%s' % (' (sudo) ' if run.use_sudo else ''), 'red')) slave_stdout.write(colored(run.command, 'green')) slave_stdout.write('\n') slave_stdout.flush() self.line += 1 # TODO: I guess these writes should be atomic (group in # one statement), because they can come from different # threads. def callback(): # TODO: Make more generic. We don't want to write to # this logger when it has been closed. It may be # possible that the logger has been disconnected and # removed from the globals loggers list, but the # callback still exists. if not slave_stdout.closed: self._print_status(line, run.succeeded) return callback else: return lambda: None def log_file_opened(self, session, file_entry): if not file_entry.sandboxing: line = self.line self._print_username(session.username) slave_stdout.write( colored('%-17s' % file_entry.host.slug, 'cyan')) slave_stdout.write( colored( '%s' % (' (sudo) ' if file_entry.use_sudo else ''), 'red')) if file_entry.entry_type == Actions.Get: slave_stdout.write('Downloading: ') slave_stdout.write( colored(file_entry.remote_path, 'green')) elif file_entry.entry_type == Actions.Put: slave_stdout.write('Uploading: ') slave_stdout.write( colored(file_entry.remote_path, 'green')) elif file_entry.entry_type == Actions.Open: slave_stdout.write('Opening file: ') slave_stdout.write( colored(file_entry.remote_path, 'green')) slave_stdout.write(' mode=') slave_stdout.write(colored(file_entry.mode, 'green')) slave_stdout.write('\n') slave_stdout.flush() self.line += 1 def callback(): if not slave_stdout.closed: self._print_status(line, file_entry.succeeded) return callback else: return lambda: None def action_started(self, session, action_entry): line = self.line if action_entry.sandboxing: self._print_user_and_event( session.username, 'Calling action (in sandbox): %s\n' % colored(action_entry.command, 'green')) else: self._print_user_and_event( session.username, 'Calling action: %s\n' % colored(action_entry.command, 'green')) class Callbacks(object): def succeeded(s, result): if not slave_stdout.closed: self._print_status(line, True) def failed(s, exception): if not slave_stdout.closed: self._print_status(line, False) self.line += 1 return Callbacks() def session_created(self, session): self.line += 1 self._print_user_and_event( session.username, 'Started session host=%s id=%s\n' % (session.host, session.id)) def session_closed(self, session): self.line += 1 self._print_user_and_event( session.username, 'Closed session host=%s id=%s\n' % (session.host, session.id)) monitor = M() monitors.append(monitor) with std.raw_mode(stdin): while True: r, w, e = select.select([master_stdin, stdin], [], []) # Receive stream from monitor if master_stdin in r: stdout.write(master_stdin.read(4096)) if stdin in r: char = stdin.read(1) # Leave monitor when 'q' or Ctrl-C has been pressed. if char in ('q', '\x03'): # Cleanup monitors.remove(monitor) slave_stdout.close() master_stdin.close() return
def input(self, label, is_password=False, answers=None, default=None): """ Ask for plain text input. (Similar to raw_input.) :param is_password: Show stars instead of the actual user input. :type is_password: bool :param answers: A list of the accepted answers or None. :param default: Default answer. """ stdin = self._pty.stdin stdout = self._pty.stdout def print_question(): answers_str = (' [%s]' % (','.join(answers)) if answers else '') default_str = (' (default=%s)' % default if default is not None else '') stdout.write(colored(' %s%s%s: ' % (label, answers_str, default_str), 'cyan')) stdout.flush() def read_answer(): value = '' print_question() while True: c = stdin.read(1) # Enter pressed if c in ('\r', '\n') and (value or default): stdout.write('\r\n') break # Backspace pressed elif c == '\x7f' and value: stdout.write('\b \b') value = value[:-1] # Valid character elif ord(c) in range(32, 127): stdout.write(colored('*' if is_password else c, attrs=['bold'])) value += c elif c == '\x03': # Ctrl-C raise NoInput stdout.flush() # Return result if not value and default is not None: return default else: return value with std.raw_mode(stdin): while True: if self._pty.interactive: value = read_answer() elif default is not None: print_question() stdout.write('[non interactive] %r\r\n' % default) stdout.flush() value = default else: # XXX: Asking for input in non-interactive session value = read_answer() # Return if valid anwer if not answers or value in answers: return value # Otherwise, ask again. else: stdout.write('Invalid answer.\r\n') stdout.flush()
def _posix_shell(self, chan, raw=True, initial_input=None): """ Create a loop which redirects sys.stdin/stdout into this channel. The loop ends when channel.recv() returns 0. Code inspired by the Paramiko interactive demo. """ result = [] password_sent = False # Set terminal in raw mode if raw: context = raw_mode(self.pty.stdin) else: context = contextlib.nested() assert self.pty.set_ssh_channel_size with context: # Make channel non blocking. chan.settimeout(0.0) # When initial input has been given, send this first if initial_input: time.sleep( 0.2 ) # Wait a very short while for the channel to be initialized, before sending. chan.send(initial_input) reading_from_stdin = True # Read/write loop while True: # Don't wait for any input when an exit status code has been # set already. (But still wait for the output to finish.) if chan.status_event.isSet(): reading_from_stdin = False # When the channel is closed, and there's nothing to read # anymore. We can return what we got from Paramiko. (Not # sure why this happens. Most of the time, select() still # returns and chan.recv() returns an empty string, but when # read_ready is False, select() doesn't return anymore.) if chan.closed and not chan.in_buffer.read_ready(): break channels = [self.pty.stdin, chan ] if reading_from_stdin else [chan] r, w, e = select(channels, [], [], 1) # Note the select-timeout. That is required in order to # check for the status_event every second. # Receive stream if chan in r: try: x = chan.recv(1024) # Received length 0 -> end of stream if len(x) == 0: break # Write received characters to stdout and flush while True: try: self.pty.stdout.write(x) break except IOError as e: # Sometimes, when we have a lot of output, we get here: # IOError: [Errno 11] Resource temporarily unavailable # Just waiting a little, and retrying seems to work. # See also: deployer.run.socket_client for a similar issue. time.sleep(0.2) self.pty.stdout.flush() # Also remember received output. # We want to return what's written to stdout. result.append(x) # Do we need to send the sudo password? (It's when the # magic prompt has been detected in the stream) Note # that we only monitor the last part of 'result', it's # a bit fuzzy, but works. if not password_sent and self.magic_sudo_prompt in ''.join( result[-32:]): chan.send(self.password) chan.send('\n') password_sent = True except socket.timeout: pass # Send stream (one by one character) # (use 'elif', read stdin only when there is no more output to be received.) elif self.pty.stdin in r: try: # Make stdin non-blocking. (The select call already # blocked for us, we want sys.stdin.read() to read # as many bytes as possible without blocking.) try: fdesc.setNonBlocking(self.pty.stdin) x = self.pty.stdin.read(1024) finally: # Set stdin blocking again # (Writing works better in blocking mode. # Especially OS X seems to be very sensitive if we # write lange amounts [>1000 bytes] nonblocking to # stdout. That causes a lot of IOErrors.) fdesc.setBlocking(self.pty.stdin) # We receive \n from stdin, but \r is required to # send. (Until now, the only place where the # difference became clear is in redis-cli, which # only accepts \r as confirmation.) x = x.replace('\n', '\r') except IOError as e: # What to do with IOError exceptions? # (we see what happens in the next select-call.) continue # Received length 0 # There's no more at stdin to read. if len(x) == 0: # However, we should go on processing the input # from the remote end, until the process finishes # there (because it was done or processed Ctrl-C or # Ctrl-D/EOF.) # # The end of the input stream happens when we are # using StringIO at the client side, and we're not # attached to a real pseudo terminal. (For # unit-testing, or background commands.) reading_from_stdin = False continue # Write to channel chan.send(x) # Not sure about this. Sometimes, when pasting large data # in the command line, the many concecutive read or write # commands will make Paramiko hang somehow... (This was # the case, when still using a blocking pty.stdin.read(1) # instead of a non-blocking readmany. time.sleep(0.01) return ''.join(result)