From 8add184da016c521376481e6f7c5efe41093c0d1 Mon Sep 17 00:00:00 2001 From: aakash-test7 Date: Mon, 3 Mar 2025 18:09:21 +0530 Subject: [PATCH 1/4] Fix for issue #449 --- datashuttle/datashuttle_class.py | 13 +++++++ datashuttle/utils/ssh.py | 60 ++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/datashuttle/datashuttle_class.py b/datashuttle/datashuttle_class.py index fe2623795..e5ffe5df8 100644 --- a/datashuttle/datashuttle_class.py +++ b/datashuttle/datashuttle_class.py @@ -868,10 +868,23 @@ def setup_ssh_connection(self) -> None: if verified: ssh.setup_ssh_key(self.cfg, log=True) + # Directly use the central path removed input + if not self._check_write_permissions(self.cfg["central_path"]): # 449 + raise PermissionError( + f"Cannot write to the central path: {self.cfg['central_path']}" + ) self._setup_rclone_central_ssh_config(log=True) ds_logger.close_log_filehandler() + + def _check_write_permissions(self, path) -> bool: # 449 + """ + Check if the user has write permissions on the central path. + """ + return ssh.check_write_permissions(self.cfg, path, log=False) + + @requires_ssh_configs @check_is_not_local_project def write_public_key(self, filepath: str) -> None: diff --git a/datashuttle/utils/ssh.py b/datashuttle/utils/ssh.py index 2bf5db5c0..ff0e844a8 100644 --- a/datashuttle/utils/ssh.py +++ b/datashuttle/utils/ssh.py @@ -1,5 +1,7 @@ from __future__ import annotations +from dataclasses import dataclass + from typing import TYPE_CHECKING if TYPE_CHECKING: @@ -320,3 +322,61 @@ def get_list_of_folder_names_over_sftp( utils.log_and_message(f"No file found at {search_path.as_posix()}") return all_folder_names, all_filenames + + +# 449 +@dataclass +class SSHCommandResult: + stdout: str + stderr: str + returncode: int + + +def run_ssh_command( + cfg: Configs, + command: str, + password: Optional[str] = None, + log: bool = True, +) -> SSHCommandResult: + """ + Run a command on the remote server over SSH and return a result object. + """ + client = paramiko.SSHClient() + try: + connect_client_with_logging( + client, cfg, password, message_on_sucessful_connection=log + ) + stdin, stdout, stderr = client.exec_command(command) + stdout_output = stdout.read().decode("utf-8").strip() + stderr_output = stderr.read().decode("utf-8").strip() + return_code = stdout.channel.recv_exit_status() + + if log: + utils.log(f"Command executed: {command}") + utils.log(f"STDOUT: {stdout_output}") + utils.log(f"STDERR: {stderr_output}") + utils.log(f"Return code: {return_code}") + + return SSHCommandResult(stdout_output, stderr_output, return_code) + except Exception as e: + if log: + utils.log_and_raise_error( + f"Failed to execute command over SSH: {str(e)}", Exception + ) + raise + finally: + client.close() + +# 449 +def check_write_permissions(cfg: Configs, path: str, log: bool = True) -> bool: + """ + Check if the user has write permissions on the specified path on the remote server. + """ + try: + command = f"touch {path}/test_write_permission.txt && rm {path}/test_write_permission.txt" + result = run_ssh_command(cfg, command, log=log) + return result.returncode == 0 + except Exception as e: + if log: + utils.log(f"Write permission check failed: {e}") + return False From 9d183154eae19aec101df68da584f60166b44d48 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 12:51:55 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- datashuttle/datashuttle_class.py | 16 ++++++++-------- datashuttle/utils/ssh.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/datashuttle/datashuttle_class.py b/datashuttle/datashuttle_class.py index e5ffe5df8..801bdba62 100644 --- a/datashuttle/datashuttle_class.py +++ b/datashuttle/datashuttle_class.py @@ -869,7 +869,9 @@ def setup_ssh_connection(self) -> None: if verified: ssh.setup_ssh_key(self.cfg, log=True) # Directly use the central path removed input - if not self._check_write_permissions(self.cfg["central_path"]): # 449 + if not self._check_write_permissions( + self.cfg["central_path"] + ): # 449 raise PermissionError( f"Cannot write to the central path: {self.cfg['central_path']}" ) @@ -877,13 +879,11 @@ def setup_ssh_connection(self) -> None: ds_logger.close_log_filehandler() - - def _check_write_permissions(self, path) -> bool: # 449 - """ - Check if the user has write permissions on the central path. - """ - return ssh.check_write_permissions(self.cfg, path, log=False) - + def _check_write_permissions(self, path) -> bool: # 449 + """ + Check if the user has write permissions on the central path. + """ + return ssh.check_write_permissions(self.cfg, path, log=False) @requires_ssh_configs @check_is_not_local_project diff --git a/datashuttle/utils/ssh.py b/datashuttle/utils/ssh.py index ff0e844a8..121cb67b7 100644 --- a/datashuttle/utils/ssh.py +++ b/datashuttle/utils/ssh.py @@ -1,7 +1,6 @@ from __future__ import annotations from dataclasses import dataclass - from typing import TYPE_CHECKING if TYPE_CHECKING: @@ -367,6 +366,7 @@ def run_ssh_command( finally: client.close() + # 449 def check_write_permissions(cfg: Configs, path: str, log: bool = True) -> bool: """ From 220aad9018e6d80f5413b2693dd1aef0f533e1ae Mon Sep 17 00:00:00 2001 From: aakash-test7 Date: Thu, 1 May 2025 21:00:23 +0530 Subject: [PATCH 3/4] Check central_path write permissions during SSH setup. Fixes #449 --- datashuttle/datashuttle_class.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/datashuttle/datashuttle_class.py b/datashuttle/datashuttle_class.py index b16c4eda7..d733dc5dc 100644 --- a/datashuttle/datashuttle_class.py +++ b/datashuttle/datashuttle_class.py @@ -864,23 +864,10 @@ def setup_ssh_connection(self) -> None: if verified: ssh.setup_ssh_key(self.cfg, log=True) - # Directly use the central path removed input - if not self._check_write_permissions( - self.cfg["central_path"] - ): # 449 - raise PermissionError( - f"Cannot write to the central path: {self.cfg['central_path']}" - ) self._setup_rclone_central_ssh_config(log=True) ds_logger.close_log_filehandler() - def _check_write_permissions(self, path) -> bool: # 449 - """ - Check if the user has write permissions on the central path. - """ - return ssh.check_write_permissions(self.cfg, path, log=False) - @requires_ssh_configs @check_is_not_local_project def write_public_key(self, filepath: str) -> None: @@ -1470,7 +1457,11 @@ def _make_project_metadata_if_does_not_exist(self) -> None: """ folders.create_folders(self.cfg.project_metadata_path, log=False) - def _setup_rclone_central_ssh_config(self, log: bool) -> None: + def _setup_rclone_central_ssh_config(self, log: bool) -> None: #449 + """ + 1. Set up RClone SSH config. + 2. Check write permissions on central_path using the same SSH connection. + """ rclone.setup_rclone_config_for_ssh( self.cfg, self.cfg.get_rclone_config_name("ssh"), @@ -1478,6 +1469,14 @@ def _setup_rclone_central_ssh_config(self, log: bool) -> None: log=log, ) + if not ssh.check_write_permissions(self.cfg, self.cfg["central_path"], log=log): + raise PermissionError( + f"Cannot write to central path: {self.cfg['central_path']}\n" + ) + + if log: + utils.log("SSH and RClone setup completed successfully.") + def _setup_rclone_central_local_filesystem_config(self) -> None: rclone.setup_rclone_config_for_local_filesystem( self.cfg.get_rclone_config_name("local_filesystem"), From d803c75fe6be78bc86989bb8f805a5c2197175a7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 1 May 2025 15:30:49 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- datashuttle/datashuttle_class.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/datashuttle/datashuttle_class.py b/datashuttle/datashuttle_class.py index d733dc5dc..46cca19b2 100644 --- a/datashuttle/datashuttle_class.py +++ b/datashuttle/datashuttle_class.py @@ -1457,7 +1457,7 @@ def _make_project_metadata_if_does_not_exist(self) -> None: """ folders.create_folders(self.cfg.project_metadata_path, log=False) - def _setup_rclone_central_ssh_config(self, log: bool) -> None: #449 + def _setup_rclone_central_ssh_config(self, log: bool) -> None: # 449 """ 1. Set up RClone SSH config. 2. Check write permissions on central_path using the same SSH connection. @@ -1469,7 +1469,9 @@ def _setup_rclone_central_ssh_config(self, log: bool) -> None: #449 log=log, ) - if not ssh.check_write_permissions(self.cfg, self.cfg["central_path"], log=log): + if not ssh.check_write_permissions( + self.cfg, self.cfg["central_path"], log=log + ): raise PermissionError( f"Cannot write to central path: {self.cfg['central_path']}\n" )