lib/org-converge/engine.rb in org-converge-0.0.13 vs lib/org-converge/engine.rb in org-converge-0.0.14
- old
+ new
@@ -3,10 +3,11 @@
# and just customize to watch the output from runnable code blocks
#
require 'foreman/engine'
require 'foreman/process'
require 'tco'
+require 'fileutils'
module OrgConverge
class Engine < Foreman::Engine
attr_reader :logger
@@ -21,29 +22,35 @@
@runmode = options[:runmode]
# Code blocks whose start invocation is manipulated run inside a thread
@threads = []
@running_threads = { }
+
+ # Returns a list in the end with the exit status code from the code blocks
+ # that were run in parallel
+ @procs_exit_status = { }
end
# We allow other processes to exit with 0 status
# to continue with the runlist
def start
register_signal_handlers
spawn_processes
watch_for_output
sleep 0.1
begin
- status = watch_for_termination do
+ status = watch_for_termination do
@threads.each do |t|
unless t.alive?
t.exit
@running_threads.delete(t.__id__)
end
end
end
end while (@running.count > 0 or @running_threads.count > 0)
+
+ @procs_exit_status
end
# Overriden: we do not consider process formations
def spawn_processes
@processes.each do |process|
@@ -76,10 +83,11 @@
end
def register(name, command, options={})
options[:env] ||= env
options[:cwd] ||= File.dirname(command.split(" ").first)
+
process = OrgConverge::CodeBlockProcess.new(command, options)
@names[process] = name
@processes << process
end
@@ -123,10 +131,24 @@
yield if block_given?
pid
rescue Errno::ECHILD
end
+ def termination_message_for(status)
+ n = name_for(status.pid).split('.').first
+
+ if status.exited?
+ @procs_exit_status[n] = status.exitstatus
+ "exited with code #{status.exitstatus}"
+ elsif status.signaled?
+ # TODO: How to handle exit by signals? Non-zero exit status so idempotency check fails?
+ "terminated by SIG#{Signal.list.invert[status.termsig]}"
+ else
+ "died a mysterious death"
+ end
+ end
+
def name_for(pid)
process = nil
index = nil
if @running[pid]
process, index = @running[pid]
@@ -142,49 +164,67 @@
end
# Need to expose the options to make the process be aware
# of the possible running mode (specially spec mode)
# and where to put the results output
- require 'timeout'
class CodeBlockProcess < Foreman::Process
attr_reader :options
def run(options={})
env = @options[:env].merge(options[:env] || {})
output = options[:output] || $stdout
- runner = "#{Foreman.runner}".shellescape
+ runner = "#{Foreman.runner}".shellescape
# whitelist the modifiers which manipulate how to the block is started
block_modifiers = { }
if options[:header]
block_modifiers[:waitfor] = options[:header][:waitsfor] || options[:header][:waitfor] || options[:header][:sleep]
block_modifiers[:timeout] = options[:header][:timeoutin] || options[:header][:timeout] || options[:header][:timeoutafter]
+ if options[:header][:dir]
+ block_modifiers[:cwd] = File.expand_path(File.join(self.options[:cwd], options[:header][:dir]))
+ end
end
pid = nil
thread = nil
- process = nil
- if block_modifiers and (block_modifiers[:waitfor] || block_modifiers[:timeout])
- thread = Thread.new do
- waitfor = block_modifiers[:waitfor].to_i
- timeout = block_modifiers[:timeout].to_i
- process = proc do
- sleep waitfor if waitfor > 0
- wrapped_command = "exec #{runner} -d '#{cwd}' -p -- #{command}"
- pid = Process.spawn env, wrapped_command, :out => output, :err => output
- end
- timeout > 0 ? Timeout::timeout(timeout, &process) : process.call
- end
- else
- if Foreman.windows?
- Dir.chdir(cwd) do
- pid = Process.spawn env, expanded_command(env), :out => output, :err => output
- end
+ process = proc do
+ wrapped_command = ''
+ if block_modifiers[:cwd]
+ @options[:cwd] = block_modifiers[:cwd]
+ # Need to adjust the path by having the run file at the same place
+ bin, original_script = command.split(' ')
+ new_script = File.join(block_modifiers[:cwd], ".#{options[:header][:name]}")
+ FileUtils.cp(original_script, new_script)
+ cmd = [bin, new_script].join(' ')
+ wrapped_command = "exec #{runner} -d '#{cwd}' -p -- #{cmd}"
else
wrapped_command = "exec #{runner} -d '#{cwd}' -p -- #{command}"
- pid = Process.spawn env, wrapped_command, :out => output, :err => output
end
+ opts = { :out => output, :err => output }
+ pid = Process.spawn env, wrapped_command, opts
+ end
+
+ # In case we modify the run block, we run it in a Thread
+ # otherwise we continue treating it as a forked process.
+ if block_modifiers and (block_modifiers[:waitfor] || block_modifiers[:timeout] || block_modifiers[:dir])
+ waitfor = block_modifiers[:waitfor].to_i
+ timeout = block_modifiers[:timeout].to_i
+
+ thread = Thread.new do
+ sleep waitfor if waitfor > 0
+ pid = process.call
+ if timeout > 0
+ sleep timeout
+ # FIXME: Kill children properly
+ o = `ps -ef | awk '$3 == #{pid} { print $2 }'`
+ o.each_line { |cpid| Process.kill(:TERM, cpid.to_i) }
+ Process.kill(:TERM, pid)
+ Thread.current.kill
+ end
+ end
+ else
+ pid = process.call
end
# In case of thread, pid will be nil
return pid, thread
end