class MCollective::RPC::Helpers
Various utilities for the RPC
system
Public Class Methods
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
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
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
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
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
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
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