Class: MCollective::RPC::ActionRunner

Inherits:
Object
  • Object
show all
Defined in:
lib/mcollective/rpc/actionrunner.rb

Overview

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 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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(command, request, format = :json) ⇒ ActionRunner

Returns a new instance of ActionRunner.



26
27
28
29
30
31
32
33
34
# 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

Instance Attribute Details

#actionObject (readonly)

Returns the value of attribute action.



24
25
26
# File 'lib/mcollective/rpc/actionrunner.rb', line 24

def action
  @action
end

#agentObject (readonly)

Returns the value of attribute agent.



24
25
26
# File 'lib/mcollective/rpc/actionrunner.rb', line 24

def agent
  @agent
end

#commandObject (readonly)

Returns the value of attribute command.



24
25
26
# File 'lib/mcollective/rpc/actionrunner.rb', line 24

def command
  @command
end

#formatObject (readonly)

Returns the value of attribute format.



24
25
26
# File 'lib/mcollective/rpc/actionrunner.rb', line 24

def format
  @format
end

#requestObject (readonly)

Returns the value of attribute request.



24
25
26
# File 'lib/mcollective/rpc/actionrunner.rb', line 24

def request
  @request
end

#stderrObject (readonly)

Returns the value of attribute stderr.



24
25
26
# File 'lib/mcollective/rpc/actionrunner.rb', line 24

def stderr
  @stderr
end

#stdoutObject (readonly)

Returns the value of attribute stdout.



24
25
26
# File 'lib/mcollective/rpc/actionrunner.rb', line 24

def stdout
  @stdout
end

Instance Method Details

#canrun?(command) ⇒ Boolean

Returns:

  • (Boolean)


117
118
119
# File 'lib/mcollective/rpc/actionrunner.rb', line 117

def canrun?(command)
  File.executable?(command)
end

#load_json_results(file) ⇒ Object



91
92
93
94
95
96
97
# File 'lib/mcollective/rpc/actionrunner.rb', line 91

def load_json_results(file)
  return {} unless File.readable?(file)

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

#load_results(file) ⇒ Object



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# 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 # rubocop:disable Lint/RescueException
  {}
end

#path_to_command(command) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/mcollective/rpc/actionrunner.rb', line 129

def path_to_command(command)
  return command if Util.absolute_path?(command)

  Config.instance.libdir.each do |libdir|
    command_file_old = File.join(libdir, "agent", agent, command)
    command_file_new = File.join(libdir, "mcollective", "agent", agent, command)
    command_file_old_exists = File.exist?(command_file_old)
    command_file_new_exists = File.exist?(command_file_new)

    if command_file_new_exists && command_file_old_exists
      Log.debug("Found 'implemented_by' scripts found in two locations #{command_file_old} and #{command_file_new}")
      Log.debug("Running script: #{command_file_new}")
      return command_file_new
    elsif command_file_old_exists
      Log.debug("Running script: #{command_file_old}")
      return command_file_old
    elsif command_file_new_exists
      Log.debug("Running script: #{command_file_new}")
      return command_file_new
    end
  end

  Log.warn("No script found for: #{command}")
  command
end

#runObject



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/mcollective/rpc/actionrunner.rb', line 36

def run
  unless canrun?(command)
    Log.warn("Cannot run #{self}")
    raise RPCAborted, "Cannot execute #{self}"
  end

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

  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("#{self}: #{l.chomp}")} unless stderr.empty?
  stdout.each_line {|l| Log.info("#{self}: #{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) ⇒ Object



113
114
115
# File 'lib/mcollective/rpc/actionrunner.rb', line 113

def save_json_request(req)
  req.to_json
end

#saverequest(req) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
# 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) ⇒ Object



66
67
68
69
70
71
# 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) ⇒ Object



125
126
127
# File 'lib/mcollective/rpc/actionrunner.rb', line 125

def tempfile(prefix)
  Tempfile.new("mcollective_#{prefix}", Dir.tmpdir)
end

#to_sObject



121
122
123
# File 'lib/mcollective/rpc/actionrunner.rb', line 121

def to_s
  "%s#%s command: %s" % [agent, action, command]
end