2011-01-06 110 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