module MCollective class Applications def self.[](appname) load_application(appname) PluginManager["#{appname}_application"] end def self.run(appname) load_config begin load_application(appname) rescue Exception => e e.backtrace.first << RPC::Helpers.colorize(:red, " <----") STDERR.puts "Application '#{appname}' failed to load:" STDERR.puts STDERR.puts RPC::Helpers.colorize(:red, " #{e} (#{e.class})") STDERR.puts STDERR.puts " %s" % [e.backtrace.join("\n ")] exit 1 end PluginManager["#{appname}_application"].run end def self.load_application(appname) return if PluginManager.include?("#{appname}_application") load_config PluginManager.loadclass "MCollective::Application::#{appname.capitalize}" PluginManager << {:type => "#{appname}_application", :class => "MCollective::Application::#{appname.capitalize}"} end # Returns an array of applications found in the lib dirs def self.list load_config applist = [] Config.instance.libdir.each do |libdir| applicationdir = "#{libdir}/mcollective/application" next unless File.directory?(applicationdir) Dir.entries(applicationdir).grep(/\.rb$/).each do |application| applist << File.basename(application, ".rb") end end applist rescue SystemExit exit 1 rescue Exception => e STDERR.puts "Failed to generate application list: #{e.class}: #{e}" exit 1 end # Filters a string of opts out using Shellwords # keeping only things related to --config and -c def self.filter_extra_options(opts) res = "" words = Shellwords.shellwords(opts) words.each_with_index do |word,idx| if word == "-c" return "--config=#{words[idx + 1]}" elsif word == "--config" return "--config=#{words[idx + 1]}" elsif word =~ /\-c=/ return word elsif word =~ /\-\-config=/ return word end end return "" end # We need to know the config file in order to know the libdir # so that we can find applications. # # The problem is the CLI might be stuffed with options only the # app in the libdir might understand so we have a chicken and # egg situation. # # We're parsing and filtering MCOLLECTIVE_EXTRA_OPTS removing # all but config related options and parsing the options looking # just for the config file. # # We're handling failures gracefully and finally restoring the # ARG and MCOLLECTIVE_EXTRA_OPTS to the state they were before # we started parsing. # # This is mostly a hack, when we're redoing how config works # this stuff should be made less sucky def self.load_config return if Config.instance.configured original_argv = ARGV.clone original_extra_opts = ENV["MCOLLECTIVE_EXTRA_OPTS"].clone rescue nil configfile = nil parser = OptionParser.new parser.on("--config CONFIG", "-c", "Config file") do |f| configfile = f end parser.program_name = $0 parser.on("--help") # avoid option parsers own internal version handling that sux parser.on("-v", "--verbose") if original_extra_opts begin # optparse will parse the whole ENV in one go and refuse # to play along with the retry trick I do below so in # order to handle unknown options properly I parse out # only -c and --config deleting everything else and # then restore the environment variable later when I # am done with it ENV["MCOLLECTIVE_EXTRA_OPTS"] = filter_extra_options(ENV["MCOLLECTIVE_EXTRA_OPTS"].clone) parser.environment("MCOLLECTIVE_EXTRA_OPTS") rescue Exception => e Log.error("Failed to parse MCOLLECTIVE_EXTRA_OPTS: #{e}") end ENV["MCOLLECTIVE_EXTRA_OPTS"] = original_extra_opts.clone end begin parser.parse! rescue OptionParser::InvalidOption => e retry end ARGV.clear original_argv.each {|a| ARGV << a} configfile = Util.config_file_for_user unless configfile Config.instance.loadconfig(configfile) end end end