X-Git-Url: https://review.fuel-infra.org/gitweb?a=blobdiff_plain;f=lib%2Fmcollective%2Fpluginmanager.rb;fp=lib%2Fmcollective%2Fpluginmanager.rb;h=77e51502efabfcda86c872b533293dd13f3f8baa;hb=b87d2f4e68281062df1913440ca5753ae63314a9;hp=0000000000000000000000000000000000000000;hpb=ab0ea530b8ac956091f17b104ab2311336cfc250;p=packages%2Fprecise%2Fmcollective.git diff --git a/lib/mcollective/pluginmanager.rb b/lib/mcollective/pluginmanager.rb new file mode 100644 index 0000000..77e5150 --- /dev/null +++ b/lib/mcollective/pluginmanager.rb @@ -0,0 +1,180 @@ +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