module MCollective # A simple plugin manager, it stores one plugin each of a specific type # the idea is that we can only have one security provider, one connector etc. module PluginManager @plugins = {} # Adds a plugin to the list of plugins, we expect a hash like: # # {:type => "base", # :class => foo.new} # # or like: # {:type => "base", # :class => "Foo::Bar"} # # In the event that we already have a class with the given type # an exception will be raised. # # If the :class passed is a String then we will delay instantiation # till the first time someone asks for the plugin, this is because most likely # the registration gets done by inherited() hooks, at which point the plugin class is not final. # # If we were to do a .new here the Class initialize method would get called and not # the plugins, we there for only initialize the classes when they get requested via [] # # By default all plugin instances are cached and returned later so there's # always a single instance. You can pass :single_instance => false when # calling this to instruct it to always return a new instance when a copy # is requested. This only works with sending a String for :class. def self.<<(plugin) plugin[:single_instance] = true unless plugin.include?(:single_instance) type = plugin[:type] klass = plugin[:class] single = plugin[:single_instance] raise("Plugin #{type} already loaded") if @plugins.include?(type) # If we get a string then store 'nil' as the instance, signalling that we'll # create the class later on demand. if klass.is_a?(String) @plugins[type] = {:loadtime => Time.now, :class => klass, :instance => nil, :single => single} Log.debug("Registering plugin #{type} with class #{klass} single_instance: #{single}") else @plugins[type] = {:loadtime => Time.now, :class => klass.class, :instance => klass, :single => true} Log.debug("Registering plugin #{type} with class #{klass.class} single_instance: true") end end # Removes a plugim the list def self.delete(plugin) @plugins.delete(plugin) if @plugins.include?(plugin) end # Finds out if we have a plugin with the given name def self.include?(plugin) @plugins.include?(plugin) end # Provides a list of plugins we know about def self.pluginlist @plugins.keys.sort end # deletes all registered plugins def self.clear @plugins.clear end # Gets a plugin by type def self.[](plugin) raise("No plugin #{plugin} defined") unless @plugins.include?(plugin) klass = @plugins[plugin][:class] if @plugins[plugin][:single] # Create an instance of the class if one hasn't been done before if @plugins[plugin][:instance] == nil Log.debug("Returning new plugin #{plugin} with class #{klass}") @plugins[plugin][:instance] = create_instance(klass) else Log.debug("Returning cached plugin #{plugin} with class #{klass}") end @plugins[plugin][:instance] else Log.debug("Returning new plugin #{plugin} with class #{klass}") create_instance(klass) end end # use eval to create an instance of a class def self.create_instance(klass) begin eval("#{klass}.new") rescue Exception => e raise("Could not create instance of plugin #{klass}: #{e}") end end # Finds plugins in all configured libdirs # # find("agent") # # will return an array of just agent names, for example: # # ["puppetd", "package"] # # Can also be used to find files of other extensions: # # find("agent", "ddl") # # Will return the same list but only of files with extension .ddl # in the agent subdirectory def self.find(type, extension="rb") extension = ".#{extension}" unless extension.match(/^\./) plugins = [] Config.instance.libdir.each do |libdir| plugdir = File.join([libdir, "mcollective", type.to_s]) next unless File.directory?(plugdir) Dir.new(plugdir).grep(/#{extension}$/).map do |plugin| plugins << File.basename(plugin, extension) end end plugins.sort.uniq end # Finds and loads from disk all plugins from all libdirs that match # certain criteria. # # find_and_load("pluginpackager") # # Will find all .rb files in the libdir/mcollective/pluginpackager/ # directory in all libdirs and load them from disk. # # You can influence what plugins get loaded using a block notation: # # find_and_load("pluginpackager") do |plugin| # plugin.match(/puppet/) # end # # This will load only plugins matching /puppet/ def self.find_and_load(type, extension="rb") extension = ".#{extension}" unless extension.match(/^\./) klasses = find(type, extension).map do |plugin| if block_given? next unless yield(plugin) end "%s::%s::%s" % [ "MCollective", type.capitalize, plugin.capitalize ] end.compact klasses.sort.uniq.each {|klass| loadclass(klass, true)} end # Loads a class from file by doing some simple search/replace # on class names and then doing a require. def self.loadclass(klass, squash_failures=false) fname = klass.gsub("::", "/").downcase + ".rb" Log.debug("Loading #{klass} from #{fname}") load fname rescue Exception => e Log.error("Failed to load #{klass}: #{e}") raise unless squash_failures end # Grep's over the plugin list and returns the list found def self.grep(regex) @plugins.keys.grep(regex) end end end