Added mcollective 2.3.1 package
[packages/trusty/mcollective.git] / lib / mcollective / pluginmanager.rb
diff --git a/lib/mcollective/pluginmanager.rb b/lib/mcollective/pluginmanager.rb
new file mode 100644 (file)
index 0000000..77e5150
--- /dev/null
@@ -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