Forward standard error stream from remote execution
This commit is contained in:
parent
a033e19583
commit
db91eb06cd
@ -11,6 +11,16 @@ Feature: `sh' task action
|
|||||||
When I successfully execute the recipe on remote target
|
When I successfully execute the recipe on remote target
|
||||||
Then the output must contain exactly "hello from remote\n"
|
Then the output must contain exactly "hello from remote\n"
|
||||||
|
|
||||||
|
Scenario: forwards error ouput
|
||||||
|
Given a recipe with:
|
||||||
|
"""
|
||||||
|
task :sh_action do
|
||||||
|
sh '\echo error from remote >&2'
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
When I successfully execute the recipe on remote target
|
||||||
|
Then the error output must contain exactly "error from remote\n"
|
||||||
|
|
||||||
Scenario: aborts on failed command execution
|
Scenario: aborts on failed command execution
|
||||||
Given a recipe with:
|
Given a recipe with:
|
||||||
"""
|
"""
|
||||||
|
@ -21,3 +21,7 @@ end
|
|||||||
Then /^the output must contain exactly:$/ do |content|
|
Then /^the output must contain exactly:$/ do |content|
|
||||||
assert_exact_output content, all_output
|
assert_exact_output content, all_output
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Then /^the error output must contain exactly "([^"]+)"$/ do |content|
|
||||||
|
assert_exact_output content, all_stderr
|
||||||
|
end
|
||||||
|
@ -2,7 +2,7 @@ module Producer
|
|||||||
module Core
|
module Core
|
||||||
class Action
|
class Action
|
||||||
extend Forwardable
|
extend Forwardable
|
||||||
def_delegators :@env, :input, :output, :remote
|
def_delegators :@env, :input, :output, :error_output, :remote
|
||||||
def_delegators :remote, :fs
|
def_delegators :remote, :fs
|
||||||
|
|
||||||
attr_reader :env, :arguments
|
attr_reader :env, :arguments
|
||||||
|
@ -7,7 +7,7 @@ module Producer
|
|||||||
end
|
end
|
||||||
|
|
||||||
def apply
|
def apply
|
||||||
remote.execute(arguments.first, output)
|
remote.execute(arguments.first, output, error_output)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -31,7 +31,7 @@ module Producer
|
|||||||
@arguments = args
|
@arguments = args
|
||||||
@stdin = stdin
|
@stdin = stdin
|
||||||
@stdout = stdout
|
@stdout = stdout
|
||||||
@env = Env.new(input: stdin, output: stdout)
|
@env = Env.new(input: stdin, output: stdout, error_output: stderr)
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_arguments!
|
def parse_arguments!
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
module Producer
|
module Producer
|
||||||
module Core
|
module Core
|
||||||
class Env
|
class Env
|
||||||
attr_reader :input, :output, :registry, :logger
|
attr_reader :input, :output, :error_output, :registry, :logger
|
||||||
attr_accessor :target, :verbose, :dry_run
|
attr_accessor :target, :verbose, :dry_run
|
||||||
|
|
||||||
def initialize(input: $stdin, output: $stdout, remote: nil, registry: {})
|
def initialize(input: $stdin, output: $stdout, error_output: $stderr, remote: nil, registry: {})
|
||||||
@verbose = @dry_run = false
|
@verbose = @dry_run = false
|
||||||
@input = input
|
@input = input
|
||||||
@output = output
|
@output = output
|
||||||
@remote = remote
|
@error_output = error_output
|
||||||
@registry = registry
|
@remote = remote
|
||||||
|
@registry = registry
|
||||||
end
|
end
|
||||||
|
|
||||||
def remote
|
def remote
|
||||||
|
@ -24,13 +24,17 @@ module Producer
|
|||||||
@fs ||= Remote::FS.new(session.sftp.connect)
|
@fs ||= Remote::FS.new(session.sftp.connect)
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute(command, output = '')
|
def execute(command, output = '', error_output = '')
|
||||||
channel = session.open_channel do |channel|
|
channel = session.open_channel do |channel|
|
||||||
channel.exec command do |ch, success|
|
channel.exec command do |ch, success|
|
||||||
ch.on_data do |c, data|
|
ch.on_data do |c, data|
|
||||||
output << data
|
output << data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
ch.on_extended_data do |c, type, data|
|
||||||
|
error_output << data
|
||||||
|
end
|
||||||
|
|
||||||
ch.on_request 'exit-status' do |c, data|
|
ch.on_request 'exit-status' do |c, data|
|
||||||
exit_status = data.read_long
|
exit_status = data.read_long
|
||||||
fail RemoteCommandExecutionError, command if exit_status != 0
|
fail RemoteCommandExecutionError, command if exit_status != 0
|
||||||
|
@ -6,13 +6,18 @@ module Producer
|
|||||||
fail 'no session for mock remote!'
|
fail 'no session for mock remote!'
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute(command, output = '')
|
def execute(command, output = '', error_output = '')
|
||||||
tokens = command.split
|
tokens = command.gsub(/\d?>.*/, '').split
|
||||||
program = tokens.shift
|
program = tokens.shift
|
||||||
|
|
||||||
case program
|
case program
|
||||||
when 'echo'
|
when 'echo'
|
||||||
output << tokens.join(' ') << "\n"
|
out = tokens.join(' ') << "\n"
|
||||||
|
if command =~ />&2\z/
|
||||||
|
error_output << out
|
||||||
|
else
|
||||||
|
output << out
|
||||||
|
end
|
||||||
when 'true'
|
when 'true'
|
||||||
output << ''
|
output << ''
|
||||||
when 'false'
|
when 'false'
|
||||||
|
@ -19,6 +19,15 @@ module Producer::Core
|
|||||||
sh.apply
|
sh.apply
|
||||||
expect(output).to eq "#{command_args}\n"
|
expect(output).to eq "#{command_args}\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when content is written to standard error' do
|
||||||
|
let(:command) { "echo #{command_args} >&2" }
|
||||||
|
|
||||||
|
it 'writes errors to given error stream' do
|
||||||
|
sh.apply
|
||||||
|
expect(error_output).to eq "#{command_args}\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -12,7 +12,9 @@ module Producer::Core
|
|||||||
let(:stdout) { StringIO.new }
|
let(:stdout) { StringIO.new }
|
||||||
let(:stderr) { StringIO.new }
|
let(:stderr) { StringIO.new }
|
||||||
|
|
||||||
subject(:cli) { CLI.new(arguments, stdin: stdin, stdout: stdout) }
|
subject(:cli) { described_class.new(
|
||||||
|
arguments,
|
||||||
|
stdin: stdin, stdout: stdout, stderr: stderr) }
|
||||||
|
|
||||||
describe '.run!' do
|
describe '.run!' do
|
||||||
let(:cli) { double('cli').as_null_object }
|
let(:cli) { double('cli').as_null_object }
|
||||||
@ -118,6 +120,10 @@ module Producer::Core
|
|||||||
it 'assigns CLI stdout as the env output' do
|
it 'assigns CLI stdout as the env output' do
|
||||||
expect(cli.env.output).to be stdout
|
expect(cli.env.output).to be stdout
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'assigns CLI stderr as the env error output' do
|
||||||
|
expect(cli.env.error_output).to be stderr
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#parse_arguments!' do
|
describe '#parse_arguments!' do
|
||||||
|
@ -10,6 +10,10 @@ module Producer::Core
|
|||||||
expect(env.input).to be $stdin
|
expect(env.input).to be $stdin
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'assigns $stderr as the default error output' do
|
||||||
|
expect(env.error_output).to be $stderr
|
||||||
|
end
|
||||||
|
|
||||||
it 'assigns no default target' do
|
it 'assigns no default target' do
|
||||||
expect(env.target).not_to be
|
expect(env.target).not_to be
|
||||||
end
|
end
|
||||||
@ -51,6 +55,15 @@ module Producer::Core
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when error output is given as argument' do
|
||||||
|
let(:error_output) { StringIO.new }
|
||||||
|
subject(:env) { described_class.new(error_output: error_output) }
|
||||||
|
|
||||||
|
it 'assigns the given error output' do
|
||||||
|
expect(env.error_output).to be error_output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when remote is given as argument' do
|
context 'when remote is given as argument' do
|
||||||
let(:remote) { double 'remote' }
|
let(:remote) { double 'remote' }
|
||||||
subject(:env) { described_class.new(remote: remote) }
|
subject(:env) { described_class.new(remote: remote) }
|
||||||
|
@ -106,6 +106,16 @@ module Producer::Core
|
|||||||
expect(output.string).to eq arguments
|
expect(output.string).to eq arguments
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'writes command error output to provided error output' do
|
||||||
|
error_output = StringIO.new
|
||||||
|
story_with_new_channel do |ch|
|
||||||
|
ch.sends_exec command
|
||||||
|
ch.gets_extended_data arguments
|
||||||
|
end
|
||||||
|
remote.execute command, output, error_output
|
||||||
|
expect(error_output.string).to eq arguments
|
||||||
|
end
|
||||||
|
|
||||||
context 'when command execution fails' do
|
context 'when command execution fails' do
|
||||||
before do
|
before do
|
||||||
story_with_new_channel do |ch|
|
story_with_new_channel do |ch|
|
||||||
|
@ -29,6 +29,12 @@ module Producer::Core
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#error_output' do
|
||||||
|
it 'returns env error output' do
|
||||||
|
expect(action.error_output).to be env.error_output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#remote' do
|
describe '#remote' do
|
||||||
it 'returns env remote' do
|
it 'returns env remote' do
|
||||||
expect(action.remote).to be env.remote
|
expect(action.remote).to be env.remote
|
||||||
|
@ -9,6 +9,10 @@ module TestEnvHelpers
|
|||||||
env.output.string
|
env.output.string
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def error_output
|
||||||
|
env.error_output.string
|
||||||
|
end
|
||||||
|
|
||||||
def remote_fs
|
def remote_fs
|
||||||
env.remote.fs
|
env.remote.fs
|
||||||
end
|
end
|
||||||
@ -17,14 +21,18 @@ module TestEnvHelpers
|
|||||||
opts = { expected_from: caller.first }
|
opts = { expected_from: caller.first }
|
||||||
RSpec::Mocks
|
RSpec::Mocks
|
||||||
.expect_message(env.remote, :execute, opts)
|
.expect_message(env.remote, :execute, opts)
|
||||||
.with(command, env.output)
|
.with(command, env.output, env.error_output)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def build_env
|
def build_env
|
||||||
Producer::Core::Env.new(output: StringIO.new, remote: build_remote)
|
Producer::Core::Env.new(
|
||||||
|
output: StringIO.new,
|
||||||
|
error_output: StringIO.new,
|
||||||
|
remote: build_remote
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_remote
|
def build_remote
|
||||||
|
Loading…
x
Reference in New Issue
Block a user