asyncssh
asyncssh copied to clipboard
Reuse session for multiple commands
Tl;Dr I want to run multiple commands under single session so that I can share the state.
Example
sudo su - testaccount
touch test.txt
mv some.txt /tmp
echo "well test" > test2.txt
I don't want to run it as single command, as i want to navigate through every single exit code and output of each command and react accordingly in python (so bash script isn't a solution).
What I'm struggling to achieve is to enter new shell after sudo su
. I understood that run()
coroutine won't work for me, i see that create_process()
is shutting down the channel after wait()
of any first command, so i can't get the exit code while still reside in same session. Last option is create_session()
which is leaves me with manual parsing of all outputs what defeats entire purpose of using this library in the first place (yes i saw #344 already).
Am I doing it wrong? Why isn't there interface around sessions that allows per command handling within same session? Paramiko has it's ways around it, I just wish asyncssh also did.
I'm always open to suggestions for new capabilities in AsyncSSH, so if there's something that Paramiko does to help with this situation, I'd be happy to hear about it. Some of what you describe here is really a limitation of the way SSH works, though, and is likely to be a problem for all SSH clients.
No matter which API you use, a fundamental problem is that you can only run one remote process per SSH session, and get back only one exit code from that process. That process can be an interactive shell, and that will let you run multiple commands, but really you are just providing input to the shell (or to whatever processes you ask the shell to start), and any exit code returned by those commands will get returned to the shell, and not to your SSH client. You'd have to run a command such as "echo $?" to ask the shell to output the exit code of the previous command if you wanted to see what that was after running each command.
The other problem is that the SSH client has no way to know when a command run by the shell finishes, or know what output was generated by the command vs. output generated by the shell. So, unless you know something about the expected output, it can be very hard to know when a command is done and your next input will be sent to the shell instead of to the command you asked the shell to run. You'd need commands to output something unique you can look for in the output stream, or otherwise know the output well enough to know when to stop reading and send the next command to run.
Regarding create_process vs. create_session, anything you can do with create_session is also available via create_process (using its stdin, stdout, and stderr members). So, if you want to do direct read() and write() calls, you can still use a process. You just can't use wait() if you are starting an interactive shell, as that will wait for the entire shell to exit, not individual commands you ask the shell to run.
I can see how doing this in a bash script or otherwise trying to collapse this down to a single command you can pass to something like run()
would be difficult here if your first step is to run "sudo su" to get a shell running as another user. However, if you want to drive an interactive shell (either directly or after sudo), you have to play by the rules of UNIX shells, which don't tell you when command output starts and ends. This would be just as much of an issue trying to do this on a local machine with something like the subprocess module as it is over SSH.
I've tried to dig down the rabbit hole and you're correct in everything said. Its just a fundamental problem (or simply by design?) of SSH itself. Ignore my words about Paramiko, I've attempted to replicate scenario in Paramiko but wasn't successful as I was in the past experience (perhaps I did it without jumping to new shell which is most painful part to deal with). Sadly, in my use case I don't have a way to invoke command directly without entering new shell.
So, unless you know something about the expected output, it can be very hard to know when a command is done and your next input will be sent to the shell instead of to the command you asked the shell to run. You'd need commands to output something unique you can look for in the output stream, or otherwise know the output well enough to know when to stop reading and send the next command to run.
If I can't control the output and it doesn't have any kind unique characters to show that its done, is there anything else that can be done?
I've tried to dig down the rabbit hole and you're correct in everything said. Its just a fundamental problem (or simply by design?) of SSH itself.
SSH attempts to address this problem by allowing you to create multiple sessions on a single connection, each of which can run a single command and perform input & output operations on that, with a clear notification when the command terminates (or when stdin/stdout/stderr are closed or an exit status is set). However, as you correctly pointed out, there are cases where you need to run a shell, either to switch users or to set environment state based on output from one command to use in the execution of others. Once a shell gets involved, you lose visibility on when commands end or what the output boundaries are. This isn't really the fault of SSH, though -- the same applies when running subprocesses on a local machine once a shell is involved.
If I can't control the output and it doesn't have any kind unique characters to show that its done, is there anything else that can be done?
You could try to use heuristics to guess when a command ends, by doing something like looking at when there's a time delay after some output is generated and the tail end of that looks like it could be a shell prompt. The idea would be to wait long enough that any output not from the shell which included that prompt would generate more output before the timer had a chance to go off. Like the prompts, though, you'd need to adjust the timeout here based on knowing something about the commands you want to run, and when they might output things that look like a prompt and not immediately follow that with other output.
In cases where you are starting a shell over SSH, you could potentially set an environment variable like PS1 to some unique string which the shell outputs as a prompt after every command. By setting this even before the shell is started, you might even be able to separate out the login banner text from the first command output. However, I think OpenSSH servers block most environment variables by default. So, you'd have to get the target machine to allow the prompt to be changed (see the AcceptEnv option in sshd_config).
Closing due to inactivity. Feel free to reopen this or open a new issue if you need anything else.