2011-01-06 23 views
12

Tengo una aplicación retorcida que ahora necesita supervisar procesos que se ejecutan en varios cuadros. La forma en que hago manualmente es 'ssh y ps', ahora me gustaría que mi retorcida aplicación lo haga. Tengo 2 opciones.¿La mejor manera de ejecutar comandos remotos a través de ssh en Twisted?

Uso paramiko o aprovechar la potencia de twisted.conch

Realmente quiero utilizar twisted.conch pero mi investigación me llevó a creer que su objeto fundamentalmente la creación SSHServers y SSHClients. Sin embargo, mi requisito es un simple remoteExecute(some_cmd)

yo era capaz de encontrar la manera de hacer esto utilizando paramiko pero que no quería pegarse paramiko en mi aplicación torcida antes de buscar la manera de hacer esto utilizando twisted.conch

fragmentos de código que usan twisted sobre cómo ejecutar remote_cmds usando ssh sería muy apreciado. Gracias.

Respuesta

16

Seguimiento - Afortunadamente, ahora se ha resuelto el ticket al que he hecho referencia a continuación. La API más simple se incluirá en la próxima versión de Twisted. La respuesta original sigue siendo una forma válida de utilizar Conch y puede revelar algunos detalles interesantes sobre lo que está sucediendo, pero a partir de Twisted 13.1 y en adelante, si solo desea ejecutar un comando y manejar su E/S, this simpler interface will work.


Se necesita una gran cantidad de código para ejecutar un comando en un SSH utilizando las API del cliente Conch. Conch te hace lidiar con muchas capas diferentes, incluso si solo quieres un comportamiento aburrido sensible por defecto. Sin embargo, es ciertamente posible. Aquí hay algo de código que he tenido la intención de terminar y añadir a Twisted para simplificar este caso:

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() 

Algunas cosas a tener en cuenta al respecto:

  • Utiliza las nuevas API de punto final introducidas en Twisted 10.1 . Es posible hacerlo directamente en reactor.connectTCP, pero lo hice como punto final para hacerlo más útil; Los puntos finales se pueden intercambiar fácilmente sin el código que realmente solicita una conexión.
  • ¡No verifica la clave de host en absoluto! _CommandTransport.verifyHostKey es donde implementaría eso. Eche un vistazo a twisted/conch/client/default.py para obtener algunos consejos sobre qué tipo de cosas puede hacer.
  • Se necesita $USER para ser el nombre de usuario remoto, que es posible que desee ser un parámetro.
  • Probablemente solo funcione con autenticación de clave. Si desea habilitar la autenticación de contraseña, probablemente necesite subclasificar SSHUserAuthClient y anular getPassword para hacer algo.
  • Casi todas las capas de SSH y Conch son visibles aquí:
    • _CommandTransport es en la parte inferior, un protocolo simple y llano que implementa el protocolo de transporte SSH. Crea un ...
    • _CommandConnection que implementa las partes de negociación de conexión SSH del protocolo. Una vez que se completa, un ...
    • _CommandChannel se usa para hablar con un canal SSH recién abierto. _CommandChannel hace el ejecutivo real para ejecutar su comando. Una vez que se abre el canal, crea una instancia de ...
    • StdoutEcho, o cualquier otro protocolo que proporcione. Este protocolo obtendrá el resultado del comando que ejecuta, y puede escribir en el comando stdin del comando.

Ver http://twistedmatrix.com/trac/ticket/4698 para el progreso de trenzado en apoyar esto con menos código.

+0

¡Muchas gracias exarkun! Me llamó la atención realmente porque, como mencionaste acertadamente, debería haber una solución simple y lista para usar de esta cosa trivial. Me alegro de que ya haya trabajo en esa dirección. Gracias de nuevo por la pronta respuesta. –

Cuestiones relacionadas