def __init__(self, node, pty=None, logger=None, is_sandbox=False): assert isinstance(node, Node) self._node = node self._pty = pty or DummyPty() self._logger = logger or DummyLoggerInterface() self._is_sandbox = is_sandbox # When the node is callable (when it has a default action), # make sure that this env becomes collable as well. if callable(self._node): # Per instance overriding def call(self, *a, **kw): return self.__getattr__('__call__')(*a, **kw) self.__class__ = type(self.__class__.__name__, (self.__class__, ), {'__call__': call}) # Create a new HostsContainer object which is identical to the one of # the Node object, but add pty/logger/sandbox settings. (So, this # doesn't create new Host instances, only a new container.) # (do this in this constructor. Each call to Env.hosts should return # the same host container instance.) self._hosts = HostsContainer(self._node.hosts.get_hosts_as_dict(), pty=self._pty, logger=self._logger, is_sandbox=is_sandbox) # Lock Env self._lock_env = True
def test_input(self): return pty = DummyPty('my-input\n') host = self.get_host(pty=pty) result = host.run('read varname; echo $varname') self.assertEqual(result, 'my-input\r\nmy-input\r\n')
def get_definition(self): hosts = self.get_hosts() class Hosts: role1 = { hosts.h1, hosts.h2} role2 = { hosts.h3, hosts.h4, hosts.h5 } role3 = { hosts.h1 } return HostsContainer.from_definition(Hosts, pty=DummyPty())
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 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 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 test_term_var(self): pty = DummyPty() host = self.get_host(pty=pty) # Changing the TERM variable of the PTY object. # (set_term_var is called by a client.) pty.set_term_var('xterm') self.assertEqual(host.run('echo $TERM', interactive=False).strip(), 'xterm') pty.set_term_var('another-term') self.assertEqual(host.run('echo $TERM', interactive=False).strip(), 'another-term') # This with statement should override the variable. with host.host_context.env('TERM', 'env-variable'): self.assertEqual(host.run('echo $TERM', interactive=False).strip(), 'env-variable')
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())
def run(self, command, use_sudo=False, sandbox=False, interactive=True, user=None, ignore_exit_status=False, initial_input=None, silent=False): """ Execute this shell command on the host. :param command: The shell command. :type command: basestring :param use_sudo: Run as superuser. :type use_sudo: bool :param sandbox: Validate syntax instead of really executing. (Wrap the command in ``bash -n``.) :type sandbox: bool :param interactive: Start an interactive event loop which allows interaction with the remote command. Otherwise, just return the output. :type interactive: bool :param initial_input: When ``interactive``, send this input first to the host. """ assert isinstance(command, basestring) assert not initial_input or interactive # initial_input can only in case of interactive. logger = DummyLoggerInterface() if silent else self.logger pty = DummyPty() if silent else self.pty # Create new channel for this command chan = self._get_session() # Run in PTY (Sudo usually needs to be run into a pty) if interactive: height, width = pty.get_size() chan.get_pty(term=self.pty.get_term_var(), width=width, height=height) # Keep size of local pty and remote pty in sync def set_size(): height, width = pty.get_size() try: chan.resize_pty(width=width, height=height) except paramiko.SSHException as e: # Channel closed. Ignore when channel was already closed. pass pty.set_ssh_channel_size = set_size else: pty.set_ssh_channel_size = lambda:None command = " && ".join(self.host_context._command_prefixes + [command]) # Start logger with logger.log_run(self, command=command, use_sudo=use_sudo, sandboxing=sandbox, interactive=interactive) as log_entry: # Are we sandboxing? Wrap command in "bash -n" if sandbox: command = "bash -n -c '%s' " % esc1(command) command = "%s;echo '%s'" % (command, esc1(command)) logging.info('Running "%s" on host "%s" sudo=%r, interactive=%r' % (command, self.slug, use_sudo, interactive)) # Execute if use_sudo: # We use 'sudo su' instead of 'sudo -u', because shell expension # of ~ is threated differently. e.g. # # 1. This will still show the home directory of the original user # sudo -u 'postgres' bash -c ' echo $HOME ' # # 2. This shows the home directory of the user postgres: # sudo su postgres -c 'echo $HOME ' if interactive: wrapped_command = self._wrap_command(( "sudo -p '%s' su '%s' -c '%s'" % (esc1(self.magic_sudo_prompt), esc1(user), esc1(command)) #"sudo -u '%s' bash -c '%s'" % (user, esc1(command)) if user else "sudo -p '%s' bash -c '%s' " % (esc1(self.magic_sudo_prompt), esc1(command))), sandbox ) logging.debug('Running wrapped command "%s"' % wrapped_command) chan.exec_command(wrapped_command) # Some commands, like certain /etc/init.d scripts cannot be # run interactively. They won't work in a ssh pty. else: wrapped_command = self._wrap_command(( "echo '%s' | sudo -p '(passwd)' -u '%s' -P %s " % (esc1(self.password), esc1(user), command) if user else "echo '%s' | sudo -p '(passwd)' -S %s " % (esc1(self.password), command)), sandbox ) logging.debug('Running wrapped command "%s" interactive' % wrapped_command) chan.exec_command(wrapped_command) else: chan.exec_command(self._wrap_command(command, sandbox)) if interactive: # Pty receive/send loop result = self._posix_shell(chan, initial_input=initial_input) else: # Read loop. result = self._read_non_interactive(chan) #print result # I don't think we need to print the result of non-interactive runs # In any case self._run_silent_sudo should not print anything. # Retrieve status code status_code = chan.recv_exit_status() log_entry.set_status_code(status_code) pty.set_ssh_channel_size = None if status_code and not ignore_exit_status: raise ExecCommandFailed(command, self, use_sudo=use_sudo, status_code=status_code, result=result) # Return result if sandbox: return '<Not sure in sandbox>' else: return result
def run(self, command, use_sudo=False, sandbox=False, interactive=True, user=None, ignore_exit_status=False, initial_input=None, silent=False): """ Execute this shell command on the host. :param command: The shell command. :type command: basestring :param use_sudo: Run as superuser. :type use_sudo: bool :param sandbox: Validate syntax instead of really executing. (Wrap the command in ``bash -n``.) :type sandbox: bool :param interactive: Start an interactive event loop which allows interaction with the remote command. Otherwise, just return the output. :type interactive: bool :param initial_input: When ``interactive``, send this input first to the host. """ assert isinstance(command, basestring) assert not initial_input or interactive # initial_input can only in case of interactive. logger = DummyLoggerInterface() if silent else self.logger pty = DummyPty() if silent else self.pty # Create new channel for this command chan = self._get_session() # Run in PTY (Sudo usually needs to be run into a pty) if interactive: height, width = pty.get_size() chan.get_pty(term=self.pty.get_term_var(), width=width, height=height) # Keep size of local pty and remote pty in sync def set_size(): height, width = pty.get_size() try: chan.resize_pty(width=width, height=height) except paramiko.SSHException as e: # Channel closed. Ignore when channel was already closed. pass pty.set_ssh_channel_size = set_size else: pty.set_ssh_channel_size = lambda: None command = " && ".join(self.host_context._command_prefixes + [command]) # Start logger with logger.log_run(self, command=command, use_sudo=use_sudo, sandboxing=sandbox, interactive=interactive) as log_entry: # Are we sandboxing? Wrap command in "bash -n" if sandbox: command = "bash -n -c '%s' " % esc1(command) command = "%s;echo '%s'" % (command, esc1(command)) logging.info('Running "%s" on host "%s" sudo=%r, interactive=%r' % (command, self.slug, use_sudo, interactive)) # Execute if use_sudo: # We use 'sudo su' instead of 'sudo -u', because shell expension # of ~ is threated differently. e.g. # # 1. This will still show the home directory of the original user # sudo -u 'postgres' bash -c ' echo $HOME ' # # 2. This shows the home directory of the user postgres: # sudo su postgres -c 'echo $HOME ' if interactive: wrapped_command = self._wrap_command( ( "sudo -p '%s' su '%s' -c '%s'" % (esc1(self.magic_sudo_prompt), esc1(user), esc1(command)) #"sudo -u '%s' bash -c '%s'" % (user, esc1(command)) if user else "sudo -p '%s' bash -c '%s' " % (esc1(self.magic_sudo_prompt), esc1(command))), sandbox) logging.debug('Running wrapped command "%s"' % wrapped_command) chan.exec_command(wrapped_command) # Some commands, like certain /etc/init.d scripts cannot be # run interactively. They won't work in a ssh pty. else: wrapped_command = self._wrap_command( ("echo '%s' | sudo -p '(passwd)' -u '%s' -P %s " % (esc1(self.password), esc1(user), command) if user else "echo '%s' | sudo -p '(passwd)' -S %s " % (esc1(self.password), command)), sandbox) logging.debug('Running wrapped command "%s" interactive' % wrapped_command) chan.exec_command(wrapped_command) else: chan.exec_command(self._wrap_command(command, sandbox)) if interactive: # Pty receive/send loop result = self._posix_shell(chan, initial_input=initial_input) else: # Read loop. result = self._read_non_interactive(chan) #print result # I don't think we need to print the result of non-interactive runs # In any case self._run_silent_sudo should not print anything. # Retrieve status code status_code = chan.recv_exit_status() log_entry.set_status_code(status_code) pty.set_ssh_channel_size = None if status_code and not ignore_exit_status: raise ExecCommandFailed(command, self, use_sudo=use_sudo, status_code=status_code, result=result) # Return result if sandbox: return '<Not sure in sandbox>' else: return result
def __init__(self, pty=None, logger=None): self.host_context = HostContext() self.pty = pty or DummyPty() self.logger = logger or DummyLoggerInterface()