後續行動 - 令人高興的是,下面引用的票現在已經解決。更簡單的API將包含在Twisted的下一個版本中。最初的答案仍然是使用Conch的有效方式,並且可能會揭示一些有關正在發生的事情的有趣細節,但是從Twisted 13.1開始,如果您只是想運行命令並處理它的I/O,則可以使用this simpler interface will work。
需要一個不幸的大量代碼來使用Conch客戶端API在SSH上執行命令。海螺讓你處理很多不同的層次,即使你只是想要明智的無聊默認行爲。但是,這當然是可能的。下面是一些代碼,我一直想完成,並加入到扭簡化這種情況下:
import sys, os
from zope.interface import implements
from twisted.python.failure import Failure
from twisted.python.log import err
from twisted.internet.error import ConnectionDone
from twisted.internet.defer import Deferred, succeed, setDebugging
from twisted.internet.interfaces import IStreamClientEndpoint
from twisted.internet.protocol import Factory, Protocol
from twisted.conch.ssh.common import NS
from twisted.conch.ssh.channel import SSHChannel
from twisted.conch.ssh.transport import SSHClientTransport
from twisted.conch.ssh.connection import SSHConnection
from twisted.conch.client.default import SSHUserAuthClient
from twisted.conch.client.options import ConchOptions
# setDebugging(True)
class _CommandTransport(SSHClientTransport):
_secured = False
def verifyHostKey(self, hostKey, fingerprint):
return succeed(True)
def connectionSecure(self):
self._secured = True
command = _CommandConnection(
self.factory.command,
self.factory.commandProtocolFactory,
self.factory.commandConnected)
userauth = SSHUserAuthClient(
os.environ['USER'], ConchOptions(), command)
self.requestService(userauth)
def connectionLost(self, reason):
if not self._secured:
self.factory.commandConnected.errback(reason)
class _CommandConnection(SSHConnection):
def __init__(self, command, protocolFactory, commandConnected):
SSHConnection.__init__(self)
self._command = command
self._protocolFactory = protocolFactory
self._commandConnected = commandConnected
def serviceStarted(self):
channel = _CommandChannel(
self._command, self._protocolFactory, self._commandConnected)
self.openChannel(channel)
class _CommandChannel(SSHChannel):
name = 'session'
def __init__(self, command, protocolFactory, commandConnected):
SSHChannel.__init__(self)
self._command = command
self._protocolFactory = protocolFactory
self._commandConnected = commandConnected
def openFailed(self, reason):
self._commandConnected.errback(reason)
def channelOpen(self, ignored):
self.conn.sendRequest(self, 'exec', NS(self._command))
self._protocol = self._protocolFactory.buildProtocol(None)
self._protocol.makeConnection(self)
def dataReceived(self, bytes):
self._protocol.dataReceived(bytes)
def closed(self):
self._protocol.connectionLost(
Failure(ConnectionDone("ssh channel closed")))
class SSHCommandClientEndpoint(object):
implements(IStreamClientEndpoint)
def __init__(self, command, sshServer):
self._command = command
self._sshServer = sshServer
def connect(self, protocolFactory):
factory = Factory()
factory.protocol = _CommandTransport
factory.command = self._command
factory.commandProtocolFactory = protocolFactory
factory.commandConnected = Deferred()
d = self._sshServer.connect(factory)
d.addErrback(factory.commandConnected.errback)
return factory.commandConnected
class StdoutEcho(Protocol):
def dataReceived(self, bytes):
sys.stdout.write(bytes)
sys.stdout.flush()
def connectionLost(self, reason):
self.factory.finished.callback(None)
def copyToStdout(endpoint):
echoFactory = Factory()
echoFactory.protocol = StdoutEcho
echoFactory.finished = Deferred()
d = endpoint.connect(echoFactory)
d.addErrback(echoFactory.finished.errback)
return echoFactory.finished
def main():
from twisted.python.log import startLogging
from twisted.internet import reactor
from twisted.internet.endpoints import TCP4ClientEndpoint
# startLogging(sys.stdout)
sshServer = TCP4ClientEndpoint(reactor, "localhost", 22)
commandEndpoint = SSHCommandClientEndpoint("/bin/ls", sshServer)
d = copyToStdout(commandEndpoint)
d.addErrback(err, "ssh command/copy to stdout failed")
d.addCallback(lambda ignored: reactor.stop())
reactor.run()
if __name__ == '__main__':
main()
有些事情要注意一下:
- 它使用雙絞線10.1引入的新端點的API 。可以直接在
reactor.connectTCP
上執行此操作,但我已將其作爲終端使其更加有用;端點可以很容易地交換,而不需要實際要求連接知道的代碼。
- 它根本沒有主機密鑰驗證!
_CommandTransport.verifyHostKey
是你要實現的地方。看看twisted/conch/client/default.py
瞭解一些你可能想要做什麼事情的提示。
- 需要
$USER
作爲遠程用戶名,您可能希望將其作爲參數。
- 它可能只適用於密鑰認證。如果你想啓用密碼認證,你可能需要子類
SSHUserAuthClient
並覆蓋getPassword
做些什麼。
- 幾乎所有SSH和海螺的層的下面是可見:
_CommandTransport
是在底部,用於實現SSH傳輸協議一個普通的舊協議。它創建一個...
_CommandConnection
它實現協議的SSH連接協商部分。一旦完成,一個...
_CommandChannel
是用來談談一個新打開的SSH通道。 _CommandChannel
確實可以執行你的命令。一旦通道打開,它會創建一個...的實例。
StdoutEcho
,或其他您提供的協議。該協議將從您執行的命令中獲得輸出,並可寫入命令的標準輸入。
見http://twistedmatrix.com/trac/ticket/4698進步在扭曲着用更少的代碼支持這一點。
非常感謝!這讓我感到非常奇怪,因爲正如你剛纔提到的那樣,這件小事應該是簡單的,現成的解決方案。很高興已經有朝這個方向努力的工作。再次感謝您的及時迴應。 – 2011-01-07 20:09:40