class MCollective::RPC::Helpers

Various utilities for the RPC system

Public Class Methods

add_simplerpc_options(parser, options) click to toggle source

Add SimpleRPC common options

    # File lib/mcollective/rpc/helpers.rb
264 def self.add_simplerpc_options(parser, options)
265   parser.separator ""
266   parser.separator "RPC Options"
267 
268   # add SimpleRPC specific options to all clients that use our library
269   parser.on('--np', '--no-progress', 'Do not show the progress bar') do |v|
270     options[:progress_bar] = false
271   end
272 
273   parser.on('--one', '-1', 'Send request to only one discovered nodes') do |v|
274     options[:mcollective_limit_targets] = 1
275   end
276 
277   parser.on('--batch SIZE', 'Do requests in batches') do |v|
278     # validate batch string. Is it x% where x > 0 or is it an integer
279     if ((v =~ /^(\d+)%$/ && Integer($1) != 0) || v =~ /^(\d+)$/)
280       options[:batch_size] = v
281     else
282       raise(::OptionParser::InvalidArgument.new(v))
283     end
284   end
285 
286   parser.on('--batch-sleep SECONDS', Float, 'Sleep time between batches') do |v|
287     options[:batch_sleep_time] = v
288   end
289 
290   parser.on('--limit-seed NUMBER', Integer, 'Seed value for deterministic random batching') do |v|
291     options[:limit_seed] = v
292   end
293 
294   parser.on('--limit-nodes COUNT', '--ln', '--limit', 'Send request to only a subset of nodes, can be a percentage') do |v|
295     raise "Invalid limit specified: #{v} valid limits are /^\d+%*$/" unless v =~ /^\d+%*$/
296 
297     if v =~ /^\d+$/
298       options[:mcollective_limit_targets] = v.to_i
299     else
300       options[:mcollective_limit_targets] = v
301     end
302   end
303 
304   parser.on('--json', '-j', 'Produce JSON output') do |v|
305     options[:progress_bar] = false
306     options[:output_format] = :json
307   end
308 
309   parser.on('--display MODE', 'Influence how results are displayed. One of ok, all or failed') do |v|
310     if v == "all"
311       options[:force_display_mode] = :always
312     else
313       options[:force_display_mode] = v.intern
314     end
315 
316     raise "--display has to be one of 'ok', 'all' or 'failed'" unless [:ok, :failed, :always].include?(options[:force_display_mode])
317   end
318 end
extract_hosts_from_array(hosts) click to toggle source

Given an array of something, make sure each is a string chomp off any new lines and return just the array of hosts

   # File lib/mcollective/rpc/helpers.rb
39 def self.extract_hosts_from_array(hosts)
40   [hosts].flatten.map do |host|
41     raise "#{host} should be a string" unless host.is_a?(String)
42     host.chomp
43   end
44 end
extract_hosts_from_json(json) click to toggle source

Parse JSON output as produced by printrpc or puppet query and extract the “sender” / “certname” of each entry

The simplist valid JSON based data would be:

[

{"sender" => "example.com"},
{"sender" => "another.com"}

]

or

[

{"certname" => "example.com"},
{"certname" => "another.com"}

]

   # File lib/mcollective/rpc/helpers.rb
21 def self.extract_hosts_from_json(json)
22   hosts = JSON.parse(json)
23 
24   raise "JSON hosts list is not an array" unless hosts.is_a?(Array)
25 
26   hosts.map do |host|
27     raise "JSON host list is not an array of Hashes" unless host.is_a?(Hash)
28 
29     unless host.include?("sender") || host.include?("certname")
30       raise "JSON host list does not have senders in it"
31     end
32 
33     host["sender"] || host["certname"]
34   end.uniq
35 end
old_rpcresults(result, flags = {}) click to toggle source

Backward compatible display block for results without a DDL

    # File lib/mcollective/rpc/helpers.rb
213 def self.old_rpcresults(result, flags = {})
214   result_text = ""
215 
216   if flags[:flatten]
217     result.each do |r|
218       if r[:statuscode] <= 1
219         data = r[:data]
220 
221         unless data.is_a?(String)
222           result_text << data.pretty_inspect
223         else
224           result_text << data
225         end
226       else
227         result_text << r.pretty_inspect
228       end
229     end
230 
231     result_text << ""
232   else
233     [result].flatten.each do |r|
234       if flags[:verbose]
235         result_text << "%-40s: %s\n" % [r[:sender], r[:statusmsg]]
236 
237         if r[:statuscode] <= 1
238           r[:data].pretty_inspect.split("\n").each {|m| result_text += "    #{m}"}
239           result_text << "\n\n"
240         elsif r[:statuscode] == 2
241           # dont print anything, no useful data to display
242           # past what was already shown
243         elsif r[:statuscode] == 3
244           # dont print anything, no useful data to display
245           # past what was already shown
246         elsif r[:statuscode] == 4
247           # dont print anything, no useful data to display
248           # past what was already shown
249         else
250           result_text << "    #{r[:statusmsg]}"
251         end
252       else
253         unless r[:statuscode] == 0
254           result_text << "%-40s %s\n" % [r[:sender], Util.colorize(:red, r[:statusmsg])]
255         end
256       end
257     end
258   end
259 
260   result_text << ""
261 end
rpcresults(result, flags = {}) click to toggle source

Returns a blob of text representing the results in a standard way

It tries hard to do sane things so you often should not need to write your own display functions

If the agent you are getting results for has a DDL it will use the hints in there to do the right thing specifically it will look at the values of display in the DDL to choose when to show results

If you do not have a DDL you can pass these flags:

printrpc exim.mailq, :flatten => true
printrpc exim.mailq, :verbose => true

If you've asked it to flatten the result it will not print sender hostnames, it will just print the result as if it's one huge result, handy for things like showing a combined mailq.

    # File lib/mcollective/rpc/helpers.rb
 64 def self.rpcresults(result, flags = {})
 65   flags = {:verbose => false, :flatten => false, :format => :console, :force_display_mode => false}.merge(flags)
 66 
 67   result_text = ""
 68   ddl = nil
 69 
 70   # if running in verbose mode, just use the old style print
 71   # no need for all the DDL helpers obfuscating the result
 72   if flags[:format] == :json
 73     if STDOUT.tty?
 74       result_text = JSON.pretty_generate(result)
 75     else
 76       result_text = result.to_json
 77     end
 78   else
 79     if flags[:verbose]
 80       result_text = old_rpcresults(result, flags)
 81     else
 82       [result].flatten.each do |r|
 83         begin
 84           ddl ||= DDL.new(r.agent).action_interface(r.action.to_s)
 85 
 86           sender = r[:sender]
 87           status = r[:statuscode]
 88           message = r[:statusmsg]
 89           result = r[:data]
 90 
 91           if flags[:force_display_mode]
 92             display = flags[:force_display_mode]
 93           else
 94             display = ddl[:display]
 95           end
 96 
 97           # appand the results only according to what the DDL says
 98           case display
 99             when :ok
100               if status == 0
101                 result_text << text_for_result(sender, status, message, result, ddl)
102               end
103 
104             when :failed
105               if status > 0
106                 result_text << text_for_result(sender, status, message, result, ddl)
107               end
108 
109             when :always
110               result_text << text_for_result(sender, status, message, result, ddl)
111 
112             when :flatten
113               Log.warn("The display option :flatten is being deprecated and will be removed in the next minor release")
114               result_text << text_for_flattened_result(status, result)
115 
116           end
117         rescue Exception => e
118           # no DDL so just do the old style print unchanged for
119           # backward compat
120           result_text = old_rpcresults(result, flags)
121         end
122       end
123     end
124   end
125 
126   result_text
127 end
text_for_flattened_result(status, result) click to toggle source

Returns text representing a flattened result of only good data

    # File lib/mcollective/rpc/helpers.rb
200 def self.text_for_flattened_result(status, result)
201   result_text = ""
202 
203   if status <= 1
204     unless result.is_a?(String)
205       result_text << result.pretty_inspect
206     else
207       result_text << result
208     end
209   end
210 end
text_for_result(sender, status, msg, result, ddl) click to toggle source

Return text representing a result

    # File lib/mcollective/rpc/helpers.rb
130 def self.text_for_result(sender, status, msg, result, ddl)
131   statusses = ["",
132                Util.colorize(:red, "Request Aborted"),
133                Util.colorize(:yellow, "Unknown Action"),
134                Util.colorize(:yellow, "Missing Request Data"),
135                Util.colorize(:yellow, "Invalid Request Data"),
136                Util.colorize(:red, "Unknown Request Status")]
137 
138   result_text = "%-40s %s\n" % [sender, statusses[status]]
139   result_text << "   %s\n" % [Util.colorize(:yellow, msg)] unless msg == "OK"
140 
141   # only print good data, ignore data that results from failure
142   if status == 0
143     if result.is_a?(Hash)
144       # figure out the lengths of the display as strings, we'll use
145       # it later to correctly justify the output
146       lengths = result.keys.map do |k|
147         begin
148           ddl[:output][k][:display_as].size
149         rescue
150           k.to_s.size
151         end
152       end
153 
154       result.keys.sort_by{|k| k}.each do |k|
155         # get all the output fields nicely lined up with a
156         # 3 space front padding
157         begin
158           display_as = ddl[:output][k][:display_as]
159         rescue
160           display_as = k.to_s
161         end
162 
163         display_length = display_as.size
164         padding = lengths.max - display_length + 3
165         result_text << " " * padding
166 
167         result_text << "#{display_as}:"
168 
169         if [String, Numeric].include?(result[k].class)
170           lines = result[k].to_s.split("\n")
171 
172           if lines.empty?
173             result_text << "\n"
174           else
175             lines.each_with_index do |line, i|
176               i == 0 ? padtxt = " " : padtxt = " " * (padding + display_length + 2)
177 
178               result_text << "#{padtxt}#{line}\n"
179             end
180           end
181         else
182           padding = " " * (lengths.max + 5)
183           result_text << " " << result[k].pretty_inspect.split("\n").join("\n" << padding) << "\n"
184         end
185       end
186     elsif status == 1
187       # for status 1 we dont want to show half baked
188       # data by default since the DDL will supply all the defaults
189       # it just doesnt look right
190     else
191       result_text << "\n\t" + result.pretty_inspect.split("\n").join("\n\t")
192     end
193   end
194 
195   result_text << "\n"
196   result_text
197 end