6 # Intialize a blank set of options if its the first time used
7 # else returns active options
8 def application_options
9 intialize_application_options unless @application_options
13 # set an option in the options hash
14 def []=(option, value)
15 intialize_application_options unless @application_options
16 @application_options[option] = value
19 # retrieves a specific option
21 intialize_application_options unless @application_options
22 @application_options[option]
25 # Sets the application description, there can be only one
26 # description per application so multiple calls will just
27 # change the description
28 def description(descr)
29 self[:description] = descr
32 # Supplies usage information, calling multiple times will
33 # create multiple usage lines in --help output
38 def exclude_argument_sections(*sections)
39 sections = [sections].flatten
42 raise "Unknown CLI argument section #{s}" unless ["rpc", "common", "filter"].include?(s)
45 intialize_application_options unless @application_options
46 self[:exclude_arg_sections] = sections
49 # Wrapper to create command line options
51 # - name: varaible name that will be used to access the option value
52 # - description: textual info shown in --help
53 # - arguments: a list of possible arguments that can be used
54 # to activate this option
55 # - type: a data type that ObjectParser understand of :bool or :array
56 # - required: true or false if this option has to be supplied
57 # - validate: a proc that will be called with the value used to validate
61 # :description => "The foo option"
62 # :arguments => ["--foo ARG"]
64 # after this the value supplied will be in configuration[:foo]
65 def option(name, arguments)
71 :validate => Proc.new { true }}
73 arguments.each_pair{|k,v| opt[k] = v}
75 self[:cli_arguments] << opt
78 # Creates an empty set of options
79 def intialize_application_options
80 @application_options = {:description => nil,
83 :exclude_arg_sections => []}
87 # The application configuration built from CLI arguments
89 @application_configuration ||= {}
90 @application_configuration
93 # The active options hash used for MC::Client and other configuration
98 # Calls the supplied block in an option for validation, an error raised
99 # will log to STDERR and exit the application
100 def validate_option(blk, name, value)
101 validation_result = blk.call(value)
103 unless validation_result == true
104 STDERR.puts "Validation of #{name} failed: #{validation_result}"
109 # Creates a standard options hash, pass in a block to add extra headings etc
112 oparser = Optionparser.new({:verbose => false, :progress_bar => true}, "filter", application_options[:exclude_arg_sections])
114 options = oparser.parse do |parser, options|
116 yield(parser, options)
119 RPC::Helpers.add_simplerpc_options(parser, options) unless application_options[:exclude_arg_sections].include?("rpc")
122 return oparser.parser.help if help
126 post_option_parser(configuration) if respond_to?(:post_option_parser)
129 rescue Exception => e
130 application_failure(e)
133 # Builds an ObjectParser config, parse the CLI options and validates based
134 # on the option config
135 def application_parse_options(help=false)
136 @options ||= {:verbose => false}
138 @options = clioptions(help) do |parser, options|
139 parser.define_head application_description if application_description
145 application_usage.each do |u|
146 parser.separator "Usage: #{u}"
152 parser.separator "Application Options" unless application_cli_arguments.empty?
154 parser.define_tail ""
155 parser.define_tail "The Marionette Collective #{MCollective.version}"
158 application_cli_arguments.each do |carg|
163 # if a default is set from the application set it up front
164 if carg.include?(:default)
165 configuration[carg[:name]] = carg[:default]
168 # :arguments are multiple possible ones
169 if carg[:arguments].is_a?(Array)
170 carg[:arguments].each {|a| opts_array << a}
172 opts_array << carg[:arguments]
175 # type was given and its not one of our special types, just pass it onto optparse
176 opts_array << carg[:type] if carg[:type] && ![:boolean, :bool, :array].include?(carg[:type])
178 opts_array << carg[:description]
180 # Handle our special types else just rely on the optparser to handle the types
181 if [:bool, :boolean].include?(carg[:type])
182 parser.send(*opts_array) do |v|
183 validate_option(carg[:validate], carg[:name], v)
185 configuration[carg[:name]] = v
188 elsif carg[:type] == :array
189 parser.send(*opts_array) do |v|
190 validate_option(carg[:validate], carg[:name], v)
192 configuration[carg[:name]] = [] unless configuration.include?(carg[:name])
193 configuration[carg[:name]] << v
197 parser.send(*opts_array) do |v|
198 validate_option(carg[:validate], carg[:name], v)
200 configuration[carg[:name]] = v
207 def validate_cli_options
208 # Check all required parameters were set
209 validation_passed = true
210 application_cli_arguments.each do |carg|
211 # Check for required arguments
213 unless configuration[ carg[:name] ]
214 validation_passed = false
215 STDERR.puts "The #{carg[:name]} option is mandatory"
220 unless validation_passed
221 STDERR.puts "\nPlease run with --help for detailed help"
228 # Retrieves the full hash of application options
229 def application_options
230 self.class.application_options
233 # Retrieve the current application description
234 def application_description
235 application_options[:description]
238 # Return the current usage text false if nothing is set
239 def application_usage
240 usage = application_options[:usage]
242 usage.empty? ? false : usage
245 # Returns an array of all the arguments built using
247 def application_cli_arguments
248 application_options[:cli_arguments]
251 # Handles failure, if we're far enough in the initialization
252 # phase it will log backtraces if its in verbose mode only
253 def application_failure(e, err_dest=STDERR)
254 # peole can use exit() anywhere and not get nasty backtraces as a result
255 if e.is_a?(SystemExit)
260 if e.is_a?(CodedError)
261 err_dest.puts "\nThe %s application failed to run: %s: %s\n" % [ Util.colorize(:bold, $0), Util.colorize(:bold, e.code), Util.colorize(:red, e.to_s)]
265 err_dest.puts "Use the 'mco doc %s' command for details about this error" % e.code
267 err_dest.puts "Use the 'mco doc %s' command for details about this error, use -v for full error backtrace details" % e.code
271 err_dest.puts "\nThe %s application failed to run: %s\n" % [ Util.colorize(:bold, $0), Util.colorize(:red, e.to_s)]
273 err_dest.puts "\nThe %s application failed to run, use -v for full error backtrace details: %s\n" % [ Util.colorize(:bold, $0), Util.colorize(:red, e.to_s)]
277 if options.nil? || options[:verbose]
278 e.backtrace.first << Util.colorize(:red, " <----")
279 err_dest.puts "\n%s %s" % [ Util.colorize(:red, e.to_s), Util.colorize(:bold, "(#{e.class.to_s})")]
280 e.backtrace.each{|l| err_dest.puts "\tfrom #{l}"}
289 application_parse_options(true)
292 # The main logic loop, builds up the options, validate configuration and calls
293 # the main as supplied by the user. Disconnects when done and pass any exception
294 # onto the application_failure helper
296 application_parse_options
298 validate_configuration(configuration) if respond_to?(:validate_configuration)
300 Util.setup_windows_sleeper if Util.windows?
306 rescue Exception => e
307 application_failure(e)
311 MCollective::PluginManager["connector_plugin"].disconnect
315 # Fake abstract class that logs if the user tries to use an application without
316 # supplying a main override method.
318 STDERR.puts "Applications need to supply a 'main' method"
323 request_stats = {:discoverytime => 0,
326 :failcount => 0}.merge(stats.to_hash)
328 return 4 if request_stats[:discoverytime] == 0 && request_stats[:responses] == 0
329 return 3 if request_stats[:discovered] > 0 && request_stats[:responses] == 0
330 return 2 if request_stats[:discovered] > 0 && request_stats[:failcount] > 0
331 return 1 if request_stats[:discovered] == 0
332 return 0 if request_stats[:discoverytime] == 0 && request_stats[:discovered] == request_stats[:okcount]
333 return 0 if request_stats[:discovered] == request_stats[:okcount]
336 # A helper that creates a consistent exit code for applications by looking at an
337 # instance of MCollective::RPC::Stats
339 # Exit with 0 if nodes were discovered and all passed
340 # Exit with 0 if no discovery were done and > 0 responses were received, all ok
341 # Exit with 1 if no nodes were discovered
342 # Exit with 2 if nodes were discovered but some RPC requests failed
343 # Exit with 3 if nodes were discovered, but no responses received
344 # Exit with 4 if no discovery were done and no responses were received
346 exit(halt_code(stats))
349 # Wrapper around MC::RPC#rpcclient that forcably supplies our options hash
350 # if someone forgets to pass in options in an application the filters and other
351 # cli options wouldnt take effect which could have a disasterous outcome
352 def rpcclient(agent, flags = {})
353 flags[:options] = options unless flags.include?(:options)
354 flags[:exit_on_failure] = false