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