asyncssh-client-invoke_shell

1,509 views
Skip to first unread message

adria...@gmail.com

unread,
Jul 22, 2016, 3:06:18 AM7/22/16
to asyncssh-users
Hi Ron

I'm learning Python programming and I'm searching for a SSH-Client modul with less CPU and Memory consumption.
I have tried paramiko with "invoke_shell" but im not happy with this solution.

I have use the client in the following way:

1. Creat connection to a Jump host over SSH
2. Use the shell to send commands to create some other session(telnet/ssh/netconf/...) from this server
3. Read and write to the channel to catch some prompts -> Username: / Password.
4. Create some action based on the prompt


Is there also a possible way to use asyncssh-client in the same way an get a vt100/terminal session?

Can I stop some function to send the password automatically if I get a prompt like password: or can I modify this like prompt->action.


thank you in advance¨

Adrian

Ron Frederick

unread,
Jul 22, 2016, 11:21:55 PM7/22/16
to adria...@gmail.com, asyncssh-users
Hi Adrian,

On Jul 22, 2016, at 12:06 AM, adria...@gmail.com wrote:
> I'm learning Python programming and I'm searching for a SSH-Client modul with less CPU and Memory consumption.
> I have tried paramiko with "invoke_shell" but im not happy with this solution.
>
> I have use the client in the following way:
>
> 1. Creat connection to a Jump host over SSH
> 2. Use the shell to send commands to create some other session(telnet/ssh/netconf/...) from this server
> 3. Read and write to the channel to catch some prompts -> Username: / Password.
> 4. Create some action based on the prompt
>
>
> Is there also a possible way to use asyncssh-client in the same way an get a vt100/terminal session?

Definitely. You can either launch an interactive shell where you send the commands you want to run as input, or you can create separate sessions for each command you want to run, avoiding some of the hassle of having to read and skip over the shell prompts and echoing of your input. Either way, if the command itself prompts you for something, you can read that output and react to it by sending back the appropriate input.

If you want the remote session to have a particular terminal type, you can pass that in as well, almost with other information like the terminal size and a set of environment variables to set in the remote processes. The one thing to note is that sessions with a terminal associated with them mix together the output from stdout & stderr, though. If you want to read those separately, you need to use a session with no associated terminal. That’s not an AsyncSSH limitation — it’s just the way SSH servers work, since they generally create a pseudo-tty and those have only a single output stream.


> Can I stop some function to send the password automatically if I get a prompt like password: or can I modify this like prompt->action.

Once a session is started, you can read bytes/characters that are output to it and do whatever you need to do. Generally, this would involve sending back some input (like a password in your example), and then you’d go back to reading more output after that. This will all be driven by the code you write — none of the output (or input) goes to the user running your tool unless you explicitly choose to print it (or ask for input).

For an example of this, check out:

http://asyncssh.readthedocs.io/en/latest/index.html#simple-client-with-input

It launches a calculator program and sends various math calculations as input, reading back the output and printing it. The second block of code there which uses the streams API would probably be the simplest way to code something like this.
--
Ron Frederick
ro...@timeheart.net



adria...@gmail.com

unread,
Aug 23, 2016, 2:43:51 PM8/23/16
to asyncssh-users
Hi Ron

Thanks a lot for your fast feedback.
At this time my plane is to use asyncssh client on tkinter run.loop with the follwing Tasks

1. Create Connection to the host
2. Collect data on the input-stream on asyncssh and rederict this to a received-io.stream
3. Collect data on send-io.stream and send this over ansynssh
----
4. The received-ios.stream wil update a tkinter TEXT widget
5. The send-ios.stream is updated via Key-Event over this widget

How can I solve this problem over tkinter run.loop and over async.run_ever loop in a easy way or do you have a better solution to send Key-Event to asyncssh client and update TEXT widget with the receiving datas from asynssh-client.

Best Regards

Ädu

Ron Frederick

unread,
Aug 23, 2016, 10:15:45 PM8/23/16
to adria...@gmail.com, asyncssh-users
Hi Adrian,

On Aug 23, 2016, at 11:43 AM, adria...@gmail.com wrote:
> Thanks a lot for your fast feedback.
> At this time my plane is to use asyncssh client on tkinter run.loop with the follwing Tasks
>
> 1. Create Connection to the host
> 2. Collect data on the input-stream on asyncssh and rederict this to a received-io.stream
> 3. Collect data on send-io.stream and send this over ansynssh
> ----
> 4. The received-ios.stream wil update a tkinter TEXT widget
> 5. The send-ios.stream is updated via Key-Event over this widget
>
> How can I solve this problem over tkinter run.loop and over async.run_ever loop in a easy way or do you have a better solution to send Key-Event to asyncssh client and update TEXT widget with the receiving datas from asynssh-client.

I’m not all that familiar with Tkinter in Python, but it sounds like the first thing you’ll want to address is getting a single event loop that can support both asyncio events and Tkinter events. In a quick search, I ran across atiotkinter on PyPI that seems like it fits the bill here:

https://pypi.python.org/pypi/aiotkinter

If you set the TkinterEventLoopPolicy as shown in the example there, you should be able to process asyncssh and other asyncio events and Tkinter events at the same time. With that working, you can do what you describe above, where key events from your text widget could be written to an asyncssh channel and output from the channel could be added to the text widget. This could be done using a Tkinter event binding for key events
which called write() on the SSHClientChannel and a custom SSHClientSession class with a data_received() callback which updated the contents of the Text widget.
--
Ron Frederick
ro...@timeheart.net



Ron Frederick

unread,
Aug 23, 2016, 11:49:02 PM8/23/16
to adria...@gmail.com, asyncssh-users
Hi Adrian,

Since it had been many years since I last did anything with Tcl/Tk, I thought it might be fun to play around with Tkinter a bit more tonight. Here’s a quick example I threw together that should do something like what you wanted:

class SSHFrame(tkinter.Frame):
    def __init__(self, master=None):
        super().__init__(master)

        self._text = tkinter.Text(self)

        self._text.pack(expand=True, fill='both')
        self.pack(expand=True, fill='both')

    def _handle_keypress(self, event):
        self._proc.stdin.write(event.char)
        return 'break'

    async def run(self, host, command):
        async with asyncssh.connect(host) as conn:
            self._proc = await conn.create_process(command, term_type='dumb')

            self._text.bind('<KeyPress>', self._handle_keypress)

            while not self._proc.stdout.at_eof():
                output = await self._proc.stdout.read(1024)
                self._text.insert('end', output.replace('\r', ''))
                self._text.see('end')


asyncio.set_event_loop_policy(aiotkinter.TkinterEventLoopPolicy())

app = SSHFrame(tkinter.Tk())
asyncio.get_event_loop().run_until_complete(app.run('localhost', 'bc'))


There are a few rough edges here, such as it not really properly displaying things when you try and hit backspace to edit the line you are typing, but it should give you something to build on. Let me know if you have any questions!

adria...@gmail.com

unread,
Aug 24, 2016, 11:37:10 AM8/24/16
to asyncssh-users
Hi Ron

Thanks a lot for the fast Feedback and Example!!
I have to run this Script on a Window machine and "aiotkinter" is not supported.
Do you have another way to get this working ?

Best Regards

Ädu

Am Freitag, 22. Juli 2016 09:06:18 UTC+2 schrieb adria...@gmail.com:

Ron Frederick

unread,
Aug 24, 2016, 12:05:09 PM8/24/16
to adria...@gmail.com, asyncssh-users
There are some other examples out there, some of which may work on Windows. They generally involve a “sleep” of some kind, though, so they won’t be quite as responsive. You basically have to trade off responsiveness for the higher power usage of waking up frequently. Here’s one example of this:


Note that this uses the older Python 3.4 syntax and not the Python 3.5 syntax I used in my example. It could easily be adapted, though.
-- 
Ron Frederick



Ron Frederick

unread,
Aug 25, 2016, 2:29:48 AM8/25/16
to adria...@gmail.com, asyncssh-users
Hi Adrian,

On Aug 24, 2016, at 9:05 AM, Ron Frederick <ro...@timeheart.net> wrote:
There are some other examples out there, some of which may work on Windows. They generally involve a “sleep” of some kind, though, so they won’t be quite as responsive. You basically have to trade off responsiveness for the higher power usage of waking up frequently. Here’s one example of this:


Note that this uses the older Python 3.4 syntax and not the Python 3.5 syntax I used in my example. It could easily be adapted, though.

Here’s a reworked version of my previous example based on the above that doesn’t depend on aiotkinter:

import asyncio, asyncssh, tkinter

class SSHFrame(tkinter.Frame):
    def __init__(self, master=None):
        super().__init__(master)

        self._text = tkinter.Text(self)

        self._text.pack(expand=True, fill='both')
        self.pack(expand=True, fill='both')

        asyncio.ensure_future(self._event_loop())

    async def _event_loop(self, interval=0.05):
        try:
            while True:
                self.update()
                await asyncio.sleep(interval)
        except tkinter.TclError as exc:
            if "application has been destroyed" in exc.args[0]:
                self._proc.close()
            else:
                raise

    def _handle_keypress(self, event):
        self._proc.stdin.write(event.char)
        return 'break'

    async def run(self, host, command):
        async with asyncssh.connect(host) as conn:
            self._proc = await conn.create_process(command, term_type='dumb')

            self._text.bind('<KeyPress>', self._handle_keypress)

            while not self._proc.stdout.at_eof():
                output = await self._proc.stdout.read(1024)

                if output:
                    self._text.insert('end', output.replace('\r', ''))
                    self._text.see('end')


app = SSHFrame(tkinter.Tk())
asyncio.get_event_loop().run_until_complete(app.run('localhost', 'bc'))
-- 
Ron Frederick



adria...@gmail.com

unread,
Aug 25, 2016, 4:33:56 AM8/25/16
to asyncssh-users
Hi Robert

The Loop works but i have still a problem to get the create_process  working:
I use the following code:

import asyncio, asyncssh, sys

async def run_client():
async with asyncssh.connect('xxxxxx',username='xxxxxx', password='xxxxx', client_keys=None, known_hosts = None ) as conn:
proc1 = await conn.create_process(r'echo "1\n2\n3"')
proc2_result = await conn.run('tail -r', stdin=proc1.stdout)
print(proc2_result.stdout, end='')

try:
asyncio.get_event_loop().run_until_complete(run_client())
except (OSError, asyncssh.Error) as exc:
sys.exit('SSH connection failed: ' + str(exc))


and I get the error:

Z:\Programmieren\Python\WinPython-64bit-3.5.1.3\python-3.5.1.amd64\python.exe Z:/Programmieren/Project/flexterm/ssh_async_process.py
Traceback (most recent call last):
  File "Z:/Programmieren/Project/flexterm/ssh_async_process.py", line 10, in <module>
    asyncio.get_event_loop().run_until_complete(run_client())
  File "Z:\Programmieren\Python\WinPython-64bit-3.5.1.3\python-3.5.1.amd64\lib\asyncio\base_events.py", line 337, in run_until_complete
    return future.result()
  File "Z:\Programmieren\Python\WinPython-64bit-3.5.1.3\python-3.5.1.amd64\lib\asyncio\futures.py", line 274, in result
    raise self._exception
  File "Z:\Programmieren\Python\WinPython-64bit-3.5.1.3\python-3.5.1.amd64\lib\asyncio\tasks.py", line 239, in _step
    result = coro.send(None)
  File "Z:/Programmieren/Project/flexterm/ssh_async_process.py", line 5, in run_client
    proc1 = await conn.create_process(r'echo "1\n2\n3"')
AttributeError: 'SSHClientConnection' object has no attribute 'create_process'
Process finished with exit code 1

Regards

Ädu


Am Freitag, 22. Juli 2016 09:06:18 UTC+2 schrieb adria...@gmail.com:

Ron Frederick

unread,
Aug 25, 2016, 9:58:22 AM8/25/16
to adria...@gmail.com, asyncssh-users
You need version 1.6.0 of asyncssh to use the ‘create_process’ and ‘run’ methods.

-- 
Ron Frederick



adria...@gmail.com

unread,
Aug 26, 2016, 3:36:33 PM8/26/16
to asyncssh-users
Hi Ron

The Script is working, thanks a lot.
I have now integrated some tk-widgets.
Can you tell me how i can start and close the session if I press the connect and disconnect button and the Frame should not be closed.
Should I use a async loop run_forever?

Here my code:

from tkinter import ttk
import tkinter
import asyncio
import asyncssh


class SSHFrame(tkinter.Frame):
def __init__(self, master=None):
super().__init__(master)
        self._text = tkinter.Text(self, height= 60)
self._text.grid(row=0, column=0, sticky='nsew')
self.create_notebook()
asyncio.ensure_future(self._event_loop())

def create_notebook(self):
self.note = ttk.Notebook(self, name='notebook', width=500)
self.tab1 = tkinter.Frame(self.note)
self.tab2 = tkinter.Frame(self.note)
self.tab3 = tkinter.Frame(self.note)
self.tab4 = tkinter.Frame(self.note)
self.note.add(self.tab1, text="SSH")
self.note.add(self.tab2, text="a")
self.note.add(self.tab3, text="b")
self.note.add(self.tab4, text="c")
self.note.grid(row=0, column=1, sticky='n')

tkinter.Label(self.tab1, text="Server-IP:").grid(row=1, column=0, sticky='e')
tkinter.Label(self.tab1, text="User:").grid(row=2, column=0, sticky='e')
tkinter.Label(self.tab1, text="Password:").grid(row=3, column=0, sticky='ne')

self.server_ip = tkinter.Entry(self.tab1)
self.server_user = tkinter.Entry(self.tab1)
self.server_pwd = tkinter.Entry(self.tab1)
self.button_connect = tkinter.Button(self.tab1, text="Connect", fg="red")
self.button_disconnect = tkinter.Button(self.tab1, text="DisConnect", fg="red")

self.server_ip.grid(row=1, column=1, padx=2, pady=2, sticky='we', columnspan=9)
self.server_user.grid(row=2, column=1, padx=2, pady=2, sticky='we', columnspan=9)
self.server_pwd.grid(row=3, column=1, padx=2, pady=2, sticky='we', columnspan=9)
self.button_connect.grid(row=5, column=0, padx=2, pady=6, sticky='')
self.button_disconnect.grid(row=5, column=1, padx=2, pady=2, sticky='')
self.server_ip.insert(10, "server")
self.server_user.insert(10, "user")


async def _event_loop(self, interval=0.05):
try:
while True:
self.update()
await asyncio.sleep(interval)
except tkinter.TclError as exc:
if "application has been destroyed" in exc.args[0]:
self._proc.close()
else:
raise

def _handle_keypress(self, event):
self._proc.stdin.write(event.char)
return 'break'

async def run(self, host, command):
        async with asyncssh.connect(host, username='xxxxxxxxx', password='xxxxxxxxxxx', client_keys=None, known_hosts = None ) as conn:
self._proc = await conn.create_process( term_type='dumb')

self._text.bind('<KeyPress>', self._handle_keypress)
while not self._proc.stdout.at_eof():
output = await self._proc.stdout.read(1024)
if output:
self._text.insert('end', output.replace('\r', ''))
self._text.see('end')

app = SSHFrame(tkinter.Tk())
app.grid(row=1, column=0)
asyncio.get_event_loop().run_until_complete(app.run('localhost'))


Am Freitag, 22. Juli 2016 09:06:18 UTC+2 schrieb adria...@gmail.com:

Ron Frederick

unread,
Aug 27, 2016, 1:53:15 AM8/27/16
to adria...@gmail.com, asyncssh-users
Hi Adrian,

If you want to have multiple SSH connections in different frames, I would suggest moving the event loop code out to a top-level function and making that be the thing that you “run_until_complete”. That way, when you destroy the window, the program will exit.

Then, you can have connect() and disconnect() methods in SSHFrame that open/close the SSH connection, and have button callbacks that call those methods. Here’s an example:


import asyncio, asyncssh, tkinter
from tkinter import ttk

async def _event_loop(app, interval=0.05):
    try:
        while True:
            app.update()
            await asyncio.sleep(interval)
    except tkinter.TclError as exc:
        if "application has been destroyed" not in exc.args[0]:
            raise

class SSHFrame(tkinter.Frame):
    def __init__(self, parent):
        super().__init__(parent)

        self._proc = None

        self._text = tkinter.Text(self)
        self._text.grid(sticky='n,s,e,w')
        self._text.bind('<KeyPress>', self._handle_keypress)

        self.bind('<Destroy>', self._destroy)
        self.grid(sticky='n,s,e,w')

    def _destroy(self, event):
        self._text = None
        self.disconnect()

    def _handle_keypress(self, event):
        if self._proc:
            self._proc.stdin.write(event.char)

        return 'break'

    def _output(self, output):
        if self._text and output:
            self._text.insert('end', output.replace('\r', ''))
            self._text.see('end’)

    async def _run(self, host, command, user, password):
        try:
            self._text.delete('1.0', 'end')

            async with asyncssh.connect(host, username=user, password=password,
                                        client_keys=None) as conn:
                self._proc = await conn.create_process(command,
                                                       term_type='dumb')
                while not self._proc.stdout.at_eof():
                    self._output(await self._proc.stdout.read(1024))

                self._output('\n[Disconnected]\n')
        except (asyncssh.Error, OSError) as exc:
            self._output('[%s]\n' % str(exc))
        finally:
            self._proc = None

    def connect(self, host, command, user, password):
        asyncio.ensure_future(self._run(host, command, user, password))

    def disconnect(self):
        if self._proc:
            self._proc.close()

class App(tkinter.Frame):
    def __init__(self, parent):
        super().__init__(parent)

        self._l_host = tkinter.Label(self, text='Host:', width=10)
        self._l_host.grid(row=0, column=0, sticky='w')

        self._e_host = tkinter.Entry(self)
        self._e_host.grid(row=0, column=1, sticky='e,w')
        self._e_host.insert('1', 'localhost')

        self._l_command = tkinter.Label(self, text='Command:', width=10)
        self._l_command.grid(row=0, column=2, sticky='w')

        self._e_command = tkinter.Entry(self)
        self._e_command.grid(row=0, column=3, sticky='e,w')
        self._e_command.insert('1', 'bc')

        self._l_user = tkinter.Label(self, text='User:', width=10)
        self._l_user.grid(row=1, column=0, sticky='w')

        self._e_user = tkinter.Entry(self)
        self._e_user.grid(row=1, column=1, sticky='e,w')

        self._l_password = tkinter.Label(self, text='Password:', width=10)
        self._l_password.grid(row=1, column=2, sticky='w')

        self._e_password = tkinter.Entry(self, show='*')
        self._e_password.grid(row=1, column=3, sticky='e,w')

        self._b_connect = tkinter.Button(self, text='Connect', width=10,
                                         command=self._connect)
        self._b_connect.grid(row=2, column=0, columnspan=2, sticky='e,w')

        self._b_disconnect = tkinter.Button(self, text='Disconnect', width=10,
                                            command=self._disconnect)
        self._b_disconnect.grid(row=2, column=2, columnspan=2, sticky='e,w')

        self._nb = ttk.Notebook(self)
        self._nb.grid(row=3, sticky='s,e,w', columnspan=4)

        self._tabs = []

        for i in range(1, 5):
            tab = SSHFrame(self._nb)
            self._nb.add(tab, text='Tab %d' % i)
            self._tabs.append(tab)

        self.grid()

    @property
    def _current_tab(self):
        return self._tabs[self._nb.index(self._nb.select())]

    def _connect(self):
        self._current_tab.connect(self._e_host.get(), self._e_command.get(),
                                  self._e_user.get(), self._e_password.get())

    def _disconnect(self):
        self._current_tab.disconnect()

asyncio.get_event_loop().run_until_complete(_event_loop(App(tkinter.Tk())))
-- 
Ron Frederick



Reply all
Reply to author
Forward
0 new messages