diff --git a/plumbum/machines/ssh_machine.py b/plumbum/machines/ssh_machine.py index 648b0efbf..f5799a738 100644 --- a/plumbum/machines/ssh_machine.py +++ b/plumbum/machines/ssh_machine.py @@ -1,4 +1,5 @@ import warnings +import ipaddress from plumbum.commands import ProcessExecutionError, shquote from plumbum.lib import IS_WIN32 @@ -32,6 +33,12 @@ def close(self): """Closes(terminates) the tunnel""" self._session.close() +def is_ipv6_address(addr: str) -> bool: + try: + ipaddress.IPv6Address(addr) + return True + except ValueError: + return False class SshMachine(BaseRemoteMachine): """ @@ -109,6 +116,9 @@ def __init__( self._fqhost = f"{user}@{host}" else: self._fqhost = host + # if the host is an IPv6 address, we need to wrap it in brackets for scp command (upload / download functions) + scp_host = f'[{self.host}]' if not self.host.startswith('[') and is_ipv6_address(self.host) else self.host + self.scp_fqhost = f"{user}@{scp_host}" if user else scp_host if port: ssh_args.extend(["-p", str(port)]) scp_args.extend(["-P", str(port)]) @@ -307,7 +317,7 @@ def download(self, src, dst): if IS_WIN32: src = self._translate_drive_letter(src) dst = self._translate_drive_letter(dst) - self._scp_command(f"{self._fqhost}:{shquote(src)}", dst) + self._scp_command(f"{self.scp_fqhost}:{shquote(src)}", dst) def upload(self, src, dst): if isinstance(src, RemotePath): @@ -319,7 +329,7 @@ def upload(self, src, dst): if IS_WIN32: src = self._translate_drive_letter(src) dst = self._translate_drive_letter(dst) - self._scp_command(src, f"{self._fqhost}:{shquote(dst)}") + self._scp_command(src, f"{self.scp_fqhost}:{shquote(dst)}") class PuttyMachine(SshMachine): diff --git a/pyproject.toml b/pyproject.toml index 7f03dc928..590a354b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ requires = [ "setuptools>=42", "wheel", - "setuptools_scm[toml]>=3.4.3" + "setuptools_scm[toml]<8.0.0" ] build-backend = "setuptools.build_meta"