[Twisted-Python] Re: Running commands (ssh) from a GUI client
paul
paul at subsignal.org
Tue Oct 9 16:18:27 EDT 2007
Paul_S_Johnson at mnb.uscourts.gov schrieb:
> Raul,
>
> This is simply some work-in-progress code, but is basically what you are
> looking for even written for PythonCard. This takes a list of three
> commands and runs them in the order given using deferreds to wait for the
> previous to complete before executing the next.
>
> This took me about forever to get it this far. If you make and significant
> improvements, please share.
I'll take your word and add my 2 cents here:
Running commands is not a problem, the client example provided at
twistedmatrix.com does just that. However, it always uses a new
connection with all the associated overhead.
I've been told you need a Channel for each command but you can reuse the
connection. To make this work you have to decouple the Channel from the
connection setup:
class ClientConnection(connection.SSHConnection):
def __init__(self):
self.started = False
self.runCalled = 0
connection.SSHConnection.__init__(self)
def serviceStarted(self):
# just set a flag
self.started = True
log.msg('ssh connection started')
def runCommand(self, d, command, *args):
"""
the connection needs some time to get up, hence we use callLater
so that the caller doesn't have to bother.
FIXME: remove hardcoded delay, add backlog and break eventually
if self.started never gets true for some reason...
"""
if not self.started:
reactor.callLater(1, self.runCommand, d, command, *args)
else:
self.openChannel(CommandChannel(d, self, command, *args))
the self.openChannel is moved out of serviceStarted() and we have an
independent method runCommmand() we can call as often as we want as long
as the connection is alive (I wonder if the self.started hack can be
avoided...)
The CommandChannel fires(?) the passed in Deferred when the command has
finished:
class CommandChannel(channel.SSHChannel):
name = 'session'
def __init__(self, d, connection, command, *args):
self.command = command
self.args = args
self.d = d
channel.SSHChannel.__init__(self, conn=connection)
def channelOpen(self, data):
args = list(self.args)
args.insert(0, self.command)
d = self.conn.sendRequest(self, 'exec', common.NS(" "
.join(args)), wantReply = 1)
d.addCallback(self._endCommand)
self.catData = ''
def _endCommand(self, ignored):
self.conn.sendEOF(self)
def eofReceived(self):
self.d.callback((self.catData,))
def dataReceived(self, data):
#log.msg('DEBUG, dataReceived: %s' % data)
self.catData += data
The last piece of the puzzle is the factory which holds the connection
specific data and !! a reference to the connection object to call the
runCommand method:
class CommandClientFactory(protocol.ClientFactory):
def __init__(self, host, user, fingerprint, password=None,
ConnClass=ClientConnection,
AuthClass=ClientUserAuth,
TransportClass=ClientTransport):
self.host = host
self.user = user
self.password = password
self.fingerprint = fingerprint
self.TransportClass = TransportClass
self.connection = ConnClass() #<- we have the connection here
self.auth_client = AuthClass(self, self.connection)
def runCommand(self, deferred, cmd, *args):
self.connection.runCommand(deferred, cmd, *args)
def buildProtocol(self, addr):
p = self.TransportClass()
p.factory = self
return p
def clientConnectionFailed(self, connection, reason):
print "connect to %s as %s failed, reason: %s" % (
self.host, self.user, reason)
def clientConnectionLost(self, connection, reason):
print 'connection to "%s" as "%s" lost, reason: %s' % (
self.host, self.user, reason)
Now you setup a CommandClientFactory, create a new Deferred, a callback
function and put it all together:
def cmdCallback(result):
print result
cf = CommandClientFactory(host, user, fingerprint, password=None)
reactor.connectTCP(host, self.port, cf)
d = defer.Deferred()
d.addCallback(cmdCallback)
cf.connection.runCommand(d, command, *args)
WRT the su - problem, I'll probably use sudo (to fetch the sudoers file ;))
hth
Paul
BTW: This is not working code, it's just to show the basic schema...
More information about the Twisted-Python
mailing list