diff --git a/pulp_smash/cli.py b/pulp_smash/cli.py index 3ab91257b..e5b60922d 100644 --- a/pulp_smash/cli.py +++ b/pulp_smash/cli.py @@ -213,7 +213,9 @@ def __init__(self, cfg, response_handler=None, pulp_host=None): else: self.response_handler = response_handler - def run(self, args, **kwargs): + self.cfg = cfg + + def run(self, args, sudo=False, **kwargs): """Run a command and ``return self.response_handler(result)``. This method is a thin wrapper around Plumbum's `BaseCommand.run`_ @@ -221,6 +223,10 @@ def run(self, args, **kwargs): `subprocess.Popen`_ class. See their documentation for detailed usage instructions. See :class:`pulp_smash.cli.Client` for a usage example. + :param args: Any arguments to be passed to the process (a tuple). + :param sudo: If the command should run as superuser (a boolean). + :param kwargs: Extra named arguments passed to plumbumBaseCommand.run. + .. _BaseCommand.run: http://plumbum.readthedocs.io/en/latest/api/commands.html#plumbum.commands.base.BaseCommand.run .. _subprocess.Popen: @@ -230,6 +236,9 @@ def run(self, args, **kwargs): # https://plumbum.readthedocs.io/en/latest/api/commands.html#plumbum.commands.base.BaseCommand.run kwargs.setdefault('retcode') + if sudo and args[0] != 'sudo' and not is_root(self.cfg): + args = ('sudo',) + tuple(args) + code, stdout, stderr = self.machine[args[0]].run(args[1:], **kwargs) completed_process = CompletedProcess(args, code, stdout, stderr) return self.response_handler(completed_process) diff --git a/tests/test_cli.py b/tests/test_cli.py index a8f981811..654732c83 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -167,6 +167,58 @@ def test_explicit_pulp_host(self): plumbum.machines.SshMachine.assert_called_once_with( cfg.hosts[1].hostname) + def test_run(self): + """Test run commands.""" + cfg = _get_pulp_smash_config(hosts=[ + config.PulpHost( + hostname=socket.getfqdn(), + roles={'shell': {}}, + ) + ]) + client = cli.Client(cfg) + with mock.patch.object(client, 'machine') as machine: + + machine.__getitem__.return_value = machine + machine.run.return_value = (0, 'ok', None) + + result = client.run(('ls', '-la')) + + # Internal call is: `machine[args[0]].run(args[1:], **kwargs)` + # So assert `machine['ls']` is called + machine.__getitem__.assert_called_once_with('ls') + # then `.run(('-la',), **kwargs)` + machine.run.assert_called_once_with(('-la',), retcode=None) + + self.assertEqual(result.returncode, 0) + self.assertEqual(result.stdout, 'ok') + self.assertIsNone(result.stderr) + + def test_run_as_sudo(self): + """Test run commands as sudo.""" + cfg = _get_pulp_smash_config(hosts=[ + config.PulpHost( + hostname=socket.getfqdn(), + roles={'shell': {}}, + ) + ]) + client = cli.Client(cfg) + with mock.patch.object(client, 'machine') as machine: + + machine.__getitem__.return_value = machine + machine.run.return_value = (0, 'ok', None) + + result = client.run(('ls', '-la'), sudo=True) + + # Internal call is: `machine[args[0]].run(args[1:], **kwargs)` + # So assert `machine['sudo']` is called + machine.__getitem__.assert_called_once_with('sudo') + # then `.run(('ls', '-la'), **kwargs)` + machine.run.assert_called_once_with(('ls', '-la'), retcode=None) + + self.assertEqual(result.returncode, 0) + self.assertEqual(result.stdout, 'ok') + self.assertIsNone(result.stderr) + class IsRootTestCase(unittest.TestCase): """Tests for :class:`pulp_smash.cli.is_root`."""