diff --git a/.flake8 b/.flake8 index 0b70986..93bab28 100644 --- a/.flake8 +++ b/.flake8 @@ -1,6 +1,6 @@ [flake8] select = B,B9,C,D,DAR,E,F,N,RST,S,W -ignore = E203,E501,RST201,RST203,RST301,W503,S601,S404 +ignore = E203,E501,RST201,RST203,RST301,W503,S601,S404,B907 max-line-length = 120 max-complexity = 10 docstring-convention = google diff --git a/README.md b/README.md index 1c89afe..b955456 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ - Execute a command for PowerShell, command-line (cmd) and bash/sh on any of the above systems - Can execute commands elevated on the supported systems - Returns a standard response object, as well as displays a formatter version to the console via logging +- Copy a file from a local to remote host ### Response Object diff --git a/src/atomic_operator_runner/remote.py b/src/atomic_operator_runner/remote.py index d84983b..ed8b527 100644 --- a/src/atomic_operator_runner/remote.py +++ b/src/atomic_operator_runner/remote.py @@ -18,7 +18,9 @@ class RemoteRunner(Base): """Used to run command remotely.""" - def _copy_file_to_windows(self, source: str, desintation: str, executor: str, elevation_required: bool = False) -> bool: + def _copy_file_to_windows( + self, source: str, desintation: str, executor: str, elevation_required: bool = False + ) -> bool: """Copies files on Windows using PowerShell remoting (only). Args: @@ -34,17 +36,17 @@ def _copy_file_to_windows(self, source: str, desintation: str, executor: str, el if executor == "powershell": command = f"New-Item -Path {os.path.dirname(desintation)} -ItemType Directory" if elevation_required: - command = f'Start-Process PowerShell -Verb RunAs; {command}' + command = f"Start-Process PowerShell -Verb RunAs; {command}" output, streams, had_errors = self._get_pypsrp_client().execute_ps(command) # saving the output from the execution to our RunnerResponse object if isinstance(had_errors, bool): had_errors = 0 if had_errors is False else 1 Processor(command=command, executor=executor, return_code=had_errors, output=output, errors=streams) - response = self._get_pypsrp_client().copy(source, desintation) + self._get_pypsrp_client().copy(source, desintation) return True - except: - self.__logger.warning(f'Unable to execute copy of supporting file {source}') - self.__logger.warning(f'Output: {output}/nStreams: {streams}/nHad Errors: {had_errors}') + except Exception as e: + self.__logger.warning(f"Unable to execute copy of supporting file {source}") + self.__logger.warning(f"Output: {output}/nStreams: {streams}/nHad Errors: {had_errors}. {e}") return False def _copy_file_to_nix(self, source: str, destination: str, elevation_required: bool = False) -> bool: @@ -52,24 +54,24 @@ def _copy_file_to_nix(self, source: str, destination: str, elevation_required: b Args: source (str): The source file to copy to the remote host. - desintation (str): The destination location on the remote host to copy the file. + destination (str): The destination location on the remote host to copy the file. elevation_required (bool, optional): Whether or not elevation is required. Defaults to False. Returns: bool: Returns True if successful and False is not. """ atexit.register(self._close_paramiko_client) - file = destination.rsplit('/', 1) + file = destination.rsplit("/", 1) try: command = "sh -c '" + f'file="{destination}"' + ' && mkdir -p "${file%/*}" && cat > "${file}"' + "'" if elevation_required: - command = f'sudo {command}' + command = f"sudo {command}" ssh_stdin, ssh_stdout, ssh_stderr = self._get_paramiko_client().exec_command(command) - ssh_stdin.write(open(f'{source}', 'r').read()) + ssh_stdin.write(open(f"{source}").read()) return True - except: - self.__logger.warning(f'Unable to execute copy of supporting file {file[-1]}') - self.__logger.warning(f'STDIN: {ssh_stdin}/nSTDOUT: {ssh_stdout}/nSTDERR: {ssh_stderr}') + except Exception as e: + self.__logger.warning(f"Unable to execute copy of supporting file {file[-1]}") + self.__logger.warning(f"STDIN: {ssh_stdin}/nSTDOUT: {ssh_stdout}/nSTDERR: {ssh_stderr}. {e}") return False def _get_paramiko_client(self) -> SSHClient: diff --git a/src/atomic_operator_runner/runner.py b/src/atomic_operator_runner/runner.py index 13294d5..3b1b0a4 100644 --- a/src/atomic_operator_runner/runner.py +++ b/src/atomic_operator_runner/runner.py @@ -128,7 +128,9 @@ def run( self.responses.append(self.response) return [x.json() for x in self.responses] - def copy_file(self, source_file: str, destination_replacement_path: str, executor: str, elevation_required: bool = False) -> None: + def copy_file( + self, source_file: str, destination_replacement_path: str, executor: str, elevation_required: bool = False + ) -> None: """Copies the provided single file to the provided destination path. Args: @@ -136,6 +138,10 @@ def copy_file(self, source_file: str, destination_replacement_path: str, executo destination_replacement_path (str): The folder path on the destination system to place the provided file to. executor (str): The executor to use when running the provided command. elevation_required (bool, optional): Whether or not elevation is required. Defaults to False. + + Raises: + SourceFileNotFoundError: Raised when the provided source file cannot be found or loaded. + SourceFileNotSupportedError: Raised when a provided file is not a supported file type. """ if not os.path.exists(source_file): raise SourceFileNotFoundError(source_file=source_file) @@ -143,7 +149,7 @@ def copy_file(self, source_file: str, destination_replacement_path: str, executo raise SourceFileNotSupportedError(source_file=source_file) if Base.config.run_type == "remote": from .remote import RemoteRunner - + self.log(val=f"Attempting to copy file '{source_file}' to remote host.") Base.response = RunnerResponse( start_timestamp=datetime.now(), @@ -155,24 +161,26 @@ def copy_file(self, source_file: str, destination_replacement_path: str, executo ) if self.config.platform == "windows": response = RemoteRunner()._copy_file_to_windows( - source=source_file, - desintation=destination_replacement_path, + source=source_file, + desintation=destination_replacement_path, executor=executor, - elevation_required=elevation_required + elevation_required=elevation_required, ) if response: self.log(val=f"Successfully transferred file '{source_file}' to remote windows host.") else: - self.log(val=f"Error occurred trying to transfer file '{source_file}' to remote host!", level="critical") + self.log( + val=f"Error occurred trying to transfer file '{source_file}' to remote host!", level="critical" + ) elif self.config.platform == "macos" or self.config.platform == "linux": response = RemoteRunner()._copy_file_to_nix( - source=source_file, - destination=destination_replacement_path, - elevation_required=elevation_required + source=source_file, destination=destination_replacement_path, elevation_required=elevation_required ) if response: self.log(val=f"Successfully transferred file '{source_file}' to remote nix host.") else: - self.log(val=f"Error occurred trying to transfer file '{source_file}' to remote host!", level="critical") + self.log( + val=f"Error occurred trying to transfer file '{source_file}' to remote host!", level="critical" + ) else: self.log(val="We only support copying of files on remote systems.", level="warning")