Make SSHKit::Runner::Parallel fail slow

Using a different SSHKit runner doesn't work well, because the group
runner uses the Parallel runner internally. So instead we'll patch its
behaviour to fail slow.

We'll also get it to return all the errors so we can report on all the
hosts that failed.
This commit is contained in:
Donal McBreen
2024-05-21 09:31:08 +01:00
parent 78c0a0ba4b
commit fa7e941648
3 changed files with 45 additions and 24 deletions

View File

@@ -7,6 +7,15 @@ require "kamal"
begin begin
Kamal::Cli::Main.start(ARGV) Kamal::Cli::Main.start(ARGV)
rescue SSHKit::Runner::MultipleExecuteError => e
e.execute_errors.each do |execute_error|
puts " \e[31mERROR (#{execute_error.cause.class}): #{execute_error.message}\e[0m"
end
if ENV["VERBOSE"]
puts "Backtrace for the first error:"
puts e.execute_errors.first.cause.backtrace
end
exit 1
rescue SSHKit::Runner::ExecuteError => e rescue SSHKit::Runner::ExecuteError => e
puts " \e[31mERROR (#{e.cause.class}): #{e.message}\e[0m" puts " \e[31mERROR (#{e.cause.class}): #{e.message}\e[0m"
puts e.cause.backtrace if ENV["VERBOSE"] puts e.cause.backtrace if ENV["VERBOSE"]

View File

@@ -150,7 +150,6 @@ class Kamal::Commander
sshkit.max_concurrent_starts = config.sshkit.max_concurrent_starts sshkit.max_concurrent_starts = config.sshkit.max_concurrent_starts
sshkit.ssh_options = config.ssh.options sshkit.ssh_options = config.ssh.options
end end
SSHKit.config.default_runner = SSHKit::Runner::ParallelCompleteAll
SSHKit.config.command_map[:docker] = "docker" # No need to use /usr/bin/env, just clogs up the logs SSHKit.config.command_map[:docker] = "docker" # No need to use /usr/bin/env, just clogs up the logs
SSHKit.config.output_verbosity = verbosity SSHKit.config.output_verbosity = verbosity
end end

View File

@@ -104,33 +104,46 @@ class SSHKit::Backend::Netssh
prepend LimitConcurrentStartsInstance prepend LimitConcurrentStartsInstance
end end
require "thread" class SSHKit::Runner::MultipleExecuteError < SSHKit::StandardError
attr_reader :execute_errors
module SSHKit def initialize(execute_errors)
module Runner @execute_errors = execute_errors
class ParallelCompleteAll < Abstract end
end
class SSHKit::Runner::Parallel
# SSHKit joins the threads in sequence and fails on the first error it encounters, which means that we wait threads
# before the first failure to complete but not for ones after.
#
# We'll patch it to wait for them all to complete, and to record all the threads that errored so we can see when a
# problem occurs on multiple hosts.
module CompleteAll
def execute def execute
threads = hosts.map do |host| threads = hosts.map do |host|
Thread.new(host) do |h| Thread.new(host) do |h|
begin
backend(h, &block).run backend(h, &block).run
rescue ::StandardError => e rescue ::StandardError => e
e2 = SSHKit::Runner::ExecuteError.new e e2 = SSHKit::Runner::ExecuteError.new e
raise e2, "Exception while executing #{host.user ? "as #{host.user}@" : "on host "}#{host}: #{e.message}" raise e2, "Exception while executing #{host.user ? "as #{host.user}@" : "on host "}#{host}: #{e.message}"
end end
end end
end
exception = nil exceptions = []
threads.each do |t| threads.each do |t|
begin begin
t.join t.join
rescue SSHKit::Runner::ExecuteError => e rescue SSHKit::Runner::ExecuteError => e
exception ||= e exceptions << e
end end
end end
raise exception if exception if exceptions.one?
raise exceptions.first
elsif exceptions.many?
raise SSHKit::Runner::MultipleExecuteError.new(exceptions)
end end
end end
end end
prepend CompleteAll
end end