77e51502efabfcda86c872b533293dd13f3f8baa
[packages/precise/mcollective.git] / lib / mcollective / pluginmanager.rb
1 module MCollective
2   # A simple plugin manager, it stores one plugin each of a specific type
3   # the idea is that we can only have one security provider, one connector etc.
4   module PluginManager
5     @plugins = {}
6
7     # Adds a plugin to the list of plugins, we expect a hash like:
8     #
9     #    {:type => "base",
10     #     :class => foo.new}
11     #
12     # or like:
13     #    {:type => "base",
14     #     :class => "Foo::Bar"}
15     #
16     # In the event that we already have a class with the given type
17     # an exception will be raised.
18     #
19     # If the :class passed is a String then we will delay instantiation
20     # till the first time someone asks for the plugin, this is because most likely
21     # the registration gets done by inherited() hooks, at which point the plugin class is not final.
22     #
23     # If we were to do a .new here the Class initialize method would get called and not
24     # the plugins, we there for only initialize the classes when they get requested via []
25     #
26     # By default all plugin instances are cached and returned later so there's
27     # always a single instance.  You can pass :single_instance => false when
28     # calling this to instruct it to always return a new instance when a copy
29     # is requested.  This only works with sending a String for :class.
30     def self.<<(plugin)
31       plugin[:single_instance] = true unless plugin.include?(:single_instance)
32
33       type = plugin[:type]
34       klass = plugin[:class]
35       single = plugin[:single_instance]
36
37       raise("Plugin #{type} already loaded") if @plugins.include?(type)
38
39
40       # If we get a string then store 'nil' as the instance, signalling that we'll
41       # create the class later on demand.
42       if klass.is_a?(String)
43         @plugins[type] = {:loadtime => Time.now, :class => klass, :instance => nil, :single => single}
44         Log.debug("Registering plugin #{type} with class #{klass} single_instance: #{single}")
45       else
46         @plugins[type] = {:loadtime => Time.now, :class => klass.class, :instance => klass, :single => true}
47         Log.debug("Registering plugin #{type} with class #{klass.class} single_instance: true")
48       end
49     end
50
51     # Removes a plugim the list
52     def self.delete(plugin)
53       @plugins.delete(plugin) if @plugins.include?(plugin)
54     end
55
56     # Finds out if we have a plugin with the given name
57     def self.include?(plugin)
58       @plugins.include?(plugin)
59     end
60
61     # Provides a list of plugins we know about
62     def self.pluginlist
63       @plugins.keys.sort
64     end
65
66     # deletes all registered plugins
67     def self.clear
68       @plugins.clear
69     end
70
71     # Gets a plugin by type
72     def self.[](plugin)
73       raise("No plugin #{plugin} defined") unless @plugins.include?(plugin)
74
75       klass = @plugins[plugin][:class]
76
77       if @plugins[plugin][:single]
78         # Create an instance of the class if one hasn't been done before
79         if @plugins[plugin][:instance] == nil
80           Log.debug("Returning new plugin #{plugin} with class #{klass}")
81           @plugins[plugin][:instance] = create_instance(klass)
82         else
83           Log.debug("Returning cached plugin #{plugin} with class #{klass}")
84         end
85
86         @plugins[plugin][:instance]
87       else
88         Log.debug("Returning new plugin #{plugin} with class #{klass}")
89         create_instance(klass)
90       end
91     end
92
93     # use eval to create an instance of a class
94     def self.create_instance(klass)
95       begin
96         eval("#{klass}.new")
97       rescue Exception => e
98         raise("Could not create instance of plugin #{klass}: #{e}")
99       end
100     end
101
102     # Finds plugins in all configured libdirs
103     #
104     #   find("agent")
105     #
106     # will return an array of just agent names, for example:
107     #
108     #   ["puppetd", "package"]
109     #
110     # Can also be used to find files of other extensions:
111     #
112     #   find("agent", "ddl")
113     #
114     # Will return the same list but only of files with extension .ddl
115     # in the agent subdirectory
116     def self.find(type, extension="rb")
117       extension = ".#{extension}" unless extension.match(/^\./)
118
119       plugins = []
120
121       Config.instance.libdir.each do |libdir|
122         plugdir = File.join([libdir, "mcollective", type.to_s])
123         next unless File.directory?(plugdir)
124
125         Dir.new(plugdir).grep(/#{extension}$/).map do |plugin|
126           plugins << File.basename(plugin, extension)
127         end
128       end
129
130       plugins.sort.uniq
131     end
132
133     # Finds and loads from disk all plugins from all libdirs that match
134     # certain criteria.
135     #
136     #    find_and_load("pluginpackager")
137     #
138     # Will find all .rb files in the libdir/mcollective/pluginpackager/
139     # directory in all libdirs and load them from disk.
140     #
141     # You can influence what plugins get loaded using a block notation:
142     #
143     #    find_and_load("pluginpackager") do |plugin|
144     #       plugin.match(/puppet/)
145     #    end
146     #
147     # This will load only plugins matching /puppet/
148     def self.find_and_load(type, extension="rb")
149       extension = ".#{extension}" unless extension.match(/^\./)
150
151       klasses = find(type, extension).map do |plugin|
152         if block_given?
153           next unless yield(plugin)
154         end
155
156         "%s::%s::%s" % [ "MCollective", type.capitalize, plugin.capitalize ]
157       end.compact
158
159       klasses.sort.uniq.each {|klass| loadclass(klass, true)}
160     end
161
162     # Loads a class from file by doing some simple search/replace
163     # on class names and then doing a require.
164     def self.loadclass(klass, squash_failures=false)
165       fname = klass.gsub("::", "/").downcase + ".rb"
166
167       Log.debug("Loading #{klass} from #{fname}")
168
169       load fname
170     rescue Exception => e
171       Log.error("Failed to load #{klass}: #{e}")
172       raise unless squash_failures
173     end
174
175     # Grep's over the plugin list and returns the list found
176     def self.grep(regex)
177       @plugins.keys.grep(regex)
178     end
179   end
180 end