3 # A helper used by RPC::Agent#implemented_by to delegate an action to
4 # an external script. At present only JSON based serialization is
5 # supported in future ones based on key=val pairs etc will be added
7 # It serializes the request object into an input file and creates an
8 # empty output file. It then calls the external command reading the
9 # output file at the end.
11 # any STDERR gets logged at error level and any STDOUT gets logged at
14 # It will interpret the exit code from the application the same way
15 # RPC::Reply#fail! and #fail handles their codes creating a consistent
16 # interface, the message part of the fail message will come from STDERR
18 # Generally externals should just exit with code 1 on failure and print to
19 # STDERR, this is exactly what Perl die() does and translates perfectly
22 # It uses the MCollective::Shell wrapper to call the external application
24 attr_reader :command, :agent, :action, :format, :stdout, :stderr, :request
26 def initialize(command, request, format=:json)
27 @agent = request.agent
28 @action = request.action
31 @command = path_to_command(command)
37 unless canrun?(command)
38 Log.warn("Cannot run #{to_s}")
39 raise RPCAborted, "Cannot execute #{to_s}"
42 Log.debug("Running #{to_s}")
44 request_file = saverequest(request)
45 reply_file = tempfile("reply")
48 runner = shell(command, request_file.path, reply_file.path)
52 Log.debug("#{command} exited with #{runner.status.exitstatus}")
54 stderr.each_line {|l| Log.error("#{to_s}: #{l.chomp}")} unless stderr.empty?
55 stdout.each_line {|l| Log.info("#{to_s}: #{l.chomp}")} unless stdout.empty?
57 {:exitstatus => runner.status.exitstatus,
58 :stdout => runner.stdout,
59 :stderr => runner.stderr,
60 :data => load_results(reply_file.path)}
62 request_file.close! if request_file.respond_to?("close!")
63 reply_file.close! if reply_file.respond_to?("close")
66 def shell(command, infile, outfile)
67 env = {"MCOLLECTIVE_REQUEST_FILE" => infile,
68 "MCOLLECTIVE_REPLY_FILE" => outfile}
70 Shell.new("#{command} #{infile} #{outfile}", :cwd => Dir.tmpdir, :stdout => stdout, :stderr => stderr, :environment => env)
73 def load_results(file)
74 Log.debug("Attempting to load results in #{format} format from #{file}")
78 if respond_to?("load_#{format}_results")
79 tempdata = send("load_#{format}_results", file)
81 tempdata.each_pair do |k,v|
91 def load_json_results(file)
92 return {} unless File.readable?(file)
94 JSON.load(File.read(file)) || {}
95 rescue JSON::ParserError
100 Log.debug("Attempting to save request in #{format} format")
102 if respond_to?("save_#{format}_request")
103 data = send("save_#{format}_request", req)
105 request_file = tempfile("request")
106 request_file.puts data
113 def save_json_request(req)
118 File.executable?(command)
122 "%s#%s command: %s" % [ agent, action, command ]
126 Tempfile.new("mcollective_#{prefix}", Dir.tmpdir)
129 def path_to_command(command)
130 unless command[0,1] == File::SEPARATOR
131 Config.instance.libdir.each do |libdir|
132 command_file = File.join(libdir, "agent", agent, command)
134 return command_file if File.exist?(command_file)