2011-01-06 10 views
12

Ho un'applicazione twistata che ora deve monitorare i processi in esecuzione su più box. Il mio modo di fare manualmente è 'ssh e ps', ora vorrei che la mia applicazione contorta fosse fatta. Ho 2 opzioni.Il modo migliore per eseguire comandi remoti tramite ssh in Twisted?

Usa paramiko o sfruttare la potenza di twisted.conch

ho voglia di utilizzare twisted.conch ma la mia ricerca mi ha portato a credere che il suo scopo primario di creare SSHServers e SSHClients. Tuttavia la mia richiesta è un semplice remoteExecute(some_cmd)

sono stato in grado di capire come fare questo usando paramiko ma non volevo attaccare paramiko nella mia app contorta prima di guardare a come farlo usando twisted.conch

frammenti di codice che utilizzano twisted su come eseguire remote_cmds utilizzando ssh sarebbe molto apprezzato. Grazie.

risposta

16

Followup - Fortunatamente, il ticket a cui ho fatto riferimento di seguito è ora risolto. L'API più semplice sarà inclusa nella prossima versione di Twisted. La risposta originale è ancora un modo valido per utilizzare Conch e potrebbe rivelare alcuni dettagli interessanti su cosa sta succedendo, ma da Twisted 13.1 e su, se si desidera eseguire un comando e gestire l'I/O, this simpler interface will work.


Richiede una quantità di codice sfortunatamente grande per eseguire un comando su un SSH utilizzando le API del client Conch. Conch ti consente di gestire molti livelli diversi, anche se desideri solo un comportamento predefinito noioso e ragionevole. Tuttavia, è certamente possibile. Ecco alcuni codice che ho in mente per finire e aggiungere Twisted per semplificare questo 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() 

Alcune cose da notare a questo proposito:

  • utilizza le nuove API endpoint introdotte nel ritorto 10.1 . È possibile farlo direttamente su reactor.connectTCP, ma l'ho fatto come endpoint per renderlo più utile; gli endpoint possono essere scambiati facilmente senza il codice che richiede effettivamente una connessione sapendo.
  • Non esegue alcuna verifica della chiave host! _CommandTransport.verifyHostKey è dove lo implementeresti. Dai un'occhiata a twisted/conch/client/default.py per alcuni suggerimenti su quali tipi di cose potresti voler fare.
  • Ci vuole $USER per essere il nome utente remoto, che potrebbe essere un parametro.
  • Probabilmente funziona solo con l'autenticazione della chiave. Se si desidera abilitare l'autenticazione della password, è necessario creare sottoclasse SSHUserAuthClient e sostituire getPassword per fare qualcosa.
  • Quasi tutti gli strati di SSH e conca sono visibili qui:
    • _CommandTransport è in basso, una pianura vecchio protocollo che implementa il protocollo di trasporto SSH. Crea un ...
    • _CommandConnection che implementa le parti di negoziazione della connessione SSH del protocollo. Una volta completato, un ...
    • _CommandChannel viene utilizzato per parlare con un canale SSH appena aperto. _CommandChannel esegue l'exec effettivo per avviare il comando. Una volta aperto il canale, crea un'istanza di ...
    • StdoutEcho, o qualunque altro protocollo si fornisce. Questo protocollo otterrà l'output dal comando che si esegue e può scrivere sullo stdin del comando.

Vedi http://twistedmatrix.com/trac/ticket/4698 progressi in ritorto a sostenere questo con meno codice.

+0

Grazie exarkun sacco! Mi è sembrato davvero strano perché, come hai giustamente menzionato, dovrebbe esserci una soluzione semplice e immediata di questa cosa banale. Sono contento che ci sia già un lavoro in quella direzione. Grazie ancora per la pronta risposta. –

Problemi correlati