フォローアップ - 幸い、下記で参照しているチケットが解決されました。より簡単なAPIは、Twistedの次のリリースに含まれます。元の答えはまだConchを使用する有効な方法であり、何が起こっているかについての興味深い詳細が明らかになるかもしれませんが、Twisted 13.1以降から、コマンドを実行してI/Oを処理したい場合はthis simpler interface will workです。
ConchクライアントAPIを使用してSSHでコマンドを実行するには、残念ながら大量のコードが必要です。 Conchでは、たとえ賢明な退屈なデフォルト動作が必要な場合でも、さまざまなレイヤーを扱うことができます。しかし、確かに可能です。
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
実際のexecを実行してコマンドを起動します。チャンネルが開かれると、そのインスタンスが作成されます...
StdoutEcho
、またはあなたが提供するその他のプロトコル。このプロトコルは、実行したコマンドの出力を取得し、コマンドのstdinに書き込むことができます。
少ないコードでこれを支援する上でツイストの進歩のためのhttp://twistedmatrix.com/trac/ticket/4698を参照してください。
ありがとうございますexarkun!あなたが正しく言及したように、この些細なことの簡単ですぐに使える解決策があるはずなので、私は本当に奇妙です。うーん、すでにその方向で働いていることを嬉しく思います。迅速な対応に感謝します。 –