2011-01-06 18 views
12

私は今、いくつかのボックスで実行中のプロセスを監視する必要があるねじれたアプリケーションを持っています。私が手動でやる方法は 'sshとps'です。今は私のツイストされたアプリケーションにしたいのですが。私には2つの選択肢があります。Twistedでsshを使ってリモートコマンドを実行する最も良い方法は?

使用paramikoまたはtwisted.conch

の力を活用し、私は本当にtwisted.conchを使用したいが、私の研究は、そのは、主にSSHServersとSSHClientsを作成することを目的と信じるように私を導きました。しかし、私の要件は、単純なremoteExecute(some_cmd)

である私はparamikoを使用して、これを行う方法を見つけ出すことができましたが、私は使用してtwisted.conch

コードスニペットを使用してこれを行う方法を見る前に、私のねじれたアプリでparamikoを固執したいdidntはtwistedを実行する方法remote_cmds sshを使用していただければ幸いです。ありがとう。

答えて

16

フォローアップ - 幸い、下記で参照しているチケットが解決されました。より簡単な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が必要です。これはパラメータになります。
  • これはおそらくキー認証でのみ機能します。パスワード認証を有効にする場合は、サブクラスSSHUserAuthClientgetPasswordを上書きする必要があります。
  • ほとんどすべてのSSHとコンクの層は、ここに表示されている:
      _CommandTransport
    • は、底部、SSHトランスポート・プロトコルを実装する昔ながらのプロトコルです。 ...
    • _CommandConnectionこのプロトコルのSSH接続ネゴシエーション部分を実装しています。これが完了すると、...
    • _CommandChannelは、新しく開かれたSSHチャネルと通信するために使用されます。 _CommandChannel実際のexecを実行してコマンドを起動します。チャンネルが開かれると、そのインスタンスが作成されます...
    • StdoutEcho、またはあなたが提供するその他のプロトコル。このプロトコルは、実行したコマンドの出力を取得し、コマンドのstdinに書き込むことができます。

少ないコードでこれを支援する上でツイストの進歩のためのhttp://twistedmatrix.com/trac/ticket/4698を参照してください。

+0

ありがとうございますexarkun!あなたが正しく言及したように、この些細なことの簡単ですぐに使える解決策があるはずなので、私は本当に奇妙です。うーん、すでにその方向で働いていることを嬉しく思います。迅速な対応に感謝します。 –

関連する問題