Drupal developers managing multiple sites may not always remember which site exists on each server. They already have Drush remote aliases that know which site is where, via the remote-host/remote-user options. That's great for running Drush commands on the remote sites, but often they just want to log in to the (or an) appropriate server for the site. So, what they want is:

drush @site ssh

that just runs ssh to the remote-host/remote-user and leaves them at the shell just as if they had run

ssh remote-user-for-site@remote-host-for-site

I started to implement this, thinking it would be easy. No such luck. :-) I now see that the command processing loop starts off like this:

  foreach ($phases as $phase) {
    if (drush_bootstrap_to_phase($phase)) {
      $command = drush_parse_command();

      // Process a remote command if 'remote-host' option is set.
      if (drush_remote_command()) {
        $command_found = TRUE;
        break;
      }

So when I run drush @site ssh, drush tries to run "ssh remote-user@remote-host drush ssh" before my command function even starts running. In theory I could define a very simple command that just exec's (in the Unix "replace the current process" sense) a shell:

function drush_ssh() {
  pcntl_exec("/bin/bash");
}

However, this doesn't work because of the way that remote drush commands are run.

So it seems like a drush command needs a way to say "don't automatically process remote aliases for this command, instead just run my callback function locally and let me deal with it" or else "run the remote alias in a way that is compatible with interactive use" or something.

I'm not familiar enough with Drush internals to do this quickly, but I'm suspecting there is a straightforward approach.

Comments

moshe weitzman’s picture

One approach is to craft a drush command which will output the ssh connect text and then surround that in backticks like `drush site-connect`. This is how sql-connect works. We would alias site-connect to 'ssh'.

greg.1.anderson’s picture

Yes; to build on what moshe said in #1, your command would be drush ssh @site; if you put the alias before the command, then drush will do a remote command invocation on the remote machine, and that is not what you want.

bjaspan’s picture

StatusFileSize
new2.09 KB

Here is a first draft, based on suggestions from Mark Sonnabaum and the point from #2 that "ssh @alias" will work when "@alias ssh" will not.

Actually, Mark suggested "ssh alias" instead of "ssh @alias" based on how drush deploy/sync/other commands work. I have no strong preference. Opinions?

Currently, drush ssh accepts --command="date" (or whatever), which is particularly useful when you give a list of site aliases. It would be nice if I could just run "drush ssh @allsites ls /tmp" (or whatever) without needing the --command or needing to know how many cli params there are. Is there a Drush pattern for this?

PS: This is going to be very exciting when combined with the drush aliases downloadable by Acquia Cloud with the ssh forwarding option -A pre-specified in ssh-options. Image this:

notebook$ drush ssh @mysite.dev
# now I'm on the staging server
staging-1$ drush @mysite.dev status         # works locally
staging-1$ drush @mysite.prod status       # works due to ssh forwarding
staging-1$ drush ssh @mysite.prod           # also uses ssh forwarding
# now I'm on one of my production servers
web-1$ 
greg.1.anderson’s picture

If you want to make drush ssh @allsites ls /tmp work, I might suggest that you instead implement a command ssh-connect, which simply prints out the ssh connection string. Then you could write a simple bash script that calls drush ssh-connect with the backtick operators.

I'm lazy / in a hurry right now, so the example below is merely the idea, and might need some correcting:

#!/bin/bash

$site=$1
shift
ssh `drush ssh-connect $site` "$@"

... if you get my general drift. For extra credit, check to see if $1 begins with an "@", and if it does not, then call through to the regular ssh. If you referenced the regular ssh by full path, then you could name your script "ssh", and do cool things like:

$ ssh @alias ls /tmp

I use some tricks like this in drush core-cli.

bjaspan’s picture

Your idea for using a shell script is a good one, and "ssh @alias" is definitely hot. I've implemented site-connect to make that possible. However, I also want a one-step command people can run without having to install a shell script, too.

This seems to work to let me implement "drush ssh @alias ls /tmp":

function drush_ssh($alias) {
  $args = func_get_args();
  array_shift($args); // $alias
  $command = implode(' ', $args);

Any reason this is a bad idea?

bjaspan’s picture

StatusFileSize
new3.43 KB

I am seriously liking this. I'm no longer convinced site-connect is really necessary but it is included anyway.

drush help ssh
Connect to a Drupal site's server via SSH

Examples:
 drush ssh @mysite                         Open an interactive shell on  
                                           @mysite's server.             
 drush ssh @site1,@site2 ls /tmp           Run "ls /tmp" on @site1's and 
                                           @site2's server.              


Arguments:
 site-alias [ cmd ... ]                    A remote site alias (command is      
                                           optional), or a list of site aliases 
                                           (requires a command)                 


Options:
 --ssh-options                             A string of extra options that will 
                                           be passed to the ssh command (e.g.  
                                           "-p 100")                           

bjaspan’s picture

Title: Allow implementing "drush @site ssh" command » Provide "drush ssh" command
greg.1.anderson’s picture

The problem with #5 is that drush will separate the options (--flag=value) out of the arguments, and there is no way to put them back in with the same order they had when entered by the user.

bjaspan’s picture

I'm unclear why that is a deal-breaker. This still works:

$ drush ssh --verbose @bjaspan.prod 'ls --color'

So, the rule is that you can't provide "-"- or "--"-style options to the command to run via drush ssh unless you enclose them as part of a command string. Not 100% ideal, I suppose, but it works. In any case, the basic ability to open an interactive shell from drush without having to use or install anything else is pretty compelling.

Again, I'm happy to have site-connect as an option, too. Something that occurred to me last night: Can we add the shell script wrapper for drush site-connect to the drush distro, e.g. called dssh? Then the instructions for using it do not have to be "now copy and paste this script into a file somewhere and you get this other cool functionality."

greg.1.anderson’s picture

'ls --color' is fine if you don't mind the extra quotes. If you forget the extra quotes, then unpredictable things happen; you could either lose or reorder the options, depending on how the command is implemented. Since losing or reordering the options to a shell command can drastically alter the behavior of what is executed from what was intended (something runs, but the wrong thing happens), it is my opinion that this requirement (to add quotes around the command when there are flags) makes this implementation too dangerous to commit to drush core. If you did #9 instead of #5 rather than in addition to (that is, only pass the first parameter to ssh), then your command would work just like ssh, and that would be okay, because in this instance, if you forgot the quotes you would only execute the first word of the command, which would usually just print the command help and exit. I hope this explanation is clear, because the difference is subtle, but very important in my view.

We could certainly include a "dssh" script in the drush distro; I think I would put it in the "examples" folder. The other thing I was thinking is that we could also have core-cli define the alias or shell function. On my systems, I like to run drush core-cli --pipe > $HOME/.bashrc, which turns my login shell into a core-cli environment. This would give dssh functionality to anyone who used this trick, and remote commands would be typed the way you expected them to be (just like local commands).

moshe weitzman’s picture

Mark suggested that we should consider adding a shell alias (new in drush5) instead of a dssh script. comments?

greg.1.anderson’s picture

That might have the same problem as a drush command, but I'll take a look. Do shell aliases change the way drush parses commandline args and options at all?

moshe weitzman’s picture

Yeah, it seems that drush shell aliases do not help here.

greg.1.anderson’s picture

Maybe we should have a drush API to get the full original commandline as typed by the user, so commands such as ssh and shell aliases could pass through the user options verbatim. For commands that used this feature, you would have to always put your global drush options (if any) before the command name; otherwise, they would be passed on.

bjaspan’s picture

My original title for this issue was 'Allow implementing a "drush ssh" command' and I was specifically thinking about something along the lines of #14:

So it seems like a drush command needs a way to say "don't automatically process remote aliases for this command, instead just run my callback function locally and let me deal with it" or else "run the remote alias in a way that is compatible with interactive use" or something.

It's the former that I was proposing. However, I no longer really think it is a critical. Command usage of

drush ssh @alias [cmd]

where the optional command can only be a single argument that cannot begin with a hyphen is fine with me.

I've been on vacation for a few days (without my computer! first time in 3 years) but hope to have a new patch for this to propose soon.

webkenny’s picture

Talking about this with bjaspan, it seems like we could also benefit from adding a --bastion argument to this code to handle use cases where a bastion host sits between the caller and the server.

This is born from my wanting to run drush @alias command on servers behind a bastion and this code could hold the key that (even if only in theory for other patches). So, subscribing, as well as seeing about adding that argument to this current patch. Hope to have something to share shortly.

bjaspan’s picture

I agree that a --bastion feature would be useful for drush ssh @alias. It would also be useful for drush @alias command.

However, now that I understand your need, I do not think these two things are related. What you want is for the standard remote alias feature to support a bastion-host option. That's not part of the drush ssh command. It should be a separate issue.

msonnabaum’s picture

Status: Active » Needs review
StatusFileSize
new3.92 KB

Here's a version that uses pcntl if available, so the drush process won't be hanging in the background.

greg.1.anderson’s picture

Status: Needs review » Needs work

Looks cool, but still contains drush_print(implode(" ", $cmd));, which I still think is too dangerous / too unstable per #10 (flags will get stripped out). I think we need to either force the user to quote everything into the first arg, or find some way to get the raw drush options as suggested in #14.

Other than that I think this is really close.

anarcat’s picture

One quick comment here:

+++ b/commands/core/ssh.drush.incundefined
@@ -0,0 +1,110 @@
+      drush_shell_exec("which %s", array_shift($cmd));

this is unnecessary. just run pcntl_exec() on $cmd. if it's not in the path, `which` will fail anyways, so might as well try to run it directly in pcntl_exec(), which will return if it fails.

anyways, as this is now, errors are not handled, so you're not better off. :)

Also, see #1190822: pcntl_exec() support for invoke commands for the feature request of supporting more generic pcntl_exec() calls.

Powered by Dreditor.

bjaspan’s picture

StatusFileSize
new4.59 KB

Here's my latest patch. It supports only a single argument command as per previous discussion. I added --bastion-host, though just today Kenny Silanskis reminded me about ssh's -o ProxyCommand option which makes it unnecessary in the simple cases. I added a couple basic tests. I REMOVED the site-connect command because I realized that

drush ssh @alias --simulate

and


drush site-connect @alias

do exactly the same thing. I guess we could refactor it to have both; I'll do that if it is really important. Note that my original version of site-connect, which is also in Mark's patch, is not right: it does not escape the command line args in the same way.

I left out the pcntl_exec() change. pcntl_exec() takes a command and an array of args (just like Unix execve()), not a command and a string of args, so the current patch isn't right. I'd be happy to use pcntl_exec() to avoid an extra process but I saw that elsewhere Drush uses popen(), so I went with that.

bjaspan’s picture

Status: Needs work » Needs review
moshe weitzman’s picture

Status: Needs review » Needs work

I'd like Greg's OK before this goes in. I saw a few really minor nits.

typo: 'bation-user'

"@alias is not a remote alias.'" - lets say "@alias does not specify a remote-host'"

"Tests for archive.drush.inc" - 'ssh' instead of 'archive'

moshe weitzman’s picture

Assigned: Unassigned » greg.1.anderson
bjaspan’s picture

Status: Needs work » Needs review
StatusFileSize
new4.79 KB

Fixes for #23.

moshe weitzman’s picture

drush_ssh() no longer exists. i think you mean drush_ssh_site_ssh().

the tests fail for me because on osx at least, there are no quotes around various arguments.. for example, here is a failure:

1) siteSshCase::testSiteSsh
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-ssh -A -o PasswordAuthentication=no 'bastion-user'@'bastion.server' 'ssh -o PasswordAuthentication=no '\''user123'\''@'\''my.server.com'\'''
+ssh -A -o PasswordAuthentication=no bastion-user@bastion.server 'ssh -o PasswordAuthentication=no user123@my.server.com'

this is solved by running args through our escaping such as $this->unish_escapeshellarg('bastion-user') ... do we really want to keep the bastion stuff?

greg.1.anderson’s picture

I don't think we should support the bastion options unless we also support them in other places where ssh-options are used. There are maybe half a dozen places in the code. It wouldn't be hard to write a wrapper function to handle it, but then again, it is not hard to put the bastion arguments into ssh-options by hand based on a recipe.

Should PasswordAuthentication really appear twice in #26? Seems one of them should be ProxyCommand.

It looks like the patch in #23 now conforms to the suggestion in #19, but the example given in the ssh help does not (ls /tmp should be wrapped in quotes).

moshe weitzman’s picture

Status: Needs review » Needs work

Fix those items that Greg points out and this is done.

moshe weitzman’s picture

Assigned: greg.1.anderson » Unassigned
Status: Needs work » Patch (to be ported)

I fixed up those items and fixed the help a lot. This command only takes one site alias, not a list separated by commas. You act upon multiple servers by making an alias that lists other aliases. Thats how I read the code anyway. I separated the alias and the bash code into separate arguments in the help ... Also added a test for non-interactive case.

Committed to 5.x. Seems safe for 4.x but the test suite has diverged now so it will have to derive from Drush_testCase there.

greg.1.anderson’s picture

Status: Patch (to be ported) » Needs work

One question I thought of that we should probably address before porting. This command could call winrs instead of ssh if the target is Windows. In that case, should the command still be called ssh?

I am refactoring the ssh code in backend invoke for use by sql-sync, and it would be natural to use that code here as well. Maybe we could alias it as winrs, so that you can call it as either ssh or winrs regardless of the target host. Some things you can do with ssh will not make sense with winrs, and visa-versa, but it would be useful to have the abstraction if you took the trouble to make the same scripts available on both platforms (very unusual?).

Finally, any time you have a drush command with the same name as a builtin or shell command, it has implications for core-cli. Some investigation should be done there too before backporting.

moshe weitzman’s picture

I'd like to keep this aliased to 'ssh' ... We should fix core-cli as needed (I thought is automatically ignores built in shell commands?) ... I'm OK with winrs being an additional alias or a separate command.

moshe weitzman’s picture

I think the refactor mentioned in #30 is being reviewed at #1149880: Remote commands to drush on Windows server + backend invoke / sql-sync refactor. If that one does not change ssh command, then we should patch ssh in this issue.

greg.1.anderson’s picture

Assigned: Unassigned » greg.1.anderson

Now that I have committed #1149880, I can go back and see if it makes sense to use that code here.

greg.1.anderson’s picture

Issue tags: +Windows

Adding 'Windows' tag, because the only thing to do here is to update to use winrs on Windows.

greg.1.anderson’s picture

Title: Provide "drush ssh" command » Use winrs when using "drush ssh" command on Windows
moshe weitzman’s picture

Issue tags: +Needs change record

Needs change notification

Jean Gionet’s picture

has anybody successfully used drush with winrs? comments?

I'm been trying to sync from Windows 2003 (staging) to Windows 2003 (production) and my production server is running SSH WRQ Reflection (aka F-Secure) which only supports Command Prompt shells (My staging server has cygwin/openssh). Once I ssh to the production sever I'm dropped to a Command Prompt Shell (ie: c:\my_home_folder) When this happens Drush cannot continue using ssh as it loses it's environment and I have no idea how to work around this... (very annoying/frustrating..)

anyways, I've stumbled on this post and I'm wondering how well it works?

Thanks

greg.1.anderson’s picture

Version: » 8.x-6.x-dev
Status: Needs work » Closed (won't fix)
Issue tags: +Needs migration

This issue was marked closed (won't fix) because Drush has moved to Github.

If this feature is still desired, you may copy it to our Github project. For best results, create a Pull Request that has been updated for the master branch. Post a link here to the PR, and please also change the status of this issue to closed (duplicate).

Please ask support questions on Drupal Answers.