paramiko_vendor.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. # paramiko_vendor.py -- paramiko implementation of the SSHVendor interface
  2. # Copyright (C) 2013 Aaron O'Mullan <aaron.omullan@friendco.de>
  3. #
  4. # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
  5. # General Public License as public by the Free Software Foundation; version 2.0
  6. # or (at your option) any later version. You can redistribute it and/or
  7. # modify it under the terms of either of these two licenses.
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. #
  15. # You should have received a copy of the licenses; if not, see
  16. # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
  17. # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
  18. # License, Version 2.0.
  19. #
  20. """Paramiko SSH support for Dulwich.
  21. To use this implementation as the SSH implementation in Dulwich, override
  22. the dulwich.client.get_ssh_vendor attribute:
  23. >>> from dulwich import client as _mod_client
  24. >>> from dulwich.contrib.paramiko_vendor import ParamikoSSHVendor
  25. >>> _mod_client.get_ssh_vendor = ParamikoSSHVendor
  26. This implementation is experimental and does not have any tests.
  27. """
  28. import paramiko
  29. import paramiko.client
  30. import threading
  31. class _ParamikoWrapper(object):
  32. STDERR_READ_N = 2048 # 2k
  33. def __init__(self, client, channel, progress_stderr=None):
  34. self.client = client
  35. self.channel = channel
  36. self.progress_stderr = progress_stderr
  37. self.should_monitor = bool(progress_stderr) or True
  38. self.monitor_thread = None
  39. self.stderr = b''
  40. # Channel must block
  41. self.channel.setblocking(True)
  42. # Start
  43. if self.should_monitor:
  44. self.monitor_thread = threading.Thread(
  45. target=self.monitor_stderr)
  46. self.monitor_thread.start()
  47. def monitor_stderr(self):
  48. while self.should_monitor:
  49. # Block and read
  50. data = self.read_stderr(self.STDERR_READ_N)
  51. # Socket closed
  52. if not data:
  53. self.should_monitor = False
  54. break
  55. # Emit data
  56. if self.progress_stderr:
  57. self.progress_stderr(data)
  58. # Append to buffer
  59. self.stderr += data
  60. def stop_monitoring(self):
  61. # Stop StdErr thread
  62. if self.should_monitor:
  63. self.should_monitor = False
  64. self.monitor_thread.join()
  65. # Get left over data
  66. data = self.channel.in_stderr_buffer.empty()
  67. self.stderr += data
  68. def can_read(self):
  69. return self.channel.recv_ready()
  70. def write(self, data):
  71. return self.channel.sendall(data)
  72. def read_stderr(self, n):
  73. return self.channel.recv_stderr(n)
  74. def read(self, n=None):
  75. data = self.channel.recv(n)
  76. data_len = len(data)
  77. # Closed socket
  78. if not data:
  79. return b''
  80. # Read more if needed
  81. if n and data_len < n:
  82. diff_len = n - data_len
  83. return data + self.read(diff_len)
  84. return data
  85. def close(self):
  86. self.channel.close()
  87. self.stop_monitoring()
  88. class ParamikoSSHVendor(object):
  89. # http://docs.paramiko.org/en/2.4/api/client.html
  90. def __init__(self, **kwargs):
  91. self.kwargs = kwargs
  92. def run_command(self, host, command,
  93. username=None, port=None,
  94. progress_stderr=None,
  95. password=None, pkey=None,
  96. key_filename=None, **kwargs):
  97. client = paramiko.SSHClient()
  98. connection_kwargs = {'hostname': host}
  99. connection_kwargs.update(self.kwargs)
  100. if username:
  101. connection_kwargs['username'] = username
  102. if port:
  103. connection_kwargs['port'] = port
  104. if password:
  105. connection_kwargs['password'] = password
  106. if pkey:
  107. connection_kwargs['pkey'] = pkey
  108. if key_filename:
  109. connection_kwargs['key_filename'] = key_filename
  110. connection_kwargs.update(kwargs)
  111. policy = paramiko.client.MissingHostKeyPolicy()
  112. client.set_missing_host_key_policy(policy)
  113. client.connect(**connection_kwargs)
  114. # Open SSH session
  115. channel = client.get_transport().open_session()
  116. # Run commands
  117. channel.exec_command(command)
  118. return _ParamikoWrapper(
  119. client, channel, progress_stderr=progress_stderr)