class OrgConverge::CodeBlockProcess

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

Attributes

options[R]

Public Instance Methods

run(options={}) click to toggle source
# File lib/org-converge/engine.rb, line 177
def run(options={})
  env    = @options[:env].merge(options[:env] || {})
  logger = @options[:logger]
  output = options[:output] || $stdout
  runner = "#{Foreman.runner}".shellescape
  @babel = @options[:babel]

  # 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]
      ssh_params = determine_ssh_params(options[:header][:dir])
      if ssh_params[:host]
        block_modifiers[:ssh] = ssh_params
      else
        block_modifiers[:cwd] = File.expand_path(File.join(self.options[:cwd], options[:header][:dir]))
      end
    end
  end

  pid     = nil
  thread  = nil

  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}"
    end
    opts = { :out => output, :err => output }
    pid  = Process.spawn env, wrapped_command, opts
  end

  ssh_process = nil
  if block_modifiers[:ssh]
    ssh_process = proc do
      ssh_options = { }
      ssh_options[:port]       = block_modifiers[:ssh][:port]
      ssh_options[:password]   = block_modifiers[:ssh][:password]   if block_modifiers[:ssh][:password]
      ssh_options[:keys] = @babel.ob.in_buffer_settings['SSHIDENTITYFILE'] if @babel.ob.in_buffer_settings['SSHIDENTITYFILE']
      begin
        # SCP the script to run remotely and the binary used to run it
        binary, script = command.split(' ')
        remote_file = if not block_modifiers[:ssh][:remote_dir].empty?
                        File.join(block_modifiers[:ssh][:remote_dir], "org-run-#{File.basename(script)}")
                      else
                        "org-run-#{File.basename(script)}"
                      end
        scp_options = ssh_options
        scp_options[:keys] = [ssh_options[:keys]] if ssh_options[:keys]

        # TODO: Detect and upload the file only once
        Net::SCP.upload!(block_modifiers[:ssh][:host],
                         block_modifiers[:ssh][:user],
                         script,
                         remote_file,
                         :ssh => scp_options)
        Net::SSH.start(block_modifiers[:ssh][:host], 
                       block_modifiers[:ssh][:user], ssh_options) do |ssh|
          channel = ssh.open_channel do |chan|
            chan.exec "#{binary} #{remote_file}" do |ch, success|
              raise "could not execute command" unless success

              # "on_data" is called when the process writes something to stdout
              # "on_extended_data" is called when the process writes something to stderr
              chan.on_data          { |c, data| output.puts data       }
              chan.on_extended_data { |c, type, data| output.puts data }
              chan.on_close         { output.puts "exited from #{block_modifiers[:ssh][:host]}"}
            end
            chan.wait
          end
          ssh.loop
        end
      rescue Net::SCP::Error
        output.puts "Error when transporting file: #{script}"
      rescue => e
        puts "Error during ssh session: #{e}"
      end
    end
  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] || block_modifiers[:ssh])
    waitfor = block_modifiers[:waitfor].to_i
    timeout = block_modifiers[:timeout].to_i

    thread = Thread.new do
      sleep waitfor if waitfor > 0
      if ssh_process
        ssh_process.call
      else
        pid = process.call
      end
      # TODO: This doesn't work
      # 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