mcollective-client 1.3.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of mcollective-client might be problematic. Click here for more details.
- data/bin/mc-call-agent +54 -0
- data/bin/mco +27 -0
- data/lib/mcollective.rb +70 -0
- data/lib/mcollective/agents.rb +160 -0
- data/lib/mcollective/application.rb +354 -0
- data/lib/mcollective/applications.rb +145 -0
- data/lib/mcollective/client.rb +292 -0
- data/lib/mcollective/config.rb +202 -0
- data/lib/mcollective/connector.rb +18 -0
- data/lib/mcollective/connector/base.rb +24 -0
- data/lib/mcollective/facts.rb +39 -0
- data/lib/mcollective/facts/base.rb +86 -0
- data/lib/mcollective/log.rb +103 -0
- data/lib/mcollective/logger.rb +5 -0
- data/lib/mcollective/logger/base.rb +73 -0
- data/lib/mcollective/logger/console_logger.rb +61 -0
- data/lib/mcollective/logger/file_logger.rb +46 -0
- data/lib/mcollective/logger/syslog_logger.rb +53 -0
- data/lib/mcollective/matcher.rb +16 -0
- data/lib/mcollective/matcher/parser.rb +93 -0
- data/lib/mcollective/matcher/scanner.rb +123 -0
- data/lib/mcollective/message.rb +201 -0
- data/lib/mcollective/monkey_patches.rb +104 -0
- data/lib/mcollective/optionparser.rb +164 -0
- data/lib/mcollective/pluginmanager.rb +180 -0
- data/lib/mcollective/pluginpackager.rb +26 -0
- data/lib/mcollective/pluginpackager/agent_definition.rb +79 -0
- data/lib/mcollective/pluginpackager/standard_definition.rb +59 -0
- data/lib/mcollective/registration.rb +16 -0
- data/lib/mcollective/registration/base.rb +75 -0
- data/lib/mcollective/rpc.rb +188 -0
- data/lib/mcollective/rpc/actionrunner.rb +142 -0
- data/lib/mcollective/rpc/agent.rb +441 -0
- data/lib/mcollective/rpc/audit.rb +38 -0
- data/lib/mcollective/rpc/client.rb +793 -0
- data/lib/mcollective/rpc/ddl.rb +258 -0
- data/lib/mcollective/rpc/helpers.rb +339 -0
- data/lib/mcollective/rpc/progress.rb +63 -0
- data/lib/mcollective/rpc/reply.rb +61 -0
- data/lib/mcollective/rpc/request.rb +51 -0
- data/lib/mcollective/rpc/result.rb +41 -0
- data/lib/mcollective/rpc/stats.rb +185 -0
- data/lib/mcollective/runnerstats.rb +90 -0
- data/lib/mcollective/security.rb +26 -0
- data/lib/mcollective/security/base.rb +237 -0
- data/lib/mcollective/shell.rb +87 -0
- data/lib/mcollective/ssl.rb +246 -0
- data/lib/mcollective/unix_daemon.rb +37 -0
- data/lib/mcollective/util.rb +274 -0
- data/lib/mcollective/vendor.rb +41 -0
- data/lib/mcollective/vendor/require_vendored.rb +2 -0
- data/lib/mcollective/windows_daemon.rb +25 -0
- data/spec/Rakefile +16 -0
- data/spec/fixtures/application/test.rb +7 -0
- data/spec/fixtures/test-cert.pem +15 -0
- data/spec/fixtures/test-private.pem +15 -0
- data/spec/fixtures/test-public.pem +6 -0
- data/spec/monkey_patches/instance_variable_defined.rb +7 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/unit/agents_spec.rb +280 -0
- data/spec/unit/application_spec.rb +636 -0
- data/spec/unit/applications_spec.rb +155 -0
- data/spec/unit/array.rb +30 -0
- data/spec/unit/config_spec.rb +148 -0
- data/spec/unit/facts/base_spec.rb +118 -0
- data/spec/unit/facts_spec.rb +39 -0
- data/spec/unit/log_spec.rb +71 -0
- data/spec/unit/logger/base_spec.rb +110 -0
- data/spec/unit/logger/syslog_logger_spec.rb +86 -0
- data/spec/unit/matcher/parser_spec.rb +106 -0
- data/spec/unit/matcher/scanner_spec.rb +71 -0
- data/spec/unit/message_spec.rb +401 -0
- data/spec/unit/optionparser_spec.rb +113 -0
- data/spec/unit/pluginmanager_spec.rb +173 -0
- data/spec/unit/pluginpackager/agent_definition_spec.rb +130 -0
- data/spec/unit/pluginpackager/standard_definition_spec.rb +75 -0
- data/spec/unit/plugins/mcollective/connector/activemq_spec.rb +533 -0
- data/spec/unit/plugins/mcollective/connector/stomp/eventlogger_spec.rb +34 -0
- data/spec/unit/plugins/mcollective/connector/stomp_spec.rb +417 -0
- data/spec/unit/plugins/mcollective/packagers/ospackage_spec.rb +229 -0
- data/spec/unit/plugins/mcollective/security/psk_spec.rb +156 -0
- data/spec/unit/registration/base_spec.rb +77 -0
- data/spec/unit/rpc/actionrunner_spec.rb +213 -0
- data/spec/unit/rpc/agent_spec.rb +155 -0
- data/spec/unit/rpc/client_spec.rb +523 -0
- data/spec/unit/rpc/ddl_spec.rb +388 -0
- data/spec/unit/rpc/helpers_spec.rb +55 -0
- data/spec/unit/rpc/reply_spec.rb +143 -0
- data/spec/unit/rpc/request_spec.rb +115 -0
- data/spec/unit/rpc/result_spec.rb +66 -0
- data/spec/unit/rpc/stats_spec.rb +288 -0
- data/spec/unit/runnerstats_spec.rb +40 -0
- data/spec/unit/security/base_spec.rb +279 -0
- data/spec/unit/shell_spec.rb +144 -0
- data/spec/unit/ssl_spec.rb +244 -0
- data/spec/unit/symbol.rb +11 -0
- data/spec/unit/unix_daemon.rb +41 -0
- data/spec/unit/util_spec.rb +342 -0
- data/spec/unit/vendor_spec.rb +34 -0
- data/spec/unit/windows_daemon.rb +43 -0
- data/spec/windows_spec.opts +1 -0
- metadata +242 -0
@@ -0,0 +1,258 @@
|
|
1
|
+
module MCollective
|
2
|
+
module RPC
|
3
|
+
# A class that helps creating data description language files
|
4
|
+
# for agents. You can define meta data, actions, input and output
|
5
|
+
# describing the behavior of your agent.
|
6
|
+
#
|
7
|
+
# Later you can access this information to assist with creating
|
8
|
+
# of user interfaces or online help
|
9
|
+
#
|
10
|
+
# A sample DDL can be seen below, you'd put this in your agent
|
11
|
+
# dir as <agent name>.ddl
|
12
|
+
#
|
13
|
+
# metadata :name => "SimpleRPC Service Agent",
|
14
|
+
# :description => "Agent to manage services using the Puppet service provider",
|
15
|
+
# :author => "R.I.Pienaar",
|
16
|
+
# :license => "GPLv2",
|
17
|
+
# :version => "1.1",
|
18
|
+
# :url => "http://mcollective-plugins.googlecode.com/",
|
19
|
+
# :timeout => 60
|
20
|
+
#
|
21
|
+
# action "status", :description => "Gets the status of a service" do
|
22
|
+
# display :always
|
23
|
+
#
|
24
|
+
# input "service",
|
25
|
+
# :prompt => "Service Name",
|
26
|
+
# :description => "The service to get the status for",
|
27
|
+
# :type => :string,
|
28
|
+
# :validation => '^[a-zA-Z\-_\d]+$',
|
29
|
+
# :optional => true,
|
30
|
+
# :maxlength => 30
|
31
|
+
#
|
32
|
+
# output "status",
|
33
|
+
# :description => "The status of service",
|
34
|
+
# :display_as => "Service Status"
|
35
|
+
# end
|
36
|
+
class DDL
|
37
|
+
attr_reader :meta
|
38
|
+
|
39
|
+
def initialize(agent, loadddl=true)
|
40
|
+
@actions = {}
|
41
|
+
@meta = {}
|
42
|
+
@config = MCollective::Config.instance
|
43
|
+
@agent = agent
|
44
|
+
|
45
|
+
if loadddl
|
46
|
+
if ddlfile = findddlfile(agent)
|
47
|
+
instance_eval(File.read(ddlfile))
|
48
|
+
else
|
49
|
+
raise("Can't find DDL for agent '#{agent}'")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def findddlfile(agent)
|
55
|
+
@config.libdir.each do |libdir|
|
56
|
+
ddlfile = File.join([libdir, "mcollective", "agent", "#{agent}.ddl"])
|
57
|
+
|
58
|
+
if File.exist?(ddlfile)
|
59
|
+
Log.debug("Found #{agent} ddl at #{ddlfile}")
|
60
|
+
return ddlfile
|
61
|
+
end
|
62
|
+
end
|
63
|
+
return false
|
64
|
+
end
|
65
|
+
|
66
|
+
# Registers meta data for the introspection hash
|
67
|
+
def metadata(meta)
|
68
|
+
[:name, :description, :author, :license, :version, :url, :timeout].each do |arg|
|
69
|
+
raise "Metadata needs a :#{arg}" unless meta.include?(arg)
|
70
|
+
end
|
71
|
+
|
72
|
+
@meta = meta
|
73
|
+
end
|
74
|
+
|
75
|
+
# Creates the definition for an action, you can nest input definitions inside the
|
76
|
+
# action to attach inputs and validation to the actions
|
77
|
+
#
|
78
|
+
# action "status", :description => "Restarts a Service" do
|
79
|
+
# display :always
|
80
|
+
#
|
81
|
+
# input "service",
|
82
|
+
# :prompt => "Service Action",
|
83
|
+
# :description => "The action to perform",
|
84
|
+
# :type => :list,
|
85
|
+
# :optional => true,
|
86
|
+
# :list => ["start", "stop", "restart", "status"]
|
87
|
+
#
|
88
|
+
# output "status"
|
89
|
+
# :description => "The status of the service after the action"
|
90
|
+
#
|
91
|
+
# end
|
92
|
+
def action(name, input, &block)
|
93
|
+
raise "Action needs a :description" unless input.include?(:description)
|
94
|
+
|
95
|
+
unless @actions.include?(name)
|
96
|
+
@actions[name] = {}
|
97
|
+
@actions[name][:action] = name
|
98
|
+
@actions[name][:input] = {}
|
99
|
+
@actions[name][:output] = {}
|
100
|
+
@actions[name][:display] = :failed
|
101
|
+
@actions[name][:description] = input[:description]
|
102
|
+
end
|
103
|
+
|
104
|
+
# if a block is passed it might be creating input methods, call it
|
105
|
+
# we set @current_action so the input block can know what its talking
|
106
|
+
# to, this is probably an epic hack, need to improve.
|
107
|
+
@current_action = name
|
108
|
+
block.call if block_given?
|
109
|
+
@current_action = nil
|
110
|
+
end
|
111
|
+
|
112
|
+
# Registers an input argument for a given action
|
113
|
+
#
|
114
|
+
# See the documentation for action for how to use this
|
115
|
+
def input(argument, properties)
|
116
|
+
raise "Cannot figure out what action input #{argument} belongs to" unless @current_action
|
117
|
+
|
118
|
+
action = @current_action
|
119
|
+
|
120
|
+
[:prompt, :description, :type, :optional].each do |arg|
|
121
|
+
raise "Input needs a :#{arg}" unless properties.include?(arg)
|
122
|
+
end
|
123
|
+
|
124
|
+
@actions[action][:input][argument] = {:prompt => properties[:prompt],
|
125
|
+
:description => properties[:description],
|
126
|
+
:type => properties[:type],
|
127
|
+
:optional => properties[:optional]}
|
128
|
+
|
129
|
+
case properties[:type]
|
130
|
+
when :string
|
131
|
+
raise "Input type :string needs a :validation argument" unless properties.include?(:validation)
|
132
|
+
raise "Input type :string needs a :maxlength argument" unless properties.include?(:maxlength)
|
133
|
+
|
134
|
+
@actions[action][:input][argument][:validation] = properties[:validation]
|
135
|
+
@actions[action][:input][argument][:maxlength] = properties[:maxlength]
|
136
|
+
|
137
|
+
when :list
|
138
|
+
raise "Input type :list needs a :list argument" unless properties.include?(:list)
|
139
|
+
|
140
|
+
@actions[action][:input][argument][:list] = properties[:list]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Registers an output argument for a given action
|
145
|
+
#
|
146
|
+
# See the documentation for action for how to use this
|
147
|
+
def output(argument, properties)
|
148
|
+
raise "Cannot figure out what action input #{argument} belongs to" unless @current_action
|
149
|
+
raise "Output #{argument} needs a description argument" unless properties.include?(:description)
|
150
|
+
raise "Output #{argument} needs a display_as argument" unless properties.include?(:display_as)
|
151
|
+
|
152
|
+
action = @current_action
|
153
|
+
|
154
|
+
@actions[action][:output][argument] = {:description => properties[:description],
|
155
|
+
:display_as => properties[:display_as]}
|
156
|
+
end
|
157
|
+
|
158
|
+
# Sets the display preference to either :ok, :failed, :flatten or :always
|
159
|
+
# operates on action level
|
160
|
+
def display(pref)
|
161
|
+
# defaults to old behavior, complain if its supplied and invalid
|
162
|
+
unless [:ok, :failed, :flatten, :always].include?(pref)
|
163
|
+
raise "Display preference #{pref} is not valid, should be :ok, :failed, :flatten or :always"
|
164
|
+
end
|
165
|
+
|
166
|
+
action = @current_action
|
167
|
+
@actions[action][:display] = pref
|
168
|
+
end
|
169
|
+
|
170
|
+
# Generates help using the template based on the data
|
171
|
+
# created with metadata and input
|
172
|
+
def help(template)
|
173
|
+
template = IO.read(template)
|
174
|
+
meta = @meta
|
175
|
+
actions = @actions
|
176
|
+
|
177
|
+
erb = ERB.new(template, 0, '%')
|
178
|
+
erb.result(binding)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Returns an array of actions this agent support
|
182
|
+
def actions
|
183
|
+
@actions.keys
|
184
|
+
end
|
185
|
+
|
186
|
+
# Returns the interface for a specific action
|
187
|
+
def action_interface(name)
|
188
|
+
@actions[name] || {}
|
189
|
+
end
|
190
|
+
|
191
|
+
# Helper to use the DDL to figure out if the remote call should be
|
192
|
+
# allowed based on action name and inputs.
|
193
|
+
def validate_request(action, arguments)
|
194
|
+
# is the action known?
|
195
|
+
unless actions.include?(action)
|
196
|
+
raise DDLValidationError, "Attempted to call action #{action} for #{@agent} but it's not declared in the DDL"
|
197
|
+
end
|
198
|
+
|
199
|
+
input = action_interface(action)[:input]
|
200
|
+
|
201
|
+
input.keys.each do |key|
|
202
|
+
unless input[key][:optional]
|
203
|
+
unless arguments.keys.include?(key)
|
204
|
+
raise DDLValidationError, "Action #{action} needs a #{key} argument"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# validate strings, lists and booleans, we'll add more types of validators when
|
209
|
+
# all the use cases are clear
|
210
|
+
#
|
211
|
+
# only does validation for arguments actually given, since some might
|
212
|
+
# be optional. We validate the presense of the argument earlier so
|
213
|
+
# this is a safe assumption, just to skip them.
|
214
|
+
#
|
215
|
+
# :string can have maxlength and regex. A maxlength of 0 will bypasss checks
|
216
|
+
# :list has a array of valid values
|
217
|
+
if arguments.keys.include?(key)
|
218
|
+
case input[key][:type]
|
219
|
+
when :string
|
220
|
+
raise DDLValidationError, "Input #{key} should be a string" unless arguments[key].is_a?(String)
|
221
|
+
|
222
|
+
if input[key][:maxlength].to_i > 0
|
223
|
+
if arguments[key].size > input[key][:maxlength].to_i
|
224
|
+
raise DDLValidationError, "Input #{key} is longer than #{input[key][:maxlength]} character(s)"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
unless arguments[key].match(Regexp.new(input[key][:validation]))
|
229
|
+
raise DDLValidationError, "Input #{key} does not match validation regex #{input[key][:validation]}"
|
230
|
+
end
|
231
|
+
|
232
|
+
when :list
|
233
|
+
unless input[key][:list].include?(arguments[key])
|
234
|
+
raise DDLValidationError, "Input #{key} doesn't match list #{input[key][:list].join(', ')}"
|
235
|
+
end
|
236
|
+
|
237
|
+
when :boolean
|
238
|
+
unless [TrueClass, FalseClass].include?(arguments[key].class)
|
239
|
+
raise DDLValidationError, "Input #{key} should be a boolean"
|
240
|
+
end
|
241
|
+
|
242
|
+
when :integer
|
243
|
+
raise DDLValidationError, "Input #{key} should be a integer" unless arguments[key].is_a?(Fixnum)
|
244
|
+
|
245
|
+
when :float
|
246
|
+
raise DDLValidationError, "Input #{key} should be a floating point number" unless arguments[key].is_a?(Float)
|
247
|
+
|
248
|
+
when :number
|
249
|
+
raise DDLValidationError, "Input #{key} should be a number" unless arguments[key].is_a?(Numeric)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
true
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
@@ -0,0 +1,339 @@
|
|
1
|
+
module MCollective
|
2
|
+
module RPC
|
3
|
+
# Various utilities for the RPC system
|
4
|
+
class Helpers
|
5
|
+
# Checks in PATH returns true if the command is found
|
6
|
+
def self.command_in_path?(command)
|
7
|
+
found = ENV["PATH"].split(File::PATH_SEPARATOR).map do |p|
|
8
|
+
File.exist?(File.join(p, command))
|
9
|
+
end
|
10
|
+
|
11
|
+
found.include?(true)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Parse JSON output as produced by printrpc and extract
|
15
|
+
# the "sender" of each rpc response
|
16
|
+
#
|
17
|
+
# The simplist valid JSON based data would be:
|
18
|
+
#
|
19
|
+
# [
|
20
|
+
# {"sender" => "example.com"},
|
21
|
+
# {"sender" => "another.com"}
|
22
|
+
# ]
|
23
|
+
def self.extract_hosts_from_json(json)
|
24
|
+
hosts = JSON.parse(json)
|
25
|
+
|
26
|
+
raise "JSON hosts list is not an array" unless hosts.is_a?(Array)
|
27
|
+
|
28
|
+
hosts.map do |host|
|
29
|
+
raise "JSON host list is not an array of Hashes" unless host.is_a?(Hash)
|
30
|
+
raise "JSON host list does not have senders in it" unless host.include?("sender")
|
31
|
+
|
32
|
+
host["sender"]
|
33
|
+
end.uniq
|
34
|
+
end
|
35
|
+
|
36
|
+
# Given an array of something, make sure each is a string
|
37
|
+
# chomp off any new lines and return just the array of hosts
|
38
|
+
def self.extract_hosts_from_array(hosts)
|
39
|
+
[hosts].flatten.map do |host|
|
40
|
+
raise "#{host} should be a string" unless host.is_a?(String)
|
41
|
+
host.chomp
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Figures out the columns and liens of the current tty
|
46
|
+
#
|
47
|
+
# Returns [0, 0] if it can't figure it out or if you're
|
48
|
+
# not running on a tty
|
49
|
+
def self.terminal_dimensions
|
50
|
+
return [0, 0] unless STDOUT.tty?
|
51
|
+
|
52
|
+
return [80, 40] if Util.windows?
|
53
|
+
|
54
|
+
if ENV["COLUMNS"] && ENV["LINES"]
|
55
|
+
return [ENV["COLUMNS"].to_i, ENV["LINES"].to_i]
|
56
|
+
|
57
|
+
elsif ENV["TERM"] && command_in_path?("tput")
|
58
|
+
return [`tput cols`.to_i, `tput lines`.to_i]
|
59
|
+
|
60
|
+
elsif command_in_path?('stty')
|
61
|
+
return `stty size`.scan(/\d+/).map {|s| s.to_i }
|
62
|
+
else
|
63
|
+
return [0, 0]
|
64
|
+
end
|
65
|
+
rescue
|
66
|
+
[0, 0]
|
67
|
+
end
|
68
|
+
|
69
|
+
# Return color codes, if the config color= option is false
|
70
|
+
# just return a empty string
|
71
|
+
def self.color(code)
|
72
|
+
colorize = Config.instance.color
|
73
|
+
|
74
|
+
colors = {:red => "[31m",
|
75
|
+
:green => "[32m",
|
76
|
+
:yellow => "[33m",
|
77
|
+
:cyan => "[36m",
|
78
|
+
:bold => "[1m",
|
79
|
+
:reset => "[0m"}
|
80
|
+
|
81
|
+
if colorize
|
82
|
+
return colors[code] || ""
|
83
|
+
else
|
84
|
+
return ""
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Helper to return a string in specific color
|
89
|
+
def self.colorize(code, msg)
|
90
|
+
"#{self.color(code)}#{msg}#{self.color(:reset)}"
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns a blob of text representing the results in a standard way
|
94
|
+
#
|
95
|
+
# It tries hard to do sane things so you often
|
96
|
+
# should not need to write your own display functions
|
97
|
+
#
|
98
|
+
# If the agent you are getting results for has a DDL
|
99
|
+
# it will use the hints in there to do the right thing specifically
|
100
|
+
# it will look at the values of display in the DDL to choose
|
101
|
+
# when to show results
|
102
|
+
#
|
103
|
+
# If you do not have a DDL you can pass these flags:
|
104
|
+
#
|
105
|
+
# printrpc exim.mailq, :flatten => true
|
106
|
+
# printrpc exim.mailq, :verbose => true
|
107
|
+
#
|
108
|
+
# If you've asked it to flatten the result it will not print sender
|
109
|
+
# hostnames, it will just print the result as if it's one huge result,
|
110
|
+
# handy for things like showing a combined mailq.
|
111
|
+
def self.rpcresults(result, flags = {})
|
112
|
+
flags = {:verbose => false, :flatten => false, :format => :console}.merge(flags)
|
113
|
+
|
114
|
+
result_text = ""
|
115
|
+
ddl = nil
|
116
|
+
|
117
|
+
# if running in verbose mode, just use the old style print
|
118
|
+
# no need for all the DDL helpers obfuscating the result
|
119
|
+
if flags[:format] == :json
|
120
|
+
if STDOUT.tty?
|
121
|
+
result_text = JSON.pretty_generate(result)
|
122
|
+
else
|
123
|
+
result_text = result.to_json
|
124
|
+
end
|
125
|
+
else
|
126
|
+
if flags[:verbose]
|
127
|
+
result_text = old_rpcresults(result, flags)
|
128
|
+
else
|
129
|
+
[result].flatten.each do |r|
|
130
|
+
begin
|
131
|
+
ddl ||= DDL.new(r.agent).action_interface(r.action.to_s)
|
132
|
+
|
133
|
+
sender = r[:sender]
|
134
|
+
status = r[:statuscode]
|
135
|
+
message = r[:statusmsg]
|
136
|
+
display = ddl[:display]
|
137
|
+
result = r[:data]
|
138
|
+
|
139
|
+
# appand the results only according to what the DDL says
|
140
|
+
case display
|
141
|
+
when :ok
|
142
|
+
if status == 0
|
143
|
+
result_text << text_for_result(sender, status, message, result, ddl)
|
144
|
+
end
|
145
|
+
|
146
|
+
when :failed
|
147
|
+
if status > 0
|
148
|
+
result_text << text_for_result(sender, status, message, result, ddl)
|
149
|
+
end
|
150
|
+
|
151
|
+
when :always
|
152
|
+
result_text << text_for_result(sender, status, message, result, ddl)
|
153
|
+
|
154
|
+
when :flatten
|
155
|
+
result_text << text_for_flattened_result(status, result)
|
156
|
+
|
157
|
+
end
|
158
|
+
rescue Exception => e
|
159
|
+
# no DDL so just do the old style print unchanged for
|
160
|
+
# backward compat
|
161
|
+
result_text = old_rpcresults(result, flags)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
result_text
|
168
|
+
end
|
169
|
+
|
170
|
+
# Return text representing a result
|
171
|
+
def self.text_for_result(sender, status, msg, result, ddl)
|
172
|
+
statusses = ["",
|
173
|
+
colorize(:red, "Request Aborted"),
|
174
|
+
colorize(:yellow, "Unknown Action"),
|
175
|
+
colorize(:yellow, "Missing Request Data"),
|
176
|
+
colorize(:yellow, "Invalid Request Data"),
|
177
|
+
colorize(:red, "Unknown Request Status")]
|
178
|
+
|
179
|
+
result_text = "%-40s %s\n" % [sender, statusses[status]]
|
180
|
+
result_text << " %s\n" % [colorize(:yellow, msg)] unless msg == "OK"
|
181
|
+
|
182
|
+
# only print good data, ignore data that results from failure
|
183
|
+
if [0, 1].include?(status)
|
184
|
+
if result.is_a?(Hash)
|
185
|
+
# figure out the lengths of the display as strings, we'll use
|
186
|
+
# it later to correctly justify the output
|
187
|
+
lengths = result.keys.map do |k|
|
188
|
+
begin
|
189
|
+
ddl[:output][k][:display_as].size
|
190
|
+
rescue
|
191
|
+
k.to_s.size
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
result.keys.each do |k|
|
196
|
+
# get all the output fields nicely lined up with a
|
197
|
+
# 3 space front padding
|
198
|
+
begin
|
199
|
+
display_as = ddl[:output][k][:display_as]
|
200
|
+
rescue
|
201
|
+
display_as = k.to_s
|
202
|
+
end
|
203
|
+
|
204
|
+
display_length = display_as.size
|
205
|
+
padding = lengths.max - display_length + 3
|
206
|
+
result_text << " " * padding
|
207
|
+
|
208
|
+
result_text << "#{display_as}:"
|
209
|
+
|
210
|
+
if [String, Numeric].include?(result[k].class)
|
211
|
+
lines = result[k].to_s.split("\n")
|
212
|
+
|
213
|
+
if lines.empty?
|
214
|
+
result_text << "\n"
|
215
|
+
else
|
216
|
+
lines.each_with_index do |line, i|
|
217
|
+
i == 0 ? padtxt = " " : padtxt = " " * (padding + display_length + 2)
|
218
|
+
|
219
|
+
result_text << "#{padtxt}#{line}\n"
|
220
|
+
end
|
221
|
+
end
|
222
|
+
else
|
223
|
+
padding = " " * (lengths.max + 5)
|
224
|
+
result_text << " " << result[k].pretty_inspect.split("\n").join("\n" << padding) << "\n"
|
225
|
+
end
|
226
|
+
end
|
227
|
+
else
|
228
|
+
result_text << "\n\t" + result.pretty_inspect.split("\n").join("\n\t")
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
result_text << "\n"
|
233
|
+
result_text
|
234
|
+
end
|
235
|
+
|
236
|
+
# Returns text representing a flattened result of only good data
|
237
|
+
def self.text_for_flattened_result(status, result)
|
238
|
+
result_text = ""
|
239
|
+
|
240
|
+
if status <= 1
|
241
|
+
unless result.is_a?(String)
|
242
|
+
result_text << result.pretty_inspect
|
243
|
+
else
|
244
|
+
result_text << result
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# Backward compatible display block for results without a DDL
|
250
|
+
def self.old_rpcresults(result, flags = {})
|
251
|
+
result_text = ""
|
252
|
+
|
253
|
+
if flags[:flatten]
|
254
|
+
result.each do |r|
|
255
|
+
if r[:statuscode] <= 1
|
256
|
+
data = r[:data]
|
257
|
+
|
258
|
+
unless data.is_a?(String)
|
259
|
+
result_text << data.pretty_inspect
|
260
|
+
else
|
261
|
+
result_text << data
|
262
|
+
end
|
263
|
+
else
|
264
|
+
result_text << r.pretty_inspect
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
result_text << ""
|
269
|
+
else
|
270
|
+
[result].flatten.each do |r|
|
271
|
+
|
272
|
+
if flags[:verbose]
|
273
|
+
result_text << "%-40s: %s\n" % [r[:sender], r[:statusmsg]]
|
274
|
+
|
275
|
+
if r[:statuscode] <= 1
|
276
|
+
r[:data].pretty_inspect.split("\n").each {|m| result_text += " #{m}"}
|
277
|
+
result_text << "\n\n"
|
278
|
+
elsif r[:statuscode] == 2
|
279
|
+
# dont print anything, no useful data to display
|
280
|
+
# past what was already shown
|
281
|
+
elsif r[:statuscode] == 3
|
282
|
+
# dont print anything, no useful data to display
|
283
|
+
# past what was already shown
|
284
|
+
elsif r[:statuscode] == 4
|
285
|
+
# dont print anything, no useful data to display
|
286
|
+
# past what was already shown
|
287
|
+
else
|
288
|
+
result_text << " #{r[:statusmsg]}"
|
289
|
+
end
|
290
|
+
else
|
291
|
+
unless r[:statuscode] == 0
|
292
|
+
result_text << "%-40s %s\n" % [r[:sender], colorize(:red, r[:statusmsg])]
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
result_text << ""
|
299
|
+
end
|
300
|
+
|
301
|
+
# Add SimpleRPC common options
|
302
|
+
def self.add_simplerpc_options(parser, options)
|
303
|
+
parser.separator ""
|
304
|
+
|
305
|
+
# add SimpleRPC specific options to all clients that use our library
|
306
|
+
parser.on('--np', '--no-progress', 'Do not show the progress bar') do |v|
|
307
|
+
options[:progress_bar] = false
|
308
|
+
end
|
309
|
+
|
310
|
+
parser.on('--one', '-1', 'Send request to only one discovered nodes') do |v|
|
311
|
+
options[:mcollective_limit_targets] = 1
|
312
|
+
end
|
313
|
+
|
314
|
+
parser.on('--batch SIZE', Integer, 'Do requests in batches') do |v|
|
315
|
+
options[:batch_size] = v
|
316
|
+
end
|
317
|
+
|
318
|
+
parser.on('--batch-sleep SECONDS', Float, 'Sleep time between batches') do |v|
|
319
|
+
options[:batch_sleep_time] = v
|
320
|
+
end
|
321
|
+
|
322
|
+
parser.on('--limit-nodes COUNT', '--ln', 'Send request to only a subset of nodes, can be a percentage') do |v|
|
323
|
+
raise "Invalid limit specified: #{v} valid limits are /^\d+%*$/" unless v =~ /^\d+%*$/
|
324
|
+
|
325
|
+
if v =~ /^\d+$/
|
326
|
+
options[:mcollective_limit_targets] = v.to_i
|
327
|
+
else
|
328
|
+
options[:mcollective_limit_targets] = v
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
parser.on('--json', '-j', 'Produce JSON output') do |v|
|
333
|
+
options[:progress_bar] = false
|
334
|
+
options[:output_format] = :json
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|