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.
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. –