def _inspect_query_attribute(self): console = Console(self.shell.pty) query = Inspector(self.node).get_query(self.attr_name).query def run(): yield termcolor.colored(' Node: ', 'cyan') + \ termcolor.colored(Inspector(self.node).get_full_name(), 'yellow') yield termcolor.colored(' Filename: ', 'cyan') + \ termcolor.colored(query._filename, 'yellow') yield termcolor.colored(' Line: ', 'cyan') + \ termcolor.colored(query._line, 'yellow') yield termcolor.colored(' Expression: ', 'cyan') + \ termcolor.colored(repr(query.query), 'yellow') yield '' # Execute query in sandboxed environment. yield 'Trace query:' try: insp = Inspector(self._get_env()) result = insp.trace_query(self.attr_name) except Exception as e: yield 'Failed to execute query: %r' % e return # Print query and all subqueries with their results. for subquery in result.walk_through_subqueries(): yield termcolor.colored(repr(subquery[0]), 'cyan') yield ' %s' % subquery[1] console.lesspipe(run())
def pty_based_auth(auth_backend, pty): """ Show a username/password login prompt. Return username when authentication succeeded. """ tries = 0 while True: # Password authentication required for this session? sys.stdout.write('\033[2J\033[0;0H') # Clear screen sys.stdout.write(colored('Please authenticate\r\n\r\n', 'cyan')) if tries: sys.stdout.write( colored(' Authentication failed, try again\r\n', 'red')) try: console = Console(pty) username = console.input('Username', False) password = console.input('Password', True) except NoInput: raise NotAuthenticated if auth_backend.authenticate(username, password): sys.stdout.write( colored(' ' * 40 + 'Authentication successful\r\n\r\n', 'green')) return username else: tries += 1 if tries == 3: raise NotAuthenticated
def fill_cache(self, pty): """ Fill cache for current directory. """ console = Console(pty) with console.progress_bar('Reading directory...', clear_on_finish=True): cwd = self.getcwd() for s in self.listdir_stat(): self._stat_cache[(cwd, s.filename)] = s
def read(rf, size=-1): if rf._is_open: # Always read in chunks of 1024 bytes and show a progress bar. # Create progress bar. p = Console(self.pty).progress_bar('Downloading data', expected=(size if size >= 0 else None)) result = StringIO() with p: while True: if size == 0: break elif size < 0: # If we have to read until EOF, keep reaeding # in chunks of 1024 chunk = rf._file.read(1024) else: # If we have to read for a certain size, read # until we reached that size. read_size = min(1024, size) chunk = rf._file.read(read_size) size -= len(chunk) if not chunk: break # EOF result.write(chunk) p.set_progress(result.len) return result.getvalue() else: raise IOError('Cannot read from closed remote file')
def read(rf, size=-1): if rf._is_open: # Always read in chunks of 1024 bytes and show a progress bar. # Create progress bar. p = Console(self.pty).progress_bar( 'Downloading data', expected=(size if size >= 0 else None)) result = StringIO() with p: while True: if size == 0: break elif size < 0: # If we have to read until EOF, keep reaeding # in chunks of 1024 chunk = rf._file.read(1024) else: # If we have to read for a certain size, read # until we reached that size. read_size = min(1024, size) chunk = rf._file.read(read_size) size -= len(chunk) if not chunk: break # EOF result.write(chunk) p.set_progress(result.len) return result.getvalue() else: raise IOError('Cannot read from closed remote file')
def __call__(self): w = self.shell.stdout.write console = Console(self.shell.pty) def run(): # Print nodes if Inspector(self.node).get_childnodes(): yield 'Child nodes:' def column_iterator(): for c in Inspector(self.node).get_childnodes(): name = Inspector(c).get_name() yield termcolor.colored( name, type_of_node(c).color), len(name) for line in console.in_columns(column_iterator()): yield line # Print actions if Inspector(self.node).get_actions(): yield 'Actions:' def column_iterator(): for a in Inspector(self.node).get_actions(): yield termcolor.colored(a.name, type_of_action(a).color), len( a.name) for line in console.in_columns(column_iterator()): yield line console.lesspipe(run())
def __call__(self): options = [] for m in self.node.__class__.__mro__: if m.__module__ != 'deployer.node' and m != object: options.append( ('%s.%s' % (termcolor.colored(m.__module__, 'red'), termcolor.colored(m.__name__, 'yellow')), m)) if len(options) > 1: try: node_class = Console(self.shell.pty).choice( 'Choose node definition', options) except NoInput: return else: node_class = options[0][1] def run(): try: # Retrieve source source = inspect.getsource(node_class) # Highlight code source = highlight(source, PythonLexer(), TerminalFormatter()) for l in source.split('\n'): yield l.rstrip('\n') except IOError: yield 'Could not retrieve source code.' Console(self.shell.pty).lesspipe(run())
def pty_based_auth(auth_backend, pty): """ Show a username/password login prompt. Return username when authentication succeeded. """ tries = 0 while True: # Password authentication required for this session? sys.stdout.write('\033[2J\033[0;0H') # Clear screen sys.stdout.write(colored('Please authenticate\r\n\r\n', 'cyan')) if tries: sys.stdout.write(colored(' Authentication failed, try again\r\n', 'red')) try: console = Console(pty) username = console.input('Username', False) password = console.input('Password', True) except NoInput: raise NotAuthenticated if auth_backend.authenticate(username, password): sys.stdout.write(colored(' ' * 40 + 'Authentication successful\r\n\r\n', 'green')) return username else: tries += 1 if tries == 3: raise NotAuthenticated
def __call__(self): w = self.shell.stdout.write console = Console(self.shell.pty) def run(): # Print nodes if Inspector(self.node).get_childnodes(): yield 'Child nodes:' def column_iterator(): for c in Inspector(self.node).get_childnodes(): name = Inspector(c).get_name() yield termcolor.colored(name, type_of_node(c).color), len(name) for line in console.in_columns(column_iterator()): yield line # Print actions if Inspector(self.node).get_actions(): yield 'Actions:' def column_iterator(): for a in Inspector(self.node).get_actions(): yield termcolor.colored(a.name, type_of_action(a).color), len(a.name) for line in console.in_columns(column_iterator()): yield line console.lesspipe(run())
def _create_connect_progress_bar(self, host): from deployer.console import ProgressBarSteps console = Console(host.pty) return console.progress_bar_with_steps('Connecting %s (%s)' % (host.address, host.slug), steps=ProgressBarSteps({ 1: "Resolving DNS", 2: "Creating socket", 3: "Creating transport", 4: "Exchanging keys", 5: "Authenticating" }), format_str="%(message)s: %(counter)s/%(expected)s %(status)s")
def _inspect_action(self): console = Console(self.shell.pty) action = Inspector(self.node).get_action(self.attr_name) def run(): yield termcolor.colored(' Action name: ', 'cyan') + \ termcolor.colored(self.attr_name, 'yellow') yield termcolor.colored(' __repr__: ', 'cyan') + \ termcolor.colored(repr(action._func), 'yellow') yield termcolor.colored(' Node: ', 'cyan') + \ termcolor.colored(repr(self.node), 'yellow') console.lesspipe(run())
def __call__(self): host = get_host_func(self.shell) files = host.listdir() def iterator(): for f in files: if host.stat(f).is_dir: yield colored(f, DirectoryType.color), len(f) else: yield f, len(f) console = Console(self.shell.pty) console.lesspipe(console.in_columns(iterator()))
def lview(shell, path): """ View local file. """ console = Console(shell.pty) with shell.localhost.open(path, 'r') as f: def reader(): while True: line = f.readline() if line: yield line.rstrip('\n') else: return # EOF console.lesspipe(reader())
def print_all_completions(self, all_completions): self.stdout.write('\r\n') # Create an iterator which yields all the comments (in their color), # and pass it through in_columns/lesspipe def column_items(): for w in all_completions: handler_type = w[1].handler_type text = '%s%s' % ( termcolor.colored(w[0], handler_type.color), termcolor.colored(handler_type.postfix, handler_type.postfix_color)) length = len(w[0]) + len(handler_type.postfix) yield text, length c = Console(self.pty) c.lesspipe(c.in_columns(column_items()))
def print_all_completions(self, all_completions): self.stdout.write('\r\n') # Create an iterator which yields all the comments (in their color), # and pass it through in_columns/lesspipe def column_items(): for w in all_completions: handler_type = w[1].handler_type text = '%s%s' % (termcolor.colored(w[0], handler_type.color), termcolor.colored(handler_type.postfix, handler_type.postfix_color)) length = len(w[0]) + len(handler_type.postfix) yield text, length c = Console(self.pty) c.lesspipe(c.in_columns(column_items()))
def console(self): """ Interface for user input. Returns a :class:`deployer.console.Console` instance. """ if not self._pty: raise AttributeError( 'Console is not available in Env when no pty was given.') return Console(self._pty)
def test_input(self): # Test normal input p = DummyPty(input_data='my-input\n') result = Console(p).input('input question') output = p.get_output() self.assertEqual(result, 'my-input') self.assertIn('input question', output) # Test default input p = DummyPty(input_data='\n') result = Console(p).input('input question', default='default-value') self.assertEqual(result, 'default-value') p = DummyPty(input_data='my-input\n') p.interactive = True # We have to set interactive=True, otherwise # Console will take the default value anyway. result = Console(p).input('input question', default='default-value') self.assertEqual(result, 'my-input')
def _get_env(self): """ Created a sandboxed environment for evaluation of attributes. (attributes shouldn't have side effects on servers, so sandboxing is fine.) Returns an ``Env`` object. """ env = Env(self.node, self.shell.pty, self.shell.logger_interface, is_sandbox=True) return Console(self.shell.pty).select_node_isolation(env)
def test_confirm(self): question = 'this is my question' # Test various inputs for inp, result in [ ('yes', True), ('y', True), ('no', False), ('n', False) ]: p = DummyPty(input_data='%s\n' % inp) c = Console(p) returnvalue = c.confirm(question) self.assertEqual(returnvalue, result) self.assertIn(question, p.get_output()) # Test default p = DummyPty(input_data='\n') c = Console(p) self.assertEqual(c.confirm('', default=True), True) self.assertEqual(c.confirm('', default=False), False)
def write(rf, data): if rf._is_open: # On some hosts, Paramiko blocks when writing more than # 1180 bytes at once. Not sure about the reason or the # exact limit, but using chunks of 1024 seems to work # well. (and that way we can visualise our progress bar.) # Create progress bar. size=len(data) p = Console(self.pty).progress_bar('Uploading data', expected=size) with p: if len(data) > 1024: while data: p.set_progress(size - len(data), rewrite=False) # Auto rewrite rf._file.write(data[:1024]) data = data[1024:] else: rf._file.write(data) p.set_progress(size, rewrite=True) else: raise IOError('Cannot write to closed remote file')
def _inspect_property_attribute(self): console = Console(self.shell.pty) action = Inspector(self.node).get_property(self.attr_name) def run(): yield termcolor.colored(' Property name: ', 'cyan') + \ termcolor.colored(self.attr_name, 'yellow') yield termcolor.colored(' __repr__: ', 'cyan') + \ termcolor.colored(repr(action._func), 'yellow') yield termcolor.colored(' Node: ', 'cyan') + \ termcolor.colored(repr(self.node), 'yellow') # Value try: value = getattr(self._get_env(), self.attr_name) yield termcolor.colored(' Value: ', 'cyan') + \ termcolor.colored(repr(value), 'yellow') except Exception as e: yield termcolor.colored(' Value: ', 'cyan') + \ termcolor.colored('Failed to evaluate value...', 'yellow') console.lesspipe(run())
def _run_on_node(self, isolation, *a, **kw): """ Run the action on one isolation. (On a normal Node, or on a ParallelNode cell.) """ with isolation._logger.group(self._action._func.__name__, *a, **kw): while True: try: return self._action._func(isolation, *a, **kw) except ActionException as e: raise except ExecCommandFailed as e: isolation._logger.log_exception(e) if self._env._pty.interactive: # If the console is interactive, ask what to do, otherwise, just abort # without showing this question. choice = Console(self._env._pty).choice( 'Continue?', [('Retry', 'retry'), ('Skip (This will not always work.)', 'skip'), ('Abort', 'abort')], default='abort') else: choice = 'abort' if choice == 'retry': continue elif choice == 'skip': class SkippedTaskResult(object): def __init__(self, node, action): self._node = node self._action = action def __getattribute__(self, name): raise Exception( 'SkippedTask(%r.%r) does not have an attribute %r' % (object.__getattr__(self, '_node'), object.__getattr__(self, '_action'), name)) return SkippedTaskResult(self._env._node, self._action) elif choice == 'abort': # TODO: send exception to logger -> and print it raise ActionException(e, traceback.format_exc()) except Exception as e: e2 = ActionException(e, traceback.format_exc()) isolation._logger.log_exception(e2) raise e2
def _ensure_password_is_known(self): # Make sure that we know the localhost password, before running sudo. global _localhost_password tries = 0 while _localhost_password is None: _localhost_password = Console(self.pty).input( '[sudo] password for %s at %s' % (self.username, self.slug), is_password=True) # Check password try: Host._run_silent_sudo(self, 'ls > /dev/null') except ExecCommandFailed: print 'Incorrect password' self._backend.password = None tries += 1 if tries >= 3: raise Exception('Incorrect password')
def __call__(self): def _list_nested_nodes(node, prefix): for a in Inspector(node).get_actions(): yield '%s %s' % (prefix, termcolor.colored( a.name, Inspector(a.node).get_group().color)) for c in Inspector(node).get_childnodes(): # Check the parent, to avoid loops. if c.parent == node: name = Inspector(c).get_name() for i in _list_nested_nodes( c, '%s %s' % (prefix, termcolor.colored(name, Inspector(c).get_group().color))): yield i Console(self.shell.pty).lesspipe(_list_nested_nodes(self.node, ''))
def __call__(self): # Choose host. hosts = self.node.hosts.get_hosts() if len(hosts) == 0: print 'No hosts found' return elif len(hosts) == 1: host = hosts.copy().pop() else: # Choose a host. options = [(h.slug, h) for h in hosts] try: host = Console(self.shell.pty).choice('Choose a host', options, allow_random=True) except NoInput: return # Start scp shell from deployer.scp_shell import Shell Shell(self.shell.pty, host, self.shell.logger_interface).cmdloop()
def test_confirm(self): question = 'this is my question' # Test various inputs for inp, result in [('yes', True), ('y', True), ('no', False), ('n', False)]: p = DummyPty(input_data='%s\n' % inp) c = Console(p) returnvalue = c.confirm(question) self.assertEqual(returnvalue, result) self.assertIn(question, p.get_output()) # Test default p = DummyPty(input_data='\n') c = Console(p) self.assertEqual(c.confirm('', default=True), True) self.assertEqual(c.confirm('', default=False), False)
def write(rf, data): if rf._is_open: # On some hosts, Paramiko blocks when writing more than # 1180 bytes at once. Not sure about the reason or the # exact limit, but using chunks of 1024 seems to work # well. (and that way we can visualise our progress bar.) # Create progress bar. size = len(data) p = Console(self.pty).progress_bar('Uploading data', expected=size) with p: if len(data) > 1024: while data: p.set_progress(size - len(data), rewrite=False) # Auto rewrite rf._file.write(data[:1024]) data = data[1024:] else: rf._file.write(data) p.set_progress(size, rewrite=True) else: raise IOError('Cannot write to closed remote file')
def get_command(self): try: text = '[SUDO] Enter command' if self.use_sudo else 'Enter command' return Console(self.shell.pty).input(text) except NoInput: return
def _inspect_node(self): console = Console(self.shell.pty) def inspect(): # Print full name yield termcolor.colored(' Node: ', 'cyan') + \ termcolor.colored(Inspector(self.node).get_full_name(), 'yellow') # Print mro yield termcolor.colored(' Mro:', 'cyan') i = 1 for m in self.node.__class__.__mro__: if m.__module__ != 'deployer.node' and m != object: yield termcolor.colored(' %i ' % i, 'cyan') + \ termcolor.colored('%s.' % m.__module__, 'red') + \ termcolor.colored('%s' % m.__name__, 'yellow') i += 1 # File names yield termcolor.colored(' Files:', 'cyan') i = 1 for m in self.node.__class__.__mro__: if m.__module__ != 'deployer.node' and m != object: yield termcolor.colored(' %i ' % i, 'cyan') + \ termcolor.colored(getfile(m), 'red') i += 1 # Print host mappings yield termcolor.colored(' Hosts:', 'cyan') for role in sorted(self.node.hosts._hosts.keys()): items = self.node.hosts._hosts[role] yield termcolor.colored(' "%s"' % role, 'yellow') i = 1 for host in sorted(items, key=lambda h:h.slug): yield termcolor.colored(' %3i ' % i, 'cyan') + \ termcolor.colored('%-25s (%s)' % (host.slug, getattr(host, 'address', '')), 'red') i += 1 # Print the first docstring (look to the parents) for m in self.node.__class__.__mro__: if m.__module__ != 'deployer.node' and m != object and m.__doc__: yield termcolor.colored(' Docstring:\n', 'cyan') + \ termcolor.colored(m.__doc__ or '<None>', 'red') break # Actions yield termcolor.colored(' Actions:', 'cyan') def item_iterator(): for a in Inspector(self.node).get_actions(): yield termcolor.colored(a.name, 'red'), len(a.name) for line in console.in_columns(item_iterator(), margin_left=13): yield line # Nodes yield termcolor.colored(' Sub nodes:', 'cyan') # Group by node group grouper = lambda c:Inspector(c).get_group() for group, nodes in groupby(sorted(Inspector(self.node).get_childnodes(), key=grouper), grouper): yield termcolor.colored(' "%s"' % group.name, 'yellow') # Create iterator for all the items in this group def item_iterator(): for n in nodes: name = Inspector(n).get_name() if n.parent == self.node: text = termcolor.colored(name, type_of_node(n).color) length = len(name) else: full_name = Inspector(n).get_full_name() text = termcolor.colored('%s -> %s' % (name, full_name), type_of_node(n).color) length = len('%s -> %s' % (name, full_name)) yield text, length # Show in columns for line in console.in_columns(item_iterator(), margin_left=13): yield line console.lesspipe(inspect())
def __call__(self, *a, **kw): # In case of a not_isolated ParallelNode, return a # ParallelActionResult, otherwise, just return the actual result. if isinstance(self._env, ParallelNode) and not self._env._node_is_isolated and \ not getattr(self._action._func, 'dont_isolate_yet', False): # Get isolations of the env. isolations = list(self._env) # No hosts in ParallelNode. Nothing to do. if len(isolations) == 0: self._env._logger.log_msg('Nothing to do. No hosts in %r' % self._action) return ParallelActionResult([]) # Exactly one host. elif len(isolations) == 1: return ParallelActionResult([ (isolations[0], self._run_on_node(isolations[0], *a, **kw)) ]) # Multiple hosts, but isolate_one_only flag set. elif getattr(self._action._func, 'isolate_one_only', False): # Ask the end-user which one to use. # TODO: this is not necessarily okay. we can have several levels of isolation. options = [('%s [%s]' % (i.host.slug, i.host.address), i) for i in isolations] i = Console(self._env._pty).choice('Choose a host', options, allow_random=True) return self._run_on_node(i, *a, **kw) # Multiple hosts. Fork for each isolation. else: errors = [] # Create a callable for each host. def closure(isolation): def call(pty): # Isolation should be an env, but i2 = Env(isolation._node, pty, isolation._logger, isolation._is_sandbox) # Fork logger logger_fork = self._env._logger.log_fork('On: %r' % i2._node) # TODO: maybe we shouldn't log fork(). Consider it an abstraction. try: # Run this action on the new service. result = self._run_on_node(i2, *a, **kw) # Succeed logger_fork.set_succeeded() return (isolation, result) except Exception as e: # TODO: handle exception in thread logger_fork.set_failed(e) errors.append(e) return call # For every isolation, create a callable. callables = [closure(i) for i in isolations] logging.info('Forking %r (%i pseudo terminals)' % (self._action, len(callables))) fork_result = self._env._pty.run_in_auxiliary_ptys(callables) fork_result.join() if errors: # When an error occcured in one fork, raise this error # again in current thread. raise errors[0] else: # This returns a list of results. return ParallelActionResult(fork_result.result) else: return self._run_on_node(self._env, *a, **kw)
def __call__(self): files = self.shell.localhost.listdir() console = Console(self.shell.pty) console.lesspipe(console.in_columns(files))
def _inspect_node(self): console = Console(self.shell.pty) def inspect(): # Print full name yield termcolor.colored(' Node: ', 'cyan') + \ termcolor.colored(Inspector(self.node).get_full_name(), 'yellow') # Print mro yield termcolor.colored(' Mro:', 'cyan') i = 1 for m in self.node.__class__.__mro__: if m.__module__ != 'deployer.node' and m != object: yield termcolor.colored(' %i ' % i, 'cyan') + \ termcolor.colored('%s.' % m.__module__, 'red') + \ termcolor.colored('%s' % m.__name__, 'yellow') i += 1 # File names yield termcolor.colored(' Files:', 'cyan') i = 1 for m in self.node.__class__.__mro__: if m.__module__ != 'deployer.node' and m != object: yield termcolor.colored(' %i ' % i, 'cyan') + \ termcolor.colored(getfile(m), 'red') i += 1 # Print host mappings yield termcolor.colored(' Hosts:', 'cyan') for role in sorted(self.node.hosts._hosts.keys()): items = self.node.hosts._hosts[role] yield termcolor.colored(' "%s"' % role, 'yellow') i = 1 for host in sorted(items, key=lambda h: h.slug): yield termcolor.colored(' %3i ' % i, 'cyan') + \ termcolor.colored('%-25s (%s)' % (host.slug, getattr(host, 'address', '')), 'red') i += 1 # Print the first docstring (look to the parents) for m in self.node.__class__.__mro__: if m.__module__ != 'deployer.node' and m != object and m.__doc__: yield termcolor.colored(' Docstring:\n', 'cyan') + \ termcolor.colored(m.__doc__ or '<None>', 'red') break # Actions yield termcolor.colored(' Actions:', 'cyan') def item_iterator(): for a in Inspector(self.node).get_actions(): yield termcolor.colored(a.name, 'red'), len(a.name) for line in console.in_columns(item_iterator(), margin_left=13): yield line # Nodes yield termcolor.colored(' Sub nodes:', 'cyan') # Group by node group grouper = lambda c: Inspector(c).get_group() for group, nodes in groupby( sorted(Inspector(self.node).get_childnodes(), key=grouper), grouper): yield termcolor.colored(' "%s"' % group.name, 'yellow') # Create iterator for all the items in this group def item_iterator(): for n in nodes: name = Inspector(n).get_name() if n.parent == self.node: text = termcolor.colored(name, type_of_node(n).color) length = len(name) else: full_name = Inspector(n).get_full_name() text = termcolor.colored( '%s -> %s' % (name, full_name), type_of_node(n).color) length = len('%s -> %s' % (name, full_name)) yield text, length # Show in columns for line in console.in_columns(item_iterator(), margin_left=13): yield line console.lesspipe(inspect())
def test_print_warning(self): p = DummyPty() c = Console(p) c.warning('this is a warning') self.assertIn('this is a warning', p.get_output())