forked from datalad/datalad-next
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
These have been developed months ago, and fix critical aspects of SSH execution on windows clients that prevent any RIA functionality to be working on windows. Given the uncertainty of a datalad-ria release, I am importing them here. Hopefully fixes datalad#654
- Loading branch information
Showing
4 changed files
with
400 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
"""Enable SSH-based remote command execution on Windows | ||
This change introduces a replacement for core's | ||
``datalad/support/sshconnector.py:BaseSSHConnection._exec_ssh()`` | ||
with a dedicated handling of ``stdin`` for Windows. | ||
The OpenSSH client in Windows modifies its ``stdin``-descriptor in such a way, that it becomes unusable for the python process, if the ``stdin``-descriptor is shared between the ``python``-process and the ``ssh``-process. As a result, all read-operations that the ``python``-process performs on ``stdin`` will block and leave the python-process "hanging". | ||
This change passes an explicit, empty, byte-string as ``stdin`` to the | ||
SSH client call, in order to avoid any interaction of SSH with the | ||
parent process's ``stdin`` descriptor. | ||
""" | ||
|
||
import logging | ||
|
||
from datalad.support.sshconnector import ( | ||
StdOutErrCapture, | ||
NoCapture, | ||
) | ||
|
||
from datalad_next.patches import apply_patch | ||
from datalad_next.utils import on_windows | ||
|
||
# use same logger as -core | ||
lgr = logging.getLogger('datalad.support.sshconnector') | ||
|
||
|
||
# This method interface/original implementation is taken from | ||
# datalad-core@58b8e06317fe1a03290aed80526bff1e2d5b7797 | ||
# datalad/support/sshconnector.py:BaseSSHConnection | ||
def _exec_ssh(self, ssh_cmd, cmd, options=None, stdin=None, log_output=True): | ||
cmd = self._adjust_cmd_for_bundle_execution(cmd) | ||
|
||
for opt in options or []: | ||
ssh_cmd.extend(["-o", opt]) | ||
|
||
# THIS IS THE PATCH | ||
if on_windows and stdin is None: | ||
# SSH on windows requires a special stdin handling. If we'd let | ||
# stdin=None do its normal thing, the Python process would hang, | ||
# because it looses touch with its own file descriptor. | ||
# See https://github.com/datalad/datalad-ria/issues/68 | ||
stdin = b'' | ||
|
||
# build SSH call, feed remote command as a single last argument | ||
# whatever it contains will go to the remote machine for execution | ||
# we cannot perform any sort of escaping, because it will limit | ||
# what we can do on the remote, e.g. concatenate commands with '&&' | ||
ssh_cmd += [self.sshri.as_str()] + [cmd] | ||
|
||
lgr.debug("%s is used to run %s", self, ssh_cmd) | ||
|
||
# TODO: pass expect parameters from above? | ||
# Hard to explain to toplevel users ... So for now, just set True | ||
out = self.runner.run( | ||
ssh_cmd, | ||
protocol=StdOutErrCapture if log_output else NoCapture, | ||
stdin=stdin) | ||
return out['stdout'], out['stderr'] | ||
|
||
|
||
apply_patch( | ||
'datalad.support.sshconnector', 'BaseSSHConnection', '_exec_ssh', | ||
_exec_ssh, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
"""Provide proper arguments for scp-command calls in `SSHConnection` | ||
The original code has errors in the methods ``BaseSSHConnection.put`` | ||
``BaseSSHConnection.get``. Both methods use ``self.sshri.hostname`` to | ||
determine the target for an ``scp``-command. They should instead use | ||
``self.sshri.as_str()`` in order to include a user specification into the | ||
target. | ||
The changes in this patch use ``self.sshri.as_str()`` to provide the correct | ||
targets for ``scp``-commands. | ||
""" | ||
|
||
import logging | ||
|
||
from datalad.support.sshconnector import ( | ||
StdOutErrCapture, | ||
ensure_list, | ||
) | ||
from datalad_next.patches import apply_patch | ||
|
||
|
||
# use same logger as -core | ||
lgr = logging.getLogger('datalad.support.sshconnector') | ||
|
||
|
||
# The method 'BaseSSHConnection_get' is a patched version of | ||
# 'datalad/support/sshconnector.py:BaseSSHConnection.get' | ||
# from datalad@e0b357d9b8ca5f432638c23c0cb7c373028c8e52 | ||
def BaseSSHConnection_get(self, source, destination, recursive=False, preserve_attrs=False): | ||
"""Copies source file/folder from remote to a local destination. | ||
Note: this method performs escaping of filenames to an extent that | ||
moderately weird ones should work (spaces, quotes, pipes, other | ||
characters with special shell meaning), but more complicated cases | ||
might require appropriate external preprocessing of filenames. | ||
Parameters | ||
---------- | ||
source : str or list | ||
file/folder path(s) to copy from the remote host | ||
destination : str | ||
file/folder path to copy to on the local host | ||
recursive : bool | ||
flag to enable recursive copying of given sources | ||
preserve_attrs : bool | ||
preserve modification times, access times, and modes from the | ||
original file | ||
Returns | ||
------- | ||
str | ||
stdout, stderr of the copy operation. | ||
""" | ||
# make sure we have an open connection, will test if action is needed | ||
# by itself | ||
self.open() | ||
scp_cmd = self._get_scp_command_spec(recursive, preserve_attrs) | ||
# add source filepath(s) to scp command, prefixed with the remote host | ||
# PATCH in the line below: replaces `self.sshri.hostname` with `self.sshri.as_str()` | ||
scp_cmd += ["%s:%s" % (self.sshri.as_str(), self._quote_filename(s)) | ||
for s in ensure_list(source)] | ||
# add destination path | ||
scp_cmd += [destination] | ||
out = self.runner.run(scp_cmd, protocol=StdOutErrCapture) | ||
return out['stdout'], out['stderr'] | ||
|
||
|
||
# The method 'BaseSSHConnection_put' is a patched version of | ||
# 'datalad/support/sshconnector.py:BaseSSHConnection.put' | ||
# from datalad@e0b357d9b8ca5f432638c23c0cb7c373028c8e52 | ||
def BaseSSHConnection_put(self, source, destination, recursive=False, preserve_attrs=False): | ||
"""Copies source file/folder to destination on the remote. | ||
Note: this method performs escaping of filenames to an extent that | ||
moderately weird ones should work (spaces, quotes, pipes, other | ||
characters with special shell meaning), but more complicated cases | ||
might require appropriate external preprocessing of filenames. | ||
Parameters | ||
---------- | ||
source : str or list | ||
file/folder path(s) to copy from on local | ||
destination : str | ||
file/folder path to copy to on remote | ||
recursive : bool | ||
flag to enable recursive copying of given sources | ||
preserve_attrs : bool | ||
preserve modification times, access times, and modes from the | ||
original file | ||
Returns | ||
------- | ||
str | ||
stdout, stderr of the copy operation. | ||
""" | ||
# make sure we have an open connection, will test if action is needed | ||
# by itself | ||
self.open() | ||
scp_cmd = self._get_scp_command_spec(recursive, preserve_attrs) | ||
# add source filepath(s) to scp command | ||
scp_cmd += ensure_list(source) | ||
# add destination path | ||
scp_cmd += ['%s:%s' % ( | ||
# PATCH in the line below: replaces `self.sshri.hostname` with `self.sshri.as_str()` | ||
self.sshri.as_str(), | ||
self._quote_filename(destination), | ||
)] | ||
out = self.runner.run(scp_cmd, protocol=StdOutErrCapture) | ||
return out['stdout'], out['stderr'] | ||
|
||
|
||
apply_patch( | ||
modname='datalad.support.sshconnector', | ||
objname='BaseSSHConnection', | ||
attrname='get', | ||
patch=BaseSSHConnection_get, | ||
) | ||
|
||
apply_patch( | ||
modname='datalad.support.sshconnector', | ||
objname='BaseSSHConnection', | ||
attrname='put', | ||
patch=BaseSSHConnection_put, | ||
) |
Oops, something went wrong.