sshkit icon indicating copy to clipboard operation
sshkit copied to clipboard

Fix handling of output streams, including in cases of error.

Open leehambley opened this issue 11 years ago • 25 comments

In SSHKit we do some weird stuff with stream handling depending on the packets (sic) we receive back from Net::SSH.

I'd like to write a test case script that produces buffered, and unbuffered output, line- and character-wise, on standard error, and standard out to test the correct usage.

With a pathologically badly behaved script, we could find a way to make this work reliably, I am sure.

leehambley avatar Jan 27 '14 15:01 leehambley

For reference https://github.com/capistrano/capistrano/issues/895

tisba avatar Jan 29 '14 12:01 tisba

@leehambley Not sure if this helps and if it does not - please ignore me. I can see everybody pointing this issue out to you and my guess is you are probably sick of it right now.

As for the weird stuff there - I've seen you remove stderr value to fix the 'double output bug' https://github.com/capistrano/sshkit/issues/20 https://github.com/capistrano/sshkit/commit/ef80a33e3cc59be25ec82f23c88ef913365094cf

Reverting this commit causes sshkit to suddenly show outputs of failed commands:

before:

on %w{madsheep@a_server_far_far_away} do
  execute "lsaslasdasd"
end
 INFO [5aec5608] Running /usr/bin/env lsaslasdasd on a_server_far_far_away
SSHKit::Command::Failed: lsaslasdasd stdout: Nothing written
lsaslasdasd stderr: Nothing written

after reverting the commit

on %w{madsheep@a_server_far_far_away} do
  execute "lsaslasdasd"
end
 INFO [0c55883b] Running /usr/bin/env lsaslasdasd on a_server_far_far_away
SSHKit::Command::Failed: lsaslasdasd stdout: Nothing written
lsaslasdasd stderr: : No such file or directory

Again - not sure if that helps with anything - but I'm keeping my local version of sshkit patched to help me build my capistrano 3 recipes.

madsheep avatar Jan 31 '14 10:01 madsheep

Thanks for the feedback @tisba, actually it's a bit more complex than that, which is to say if we don't clear the output when it finished, you get the last "chunk" twice. (although risking showing it twice might be better than not showing it at all)

leehambley avatar Jan 31 '14 11:01 leehambley

I was not able to use capistrano 3 before that change - as none of the failed commands were giving output for me, which is crucial for debugging. I think it's worth making a dirty hack for checking if the line you want to append is already there. This will make it useable (and the hack can be removed once you figure this whole part properly)

madsheep avatar Jan 31 '14 12:01 madsheep

I'd too rather prefer to have duplicate output. No output on errors is a big problem IMO.

tisba avatar Feb 04 '14 13:02 tisba

I just wrote this, which I've been meaning to do for a while:

  • https://gist.github.com/leehambley/2ea90b2d7bec46153520

I believe if we can set this up somewhere, and poke the 5 different ways it can be operated until we find a solution that doesn't lose output, we should be in a good place.

leehambley avatar Mar 11 '14 08:03 leehambley

( I will get around to it sometime, but if someone can help, that would be greatly appreciated. )

leehambley avatar Mar 11 '14 08:03 leehambley

Yeah, this had/has me stumped too. I can't see any output from failed commands unless I comment out:

              chan.on_request("exit-status") do |ch, data|
#                cmd.stdout = ''
#                cmd.stderr = ''
                cmd.exit_status = data.read_long
                output << cmd
              end

Would this be fixed with the #86 pull request to use capture instead? Am about to find out :)

denmat avatar May 20 '14 01:05 denmat

this gets me what I want after using the #86 code base.

    begin 
      on(@AGENTS, in: :parallel) do |agent|
        capture "sudo puppet agent --test --environment fixing_test_env ", raise_on_non_zero_exit: true
      end
    rescue SSHKit::Command::Failed => e
      if e.message.scan(/exit\s+status:\s+(0|2)/)
        exit(0)
      else
        puts e.message.match(/stderr:\s+(Error:.*)/m)
        @exit_status
      end
    end

denmat avatar May 20 '14 04:05 denmat

So aside from the issue with stderr not being reported on failed commands, my issue is with very messy output in my successful deploy. I'm not sure if this is related/helpful, but here's what actually happens:

I have a deploy.sh script on all machines. It has echo "fetching", echo "checking assets" and stuff (each message is output once). I have it on 5 machines. Capistrano is programmed to run it on one machine first, then on 4 others next. Keep in mind, 5 machines total.

This is what I actually see when I run this with logging set to debug and it runs successfully: https://gist.github.com/maxim/102892c03a7f4a34c61e

All of the standard output from the script is colored red (including git fetching stuff) while capistrano messages are green, so it looks even messier with color. If I set logging to info - then no output from remote machines is shown at all.

Notice all the repeated strings coming from the same machines, and some inconsistent spacing (see info message on the very bottom).

maxim avatar Jul 14 '14 17:07 maxim

@maxim My solution to all this was to write a custom subclass of SSHKit::Formatter::Abstract to get the concise kind of output I was looking for.

In my formatter, I write the full debug output to a log file and use stdout just for printing which command is being executed. The console now lets me monitor what is happening at a high level; if a command fails or I need to troubleshoot something, I can dig into the log file (which is super verbose and has the problems you mentioned, but it is a last resort).

mattbrictson avatar Jul 15 '14 00:07 mattbrictson

@mbrictson This is nice, most likely will follow your example. Would be helpful to see your implementation, if you don't mind gist'ing it.

maxim avatar Jul 15 '14 00:07 maxim

@maxim The implementation turned out to be pretty complicated (I had to monkey patch to get access to the currently executing rake task), but the resulting output is pretty:

screen shot 2014-07-14 at 5 36 19 pm

Most of the code lives here: https://github.com/mattbrictson/airbrussh/blob/master/lib/airbrussh/formatter.rb

mattbrictson avatar Jul 15 '14 00:07 mattbrictson

@maxim @leehambley Let me know if you think my formatter would be useful packaged as its own gem, or if you have any other suggestions on how I could improve it.

Edit: I've packaged up the formatter as a gem called airbrussh.

mattbrictson avatar Jul 15 '14 01:07 mattbrictson

@mbrictson Wow, this plumbing must've taken quite a bit of effort, could be immensely useful and time-saving if you can make it a drop-in solution. Unfortunate thing is that if sshkit itself is fixed this might get obsolete pretty quick. Perhaps this could be worked into sshkit directly? /cc @leehambley

maxim avatar Jul 15 '14 01:07 maxim

@mattbrictson that output looks great. Nice work!

leehambley avatar Nov 17 '14 12:11 leehambley

So since #86 was closed, what's the currently accepted solution? Seems like there's no way to get stdout/stderr at all right now when a task fails with non-zero exit status?

Aeon avatar Feb 23 '15 20:02 Aeon

would also like to know, as redirecting remote command's stdout and stderr to (remote) files and then (remote) cat'ing them seems far from ideal

ghost avatar Oct 20 '15 10:10 ghost

@daniel-gomes-sociomantic I may have misunderstood the problem here, but there is an option which can be applied to commands to stop non zero commands from raising an exception: raise_on_non_zero_exit: false. The command output is collected in the full_stderr and full_stdout attrs of the command object.

On SSHKit master, you could patch something like this on to the abstract back end (based on a mixture of the test and capture methods:

module SSHKit
  module Backend
    class Abstract
      def capture_error(*args)
        options = args.extract_options!.merge(raise_on_non_zero_exit: false, verbosity: Logger::DEBUG)
        executed_command = create_command_and_execute(args, options)
        # Do something with executed_command.full_stdout or executed_command.full_stderr
      end
    end
  end
end

Does this help?

robd avatar Oct 20 '15 10:10 robd

@robd I'm aware of raise_on_non_zero_exit: false (so I might be the one misunderstanding the issue after all), all I wanted was to be able to capture either the stdout or the stderr of a remote command regardless of whether it succeeded or not (without having to do the redirection myself, that is).

Looking at your proposed patch, it might be just the thing I need!! Unfortunately I am not so ruby-savvy as to being able to make my Capistrano use sshkit's master branch instead of the latest release (v1.7.1)... I tried calling command directly from my task but then of course there's no defined execute_command function

ghost avatar Oct 20 '15 11:10 ghost

@daniel-gomes-sociomantic Are you using a Gemfile with Bundler? If so you can add the following line to your Gemfile:

gem 'sshkit', :github => 'capistrano/sshkit', :branch => 'master'

and run bundle install

robd avatar Oct 20 '15 11:10 robd

@daniel-gomes-sociomantic Are you using a Gemfile with Bundler?

I'm not, my application is not written in ruby so I never got into the whole Gemfile setup (which is what I meant by not being very Ruby-savvy).

In the meantime, following your idea I managed to get what I wanted by combining the test and capture methods (from the v1.7.1 codebase) into one:

module SSHKit
    module Backend
        class Abstract
            def capture_error(*args)
                options = args.extract_options!.merge(
                    raise_on_non_zero_exit: false,
                    verbosity: Logger::DEBUG
                )

                executed_command = _execute(*[*args, options])

                info "stdout: #{executed_command.full_stdout}"
                info "stderr: #{executed_command.full_stderr}"
            end
        end
    end
end

and it seems to work fine, cheers @robd!

ghost avatar Oct 20 '15 11:10 ghost

@daniel-gomes-sociomantic Ah great - glad you got something working

robd avatar Oct 20 '15 11:10 robd

@mattbrictson we can close this now that Airbrusssh is the default, right ?

leehambley avatar Feb 21 '16 19:02 leehambley

Yes, this will be fixed at least in Capistrano with the merge of https://github.com/capistrano/capistrano/pull/1541. (I'm waiting until a new version of SSHKit is released to rubygems to move ahead on that PR.)

mattbrictson avatar Feb 22 '16 14:02 mattbrictson