diff --git a/docs/Configuration.md b/docs/Configuration.md index 8c7a533a..f422e338 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -131,6 +131,8 @@ backup_grace_period_in_days = 10 ;cert_file = ;keepalive_seconds = ;use_pty = +; Enables the usage of a 'login' shell which, among other things, loads user's profile files. +;login_shell = False [checks] ;health_check = diff --git a/medusa-example.ini b/medusa-example.ini index 57275957..aa9970e7 100644 --- a/medusa-example.ini +++ b/medusa-example.ini @@ -141,6 +141,8 @@ use_sudo_for_restore = True ;key_file = ;port = +; Enables the usage of a 'login' shell which, among other things, loads user's profile files. +;login_shell = False [checks] ;health_check = diff --git a/medusa/config.py b/medusa/config.py index 3e9d3ddd..0082608b 100644 --- a/medusa/config.py +++ b/medusa/config.py @@ -45,7 +45,7 @@ SSHConfig = collections.namedtuple( 'SSHConfig', - ['username', 'key_file', 'port', 'cert_file', 'use_pty', 'keepalive_seconds'] + ['username', 'key_file', 'port', 'cert_file', 'use_pty', 'keepalive_seconds', 'login_shell'] ) ChecksConfig = collections.namedtuple( @@ -148,7 +148,8 @@ def _build_default_config(): 'port': '22', 'cert_file': '', 'use_pty': 'False', - 'keepalive_seconds': '60' + 'keepalive_seconds': '60', + 'login_shell': 'False' } config['checks'] = { diff --git a/medusa/orchestration.py b/medusa/orchestration.py index 37a72f06..e82a5216 100644 --- a/medusa/orchestration.py +++ b/medusa/orchestration.py @@ -46,6 +46,7 @@ def pssh_run(self, hosts, command, hosts_variables=None, ssh_client=None): cert_file = self.config.ssh.cert_file if self.config.ssh.cert_file != '' else None keepalive_seconds = int(self.config.ssh.keepalive_seconds) use_pty = medusa.utils.evaluate_boolean(self.config.ssh.use_pty) + use_login_shell = medusa.utils.evaluate_boolean(self.config.ssh.login_shell) if ssh_client is None: if cert_file is None: @@ -81,9 +82,12 @@ def pssh_run(self, hosts, command, hosts_variables=None, ssh_client=None): pkey=pkey, cert_file=cert_file) - logging.debug('Batch #{i}: Running "{command}" on nodes {hosts} parallelism of {pool_size}' - .format(i=i, command=command, hosts=parallel_hosts, pool_size=len(parallel_hosts))) - output = client.run_command(command, host_args=hosts_variables, use_pty=use_pty, + logging.debug(f'Batch #{i}: Running "{command}" nodes={parallel_hosts} parallelism={len(parallel_hosts)} ' + f'login_shell={use_login_shell}') + + shell = '$SHELL -cl' if use_login_shell else None + + output = client.run_command(command, host_args=hosts_variables, use_pty=use_pty, shell=shell, sudo=medusa.utils.evaluate_boolean(self.config.cassandra.use_sudo)) client.join(output) diff --git a/tests/orchestration_test.py b/tests/orchestration_test.py index 913c8771..9d9cc5b3 100644 --- a/tests/orchestration_test.py +++ b/tests/orchestration_test.py @@ -69,7 +69,8 @@ def _build_config_parser(): 'port': '22', 'cert_file': '', 'keepalive_seconds': '60', - 'use_pty': 'False' + 'use_pty': 'False', + 'login_shell': 'False', } return config @@ -93,12 +94,16 @@ def test_pssh_with_sudo(self): self.mock_pssh.run_command.return_value = output assert self.orchestration.pssh_run(list(self.hosts.keys()), 'fake command', ssh_client=self.fake_ssh_client_factory) - self.mock_pssh.run_command.assert_called_with('fake command', host_args=None, use_pty=False, sudo=True) + self.mock_pssh.run_command.assert_called_with( + 'fake command', + host_args=None, use_pty=False, sudo=True, shell=None + ) def test_pssh_without_sudo(self): """Ensure that Parallel SSH honors configuration when we don't want to use sudo in commands""" conf = self.config conf['cassandra']['use_sudo'] = 'False' + conf['ssh']['login_shell'] = 'True' medusa_conf = self._build_medusa_config(conf) orchestration_no_sudo = Orchestration(medusa_conf) @@ -107,7 +112,10 @@ def test_pssh_without_sudo(self): assert orchestration_no_sudo.pssh_run(list(self.hosts.keys()), 'fake command', ssh_client=self.fake_ssh_client_factory) - self.mock_pssh.run_command.assert_called_with('fake command', host_args=None, use_pty=False, sudo=False) + self.mock_pssh.run_command.assert_called_with( + 'fake command', + ost_args=None, use_pty=False, sudo=False, shell='$SHELL -cl' + ) def test_pssh_run_failure(self): """Ensure that Parallel SSH detects a failed command on a host""" @@ -120,7 +128,10 @@ def test_pssh_run_failure(self): self.mock_pssh.run_command.return_value = output assert not self.orchestration.pssh_run(list(self.hosts.keys()), 'fake command', ssh_client=self.fake_ssh_client_factory) - self.mock_pssh.run_command.assert_called_with('fake command', host_args=None, use_pty=False, sudo=True) + self.mock_pssh.run_command.assert_called_with( + 'fake command', + host_args=None, use_pty=False, sudo=True, shell=None + ) if __name__ == '__main__':