2011-01-06 148 views
12

我有一個扭曲的應用程序,現在需要監視在幾個盒子上運行的進程。我手動做的方式是'ssh和ps',現在我想要我的扭曲應用程序來做。我有2個選項。運行遠程命令通過ssh扭曲的最佳方式?

使用paramiko或利用的twisted.conch

我真的想用twisted.conch的權力,但我的研究使我相信,它的主要目的是創造SSHServers和SSHClients。但我的要求是一個簡單的remoteExecute(some_cmd)

我能弄清楚如何使用paramiko這樣做,但我沒有想尋找如何做到這一點使用使用twisted.conch

代碼片段之前堅持paramiko在我扭曲的應用程序twisted關於如何運行使用ssh的remote_cmds將不勝感激。謝謝。

回答

16

後續行動 - 令人高興的是,下面引用的票現在已經解決。更簡單的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進步在扭曲着用更少的代碼支持這一點。

+0

非常感謝!這讓我感到非常奇怪,因爲正如你剛纔提到的那樣,這件小事應該是簡單的,現成的解決方案。很高興已經有朝這個方向努力的工作。再次感謝您的及時迴應。 – 2011-01-07 20:09:40