3 # Various utilities for the RPC system
5 # Parse JSON output as produced by printrpc and extract
6 # the "sender" of each rpc response
8 # The simplist valid JSON based data would be:
11 # {"sender" => "example.com"},
12 # {"sender" => "another.com"}
14 def self.extract_hosts_from_json(json)
15 hosts = JSON.parse(json)
17 raise "JSON hosts list is not an array" unless hosts.is_a?(Array)
20 raise "JSON host list is not an array of Hashes" unless host.is_a?(Hash)
21 raise "JSON host list does not have senders in it" unless host.include?("sender")
27 # Given an array of something, make sure each is a string
28 # chomp off any new lines and return just the array of hosts
29 def self.extract_hosts_from_array(hosts)
30 [hosts].flatten.map do |host|
31 raise "#{host} should be a string" unless host.is_a?(String)
36 # Returns a blob of text representing the results in a standard way
38 # It tries hard to do sane things so you often
39 # should not need to write your own display functions
41 # If the agent you are getting results for has a DDL
42 # it will use the hints in there to do the right thing specifically
43 # it will look at the values of display in the DDL to choose
44 # when to show results
46 # If you do not have a DDL you can pass these flags:
48 # printrpc exim.mailq, :flatten => true
49 # printrpc exim.mailq, :verbose => true
51 # If you've asked it to flatten the result it will not print sender
52 # hostnames, it will just print the result as if it's one huge result,
53 # handy for things like showing a combined mailq.
54 def self.rpcresults(result, flags = {})
55 flags = {:verbose => false, :flatten => false, :format => :console, :force_display_mode => false}.merge(flags)
60 # if running in verbose mode, just use the old style print
61 # no need for all the DDL helpers obfuscating the result
62 if flags[:format] == :json
64 result_text = JSON.pretty_generate(result)
66 result_text = result.to_json
70 result_text = old_rpcresults(result, flags)
72 [result].flatten.each do |r|
74 ddl ||= DDL.new(r.agent).action_interface(r.action.to_s)
77 status = r[:statuscode]
78 message = r[:statusmsg]
81 if flags[:force_display_mode]
82 display = flags[:force_display_mode]
84 display = ddl[:display]
87 # appand the results only according to what the DDL says
91 result_text << text_for_result(sender, status, message, result, ddl)
96 result_text << text_for_result(sender, status, message, result, ddl)
100 result_text << text_for_result(sender, status, message, result, ddl)
103 result_text << text_for_flattened_result(status, result)
106 rescue Exception => e
107 # no DDL so just do the old style print unchanged for
109 result_text = old_rpcresults(result, flags)
118 # Return text representing a result
119 def self.text_for_result(sender, status, msg, result, ddl)
121 Util.colorize(:red, "Request Aborted"),
122 Util.colorize(:yellow, "Unknown Action"),
123 Util.colorize(:yellow, "Missing Request Data"),
124 Util.colorize(:yellow, "Invalid Request Data"),
125 Util.colorize(:red, "Unknown Request Status")]
127 result_text = "%-40s %s\n" % [sender, statusses[status]]
128 result_text << " %s\n" % [Util.colorize(:yellow, msg)] unless msg == "OK"
130 # only print good data, ignore data that results from failure
132 if result.is_a?(Hash)
133 # figure out the lengths of the display as strings, we'll use
134 # it later to correctly justify the output
135 lengths = result.keys.map do |k|
137 ddl[:output][k][:display_as].size
143 result.keys.sort_by{|k| k}.each do |k|
144 # get all the output fields nicely lined up with a
145 # 3 space front padding
147 display_as = ddl[:output][k][:display_as]
152 display_length = display_as.size
153 padding = lengths.max - display_length + 3
154 result_text << " " * padding
156 result_text << "#{display_as}:"
158 if [String, Numeric].include?(result[k].class)
159 lines = result[k].to_s.split("\n")
164 lines.each_with_index do |line, i|
165 i == 0 ? padtxt = " " : padtxt = " " * (padding + display_length + 2)
167 result_text << "#{padtxt}#{line}\n"
171 padding = " " * (lengths.max + 5)
172 result_text << " " << result[k].pretty_inspect.split("\n").join("\n" << padding) << "\n"
176 # for status 1 we dont want to show half baked
177 # data by default since the DDL will supply all the defaults
178 # it just doesnt look right
180 result_text << "\n\t" + result.pretty_inspect.split("\n").join("\n\t")
188 # Returns text representing a flattened result of only good data
189 def self.text_for_flattened_result(status, result)
193 unless result.is_a?(String)
194 result_text << result.pretty_inspect
196 result_text << result
201 # Backward compatible display block for results without a DDL
202 def self.old_rpcresults(result, flags = {})
207 if r[:statuscode] <= 1
210 unless data.is_a?(String)
211 result_text << data.pretty_inspect
216 result_text << r.pretty_inspect
222 [result].flatten.each do |r|
225 result_text << "%-40s: %s\n" % [r[:sender], r[:statusmsg]]
227 if r[:statuscode] <= 1
228 r[:data].pretty_inspect.split("\n").each {|m| result_text += " #{m}"}
229 result_text << "\n\n"
230 elsif r[:statuscode] == 2
231 # dont print anything, no useful data to display
232 # past what was already shown
233 elsif r[:statuscode] == 3
234 # dont print anything, no useful data to display
235 # past what was already shown
236 elsif r[:statuscode] == 4
237 # dont print anything, no useful data to display
238 # past what was already shown
240 result_text << " #{r[:statusmsg]}"
243 unless r[:statuscode] == 0
244 result_text << "%-40s %s\n" % [r[:sender], Util.colorize(:red, r[:statusmsg])]
253 # Add SimpleRPC common options
254 def self.add_simplerpc_options(parser, options)
256 parser.separator "RPC Options"
258 # add SimpleRPC specific options to all clients that use our library
259 parser.on('--np', '--no-progress', 'Do not show the progress bar') do |v|
260 options[:progress_bar] = false
263 parser.on('--one', '-1', 'Send request to only one discovered nodes') do |v|
264 options[:mcollective_limit_targets] = 1
267 parser.on('--batch SIZE', Integer, 'Do requests in batches') do |v|
268 options[:batch_size] = v
271 parser.on('--batch-sleep SECONDS', Float, 'Sleep time between batches') do |v|
272 options[:batch_sleep_time] = v
275 parser.on('--limit-seed NUMBER', Integer, 'Seed value for deterministic random batching') do |v|
276 options[:limit_seed] = v
279 parser.on('--limit-nodes COUNT', '--ln', '--limit', 'Send request to only a subset of nodes, can be a percentage') do |v|
280 raise "Invalid limit specified: #{v} valid limits are /^\d+%*$/" unless v =~ /^\d+%*$/
283 options[:mcollective_limit_targets] = v.to_i
285 options[:mcollective_limit_targets] = v
289 parser.on('--json', '-j', 'Produce JSON output') do |v|
290 options[:progress_bar] = false
291 options[:output_format] = :json
294 parser.on('--display MODE', 'Influence how results are displayed. One of ok, all or failed') do |v|
296 options[:force_display_mode] = :always
298 options[:force_display_mode] = v.intern
301 raise "--display has to be one of 'ok', 'all' or 'failed'" unless [:ok, :failed, :always].include?(options[:force_display_mode])