summaryrefslogtreecommitdiffstats
path: root/lib/puppet/util/autoload.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/puppet/util/autoload.rb')
-rw-r--r--lib/puppet/util/autoload.rb156
1 files changed, 156 insertions, 0 deletions
diff --git a/lib/puppet/util/autoload.rb b/lib/puppet/util/autoload.rb
new file mode 100644
index 000000000..f0dd0a5c5
--- /dev/null
+++ b/lib/puppet/util/autoload.rb
@@ -0,0 +1,156 @@
+require 'puppet/util/warnings'
+require 'puppet/util/cacher'
+
+# Autoload paths, either based on names or all at once.
+class Puppet::Util::Autoload
+ require 'puppet/util/autoload/file_cache'
+
+ include Puppet::Util
+ include Puppet::Util::Warnings
+ include Puppet::Util::Cacher
+ include Puppet::Util::Autoload::FileCache
+
+ @autoloaders = {}
+ @loaded = []
+
+ class << self
+ attr_reader :autoloaders
+ private :autoloaders
+ end
+
+ # Send [], []=, and :clear to the @autloaders hash
+ Puppet::Util.classproxy self, :autoloaders, "[]", "[]="
+
+ # List all loaded files.
+ def self.list_loaded
+ @loaded.sort { |a,b| a[0] <=> b[0] }.collect do |path, hash|
+ "#{path}: #{hash[:file]}"
+ end
+ end
+
+ # Has a given path been loaded? This is used for testing whether a
+ # changed file should be loaded or just ignored. This is only
+ # used in network/client/master, when downloading plugins, to
+ # see if a given plugin is currently loaded and thus should be
+ # reloaded.
+ def self.loaded?(path)
+ path = path.to_s.sub(/\.rb$/, '')
+ @loaded.include?(path)
+ end
+
+ # Save the fact that a given path has been loaded. This is so
+ # we can load downloaded plugins if they've already been loaded
+ # into memory.
+ def self.loaded(file)
+ $" << file + ".rb" unless $".include?(file)
+ @loaded << file unless @loaded.include?(file)
+ end
+
+ attr_accessor :object, :path, :objwarn, :wrap
+
+ def initialize(obj, path, options = {})
+ @path = path.to_s
+ raise ArgumentError, "Autoload paths cannot be fully qualified" if @path !~ /^\w/
+ @object = obj
+
+ self.class[obj] = self
+
+ options.each do |opt, value|
+ opt = opt.intern if opt.is_a? String
+ begin
+ self.send(opt.to_s + "=", value)
+ rescue NoMethodError
+ raise ArgumentError, "#{opt} is not a valid option"
+ end
+ end
+
+ @wrap = true unless defined?(@wrap)
+ end
+
+ # Load a single plugin by name. We use 'load' here so we can reload a
+ # given plugin.
+ def load(name,env=nil)
+ path = name.to_s + ".rb"
+
+ searchpath(env).each do |dir|
+ file = File.join(dir, path)
+ next unless file_exist?(file)
+ begin
+ Kernel.load file, @wrap
+ name = symbolize(name)
+ loaded name, file
+ return true
+ rescue SystemExit,NoMemoryError
+ raise
+ rescue Exception => detail
+ puts detail.backtrace if Puppet[:trace]
+ raise Puppet::Error, "Could not autoload #{name}: #{detail}"
+ end
+ end
+ false
+ end
+
+ # Mark the named object as loaded. Note that this supports unqualified
+ # queries, while we store the result as a qualified query in the class.
+ def loaded(name, file)
+ self.class.loaded(File.join(@path, name.to_s))
+ end
+
+ # Indicate whether the specfied plugin has been loaded.
+ def loaded?(name)
+ self.class.loaded?(File.join(@path, name.to_s))
+ end
+
+ # Load all instances that we can. This uses require, rather than load,
+ # so that already-loaded files don't get reloaded unnecessarily.
+ def loadall
+ # Load every instance of everything we can find.
+ searchpath.each do |dir|
+ Dir.glob("#{dir}/*.rb").each do |file|
+ name = File.basename(file).sub(".rb", '').intern
+ next if loaded?(name)
+ begin
+ Kernel.require file
+ loaded(name, file)
+ rescue SystemExit,NoMemoryError
+ raise
+ rescue Exception => detail
+ puts detail.backtrace if Puppet[:trace]
+ raise Puppet::Error, "Could not autoload #{file}: #{detail}"
+ end
+ end
+ end
+ end
+
+ # The list of directories to search through for loadable plugins.
+ def searchpath(env=nil)
+ search_directories(env).collect { |d| File.join(d, @path) }.find_all { |d| FileTest.directory?(d) }
+ end
+
+ def module_directories(env=nil)
+ # We have to require this late in the process because otherwise we might have
+ # load order issues.
+ require 'puppet/node/environment'
+
+ real_env = Puppet::Node::Environment.new(env)
+
+ # We're using a per-thread cache of said module directories, so that
+ # we don't scan the filesystem each time we try to load something with
+ # this autoload instance. But since we don't want to cache for the eternity
+ # this env_module_directories gets reset after the compilation on the master.
+ # This is also reset after an agent ran.
+ # One of the side effect of this change is that this module directories list will be
+ # shared among all autoload that we have running at a time. But that won't be an issue
+ # as by definition those directories are shared by all autoload.
+ Thread.current[:env_module_directories] ||= {}
+ Thread.current[:env_module_directories][real_env] ||= real_env.modulepath.collect do |dir|
+ Dir.entries(dir).reject { |f| f =~ /^\./ }.collect { |f| File.join(dir, f) }
+ end.flatten.collect { |d| [File.join(d, "plugins"), File.join(d, "lib")] }.flatten.find_all do |d|
+ FileTest.directory?(d)
+ end
+ end
+
+ def search_directories(env=nil)
+ [module_directories(env), Puppet[:libdir].split(File::PATH_SEPARATOR), $LOAD_PATH].flatten
+ end
+end