32
from bzrlib.errors import (FileExists,
32
from bzrlib.config import config_dir, ensure_config_dir_exists
33
from bzrlib.errors import (ConnectionError,
33
35
TransportNotPossible, NoSuchFile, PathNotChild,
36
from bzrlib.config import config_dir, ensure_config_dir_exists
37
LockError, ParamikoNotPresent
39
from bzrlib.osutils import pathjoin, fancy_rename
37
40
from bzrlib.trace import mutter, warning, error
38
from bzrlib.transport import Transport, register_transport
39
from bzrlib.osutils import pathjoin, fancy_rename
41
from bzrlib.transport import Transport, Server, urlescape
45
error('The SFTP transport requires paramiko.')
46
except ImportError, e:
47
raise ParamikoNotPresent(e)
48
49
from paramiko.sftp import (SFTP_FLAG_WRITE, SFTP_FLAG_CREATE,
49
50
SFTP_FLAG_EXCL, SFTP_FLAG_TRUNC,
463
path = self._abspath(relpath)
502
path = self._remote_path(relpath)
464
503
fout = self._sftp.file(path, 'ab')
465
504
self._pump(f, fout)
466
505
except (IOError, paramiko.SSHException), e:
467
506
self._translate_io_exception(e, relpath, ': unable to append')
469
def copy(self, rel_from, rel_to):
470
"""Copy the item at rel_from to the location at rel_to"""
471
path_from = self._abspath(rel_from)
472
path_to = self._abspath(rel_to)
473
self._copy_abspaths(path_from, path_to)
475
def _copy_abspaths(self, path_from, path_to, mode=None):
476
"""Copy files given an absolute path
478
:param path_from: Path on remote server to read
479
:param path_to: Path on remote server to write
482
TODO: Should the destination location be atomically created?
483
This has not been specified
484
TODO: This should use some sort of remote copy, rather than
485
pulling the data locally, and then writing it remotely
488
fin = self._sftp.file(path_from, 'rb')
490
self._put(path_to, fin, mode=mode)
493
except (IOError, paramiko.SSHException), e:
494
self._translate_io_exception(e, path_from, ': unable copy to: %r' % path_to)
496
def copy_to(self, relpaths, other, mode=None, pb=None):
497
"""Copy a set of entries from self into another Transport.
499
:param relpaths: A list/generator of entries to be copied.
501
if isinstance(other, SFTPTransport) and other._sftp is self._sftp:
502
# Both from & to are on the same remote filesystem
503
# We can use a remote copy, instead of pulling locally, and pushing
505
total = self._get_total(relpaths)
507
for path in relpaths:
508
path_from = self._abspath(relpath)
509
path_to = other._abspath(relpath)
510
self._update_pb(pb, 'copy-to', count, total)
511
self._copy_abspaths(path_from, path_to, mode=mode)
515
return super(SFTPTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
517
508
def _rename(self, abs_from, abs_to):
518
509
"""Do a fancy rename on the remote server.
549
540
Return a list of all files at the given location.
551
542
# does anything actually use this?
552
path = self._abspath(relpath)
543
path = self._remote_path(relpath)
554
545
return self._sftp.listdir(path)
555
546
except (IOError, paramiko.SSHException), e:
556
547
self._translate_io_exception(e, path, ': failed to list_dir')
549
def rmdir(self, relpath):
550
"""See Transport.rmdir."""
551
path = self._remote_path(relpath)
553
return self._sftp.rmdir(path)
554
except (IOError, paramiko.SSHException), e:
555
self._translate_io_exception(e, path, ': failed to rmdir')
558
557
def stat(self, relpath):
559
558
"""Return the stat information for a file."""
560
path = self._abspath(relpath)
559
path = self._remote_path(relpath)
562
561
return self._sftp.stat(path)
563
562
except (IOError, paramiko.SSHException), e:
817
823
self._translate_io_exception(e, abspath, ': unable to open',
818
824
failure_exc=FileExists)
827
# ------------- server test implementation --------------
831
from bzrlib.tests.stub_sftp import StubServer, StubSFTPServer
833
STUB_SERVER_KEY = """
834
-----BEGIN RSA PRIVATE KEY-----
835
MIICWgIBAAKBgQDTj1bqB4WmayWNPB+8jVSYpZYk80Ujvj680pOTh2bORBjbIAyz
836
oWGW+GUjzKxTiiPvVmxFgx5wdsFvF03v34lEVVhMpouqPAYQ15N37K/ir5XY+9m/
837
d8ufMCkjeXsQkKqFbAlQcnWMCRnOoPHS3I4vi6hmnDDeeYTSRvfLbW0fhwIBIwKB
838
gBIiOqZYaoqbeD9OS9z2K9KR2atlTxGxOJPXiP4ESqP3NVScWNwyZ3NXHpyrJLa0
839
EbVtzsQhLn6rF+TzXnOlcipFvjsem3iYzCpuChfGQ6SovTcOjHV9z+hnpXvQ/fon
840
soVRZY65wKnF7IAoUwTmJS9opqgrN6kRgCd3DASAMd1bAkEA96SBVWFt/fJBNJ9H
841
tYnBKZGw0VeHOYmVYbvMSstssn8un+pQpUm9vlG/bp7Oxd/m+b9KWEh2xPfv6zqU
842
avNwHwJBANqzGZa/EpzF4J8pGti7oIAPUIDGMtfIcmqNXVMckrmzQ2vTfqtkEZsA
843
4rE1IERRyiJQx6EJsz21wJmGV9WJQ5kCQQDwkS0uXqVdFzgHO6S++tjmjYcxwr3g
844
H0CoFYSgbddOT6miqRskOQF3DZVkJT3kyuBgU2zKygz52ukQZMqxCb1fAkASvuTv
845
qfpH87Qq5kQhNKdbbwbmd2NxlNabazPijWuphGTdW0VfJdWfklyS2Kr+iqrs/5wV
846
HhathJt636Eg7oIjAkA8ht3MQ+XSl9yIJIS8gVpbPxSw5OMfw0PjVE7tBdQruiSc
847
nvuQES5C9BMHjF39LZiGH1iLQy7FgdHyoP+eodI7
848
-----END RSA PRIVATE KEY-----
852
class SingleListener(threading.Thread):
854
def __init__(self, callback):
855
threading.Thread.__init__(self)
856
self._callback = callback
857
self._socket = socket.socket()
858
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
859
self._socket.bind(('localhost', 0))
860
self._socket.listen(1)
861
self.port = self._socket.getsockname()[1]
862
self.stop_event = threading.Event()
865
s, _ = self._socket.accept()
866
# now close the listen socket
869
self._callback(s, self.stop_event)
871
pass #Ignore socket errors
873
# probably a failed test
874
warning('Exception from within unit test server thread: %r' % x)
877
self.stop_event.set()
878
# use a timeout here, because if the test fails, the server thread may
879
# never notice the stop_event.
883
class SFTPServer(Server):
884
"""Common code for SFTP server facilities."""
887
self._original_vendor = None
889
self._server_homedir = None
890
self._listener = None
892
self._vendor = 'none'
896
def _get_sftp_url(self, path):
897
"""Calculate an sftp url to this server for path."""
898
return 'sftp://foo:bar@localhost:%d/%s' % (self._listener.port, path)
900
def log(self, message):
901
"""StubServer uses this to log when a new server is created."""
902
self.logs.append(message)
904
def _run_server(self, s, stop_event):
905
ssh_server = paramiko.Transport(s)
906
key_file = os.path.join(self._homedir, 'test_rsa.key')
907
file(key_file, 'w').write(STUB_SERVER_KEY)
908
host_key = paramiko.RSAKey.from_private_key_file(key_file)
909
ssh_server.add_server_key(host_key)
910
server = StubServer(self)
911
ssh_server.set_subsystem_handler('sftp', paramiko.SFTPServer,
912
StubSFTPServer, root=self._root,
913
home=self._server_homedir)
914
event = threading.Event()
915
ssh_server.start_server(event, server)
917
stop_event.wait(30.0)
921
self._original_vendor = _ssh_vendor
922
_ssh_vendor = self._vendor
923
self._homedir = os.getcwdu()
924
if self._server_homedir is None:
925
self._server_homedir = self._homedir
927
# FIXME WINDOWS: _root should be _server_homedir[0]:/
928
self._listener = SingleListener(self._run_server)
929
self._listener.setDaemon(True)
930
self._listener.start()
933
"""See bzrlib.transport.Server.tearDown."""
935
self._listener.stop()
936
_ssh_vendor = self._original_vendor
939
class SFTPFullAbsoluteServer(SFTPServer):
940
"""A test server for sftp transports, using absolute urls and ssh."""
943
"""See bzrlib.transport.Server.get_url."""
944
return self._get_sftp_url(urlescape(self._homedir[1:]))
947
class SFTPServerWithoutSSH(SFTPServer):
948
"""An SFTP server that uses a simple TCP socket pair rather than SSH."""
951
super(SFTPServerWithoutSSH, self).__init__()
952
self._vendor = 'loopback'
954
def _run_server(self, sock, stop_event):
955
class FakeChannel(object):
956
def get_transport(self):
958
def get_log_channel(self):
962
def get_hexdump(self):
965
server = paramiko.SFTPServer(FakeChannel(), 'sftp', StubServer(self), StubSFTPServer,
966
root=self._root, home=self._server_homedir)
967
server.start_subsystem('sftp', None, sock)
968
server.finish_subsystem()
971
class SFTPAbsoluteServer(SFTPServerWithoutSSH):
972
"""A test server for sftp transports, using absolute urls."""
975
"""See bzrlib.transport.Server.get_url."""
976
return self._get_sftp_url(urlescape(self._homedir[1:]))
979
class SFTPHomeDirServer(SFTPServerWithoutSSH):
980
"""A test server for sftp transports, using homedir relative urls."""
983
"""See bzrlib.transport.Server.get_url."""
984
return self._get_sftp_url("~/")
987
class SFTPSiblingAbsoluteServer(SFTPAbsoluteServer):
988
"""A test servere for sftp transports, using absolute urls to non-home."""
991
self._server_homedir = '/dev/noone/runs/tests/here'
992
super(SFTPSiblingAbsoluteServer, self).setUp()
995
def get_test_permutations():
996
"""Return the permutations to be used in testing."""
997
return [(SFTPTransport, SFTPAbsoluteServer),
998
(SFTPTransport, SFTPHomeDirServer),
999
(SFTPTransport, SFTPSiblingAbsoluteServer),