# class MCollective::RPC::ActionRunner

A helper used by RPC::Agent#implemented_by to delegate an action to an external script. At present only JSON based serialization is supported in future ones based on key=val pairs etc will be added

It serializes the request object into an input file and creates an empty output file. It then calls the external command reading the output file at the end.

any STDERR gets logged at error level and any STDOUT gets logged at info level.

It will interpret the exit code from the application the same way MCollective::RPC::Reply#fail! and fail handles their codes creating a consistent interface, the message part of the fail message will come from STDERR

Generally externals should just exit with code 1 on failure and print to STDERR, this is exactly what Perl die() does and translates perfectly to our model

It uses the MCollective::Shell wrapper to call the external application

### Attributes

action[R]
agent[R]
command[R]
format[R]
request[R]
stderr[R]
stdout[R]

### Public Class Methods

new(command, request, format=:json) click to toggle source

    # File lib/mcollective/rpc/actionrunner.rb, line 26
    def initialize(command, request, format=:json)
      @agent = request.agent
      @action = request.action
      @format = format
      @request = request
      @command = path_to_command(command)
      @stdout = ""
      @stderr = ""
    end

### Public Instance Methods

canrun?(command) click to toggle source

    # File lib/mcollective/rpc/actionrunner.rb, line 117
    def canrun?(command)
      File.executable?(command)
    end

load_json_results(file) click to toggle source

    # File lib/mcollective/rpc/actionrunner.rb, line 91
    def load_json_results(file)
      return {} unless File.readable?(file)

      JSON.load(File.read(file)) || {}
    rescue JSON::ParserError
      {}
    end

load_results(file) click to toggle source

    # File lib/mcollective/rpc/actionrunner.rb, line 73
    def load_results(file)
      Log.debug("Attempting to load results in #{format} format from #{file}")

      data = {}

      if respond_to?("load_#{format}_results")
        tempdata = send("load_#{format}_results", file)

        tempdata.each_pair do |k,v|
          data[k.to_sym] = v
        end
      end

      data
    rescue Exception => e
      {}
    end

path_to_command(command) click to toggle source

    # File lib/mcollective/rpc/actionrunner.rb, line 129
    def path_to_command(command)
      unless command[0,1] == File::SEPARATOR
        Config.instance.libdir.each do |libdir|
          command_file = File.join(libdir, "agent", agent, command)

          return command_file if File.exist?(command_file)
        end
      end

      return command
    end

run() click to toggle source

    # File lib/mcollective/rpc/actionrunner.rb, line 36
    def run
      unless canrun?(command)
        Log.warn("Cannot run #{to_s}")
        raise RPCAborted, "Cannot execute #{to_s}"
      end

      Log.debug("Running #{to_s}")

      request_file = saverequest(request)
      reply_file = tempfile("reply")
      reply_file.close

      runner = shell(command, request_file.path, reply_file.path)

      runner.runcommand

      Log.debug("#{command} exited with #{runner.status.exitstatus}")

      stderr.each_line {|l| Log.error("#{to_s}: #{l.chomp}")} unless stderr.empty?
      stdout.each_line {|l| Log.info("#{to_s}: #{l.chomp}")} unless stdout.empty?

      {:exitstatus => runner.status.exitstatus,
       :stdout     => runner.stdout,
       :stderr     => runner.stderr,
       :data       => load_results(reply_file.path)}
    ensure
      request_file.close! if request_file.respond_to?("close!")
      reply_file.close! if reply_file.respond_to?("close")
    end

save_json_request(req) click to toggle source

    # File lib/mcollective/rpc/actionrunner.rb, line 113
    def save_json_request(req)
      req.to_json
    end

saverequest(req) click to toggle source

    # File lib/mcollective/rpc/actionrunner.rb, line 99
    def saverequest(req)
      Log.debug("Attempting to save request in #{format} format")

      if respond_to?("save_#{format}_request")
        data = send("save_#{format}_request", req)

        request_file = tempfile("request")
        request_file.puts data
        request_file.close
      end

      request_file
    end

shell(command, infile, outfile) click to toggle source

    # File lib/mcollective/rpc/actionrunner.rb, line 66
    def shell(command, infile, outfile)
      env = {"MCOLLECTIVE_REQUEST_FILE" => infile,
             "MCOLLECTIVE_REPLY_FILE"   => outfile}

      Shell.new("#{command} #{infile} #{outfile}", :cwd => Dir.tmpdir, :stdout => stdout, :stderr => stderr, :environment => env)
    end

tempfile(prefix) click to toggle source

    # File lib/mcollective/rpc/actionrunner.rb, line 125
    def tempfile(prefix)
      Tempfile.new("mcollective_#{prefix}", Dir.tmpdir)
    end

to_s() click to toggle source

    # File lib/mcollective/rpc/actionrunner.rb, line 121
    def to_s
      "%s#%s command: %s" % [ agent, action, command ]
    end