1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
|
require 'pathname'
require_relative '../../puppet/util/rubygems'
require_relative '../../puppet/util/warnings'
require_relative '../../puppet/pops/adaptable'
require_relative '../../puppet/concurrent/synchronized'
# An adapter that ties the module_directories cache to the environment where the modules are parsed. This
# adapter ensures that the life-cycle of this cache doesn't exceed the life-cycle of the environment.
#
# @api private
class Puppet::Util::ModuleDirectoriesAdapter < Puppet::Pops::Adaptable::Adapter
attr_accessor :directories
def self.create_adapter(env)
adapter = super(env)
adapter.directories = env.modulepath.flat_map do |dir|
Dir.glob(File.join(dir, '*', 'lib'))
end
adapter
end
end
# Autoload paths, either based on names or all at once.
class Puppet::Util::Autoload
include Puppet::Concurrent::Synchronized
extend Puppet::Concurrent::Synchronized
@loaded = {}
class << self
attr_accessor :loaded
def gem_source
@gem_source ||= Puppet::Util::RubyGems::Source.new
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 loaded?(path)
path = cleanpath(path).chomp('.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.
# @api private
def mark_loaded(name, file)
name = cleanpath(name).chomp('.rb')
file = File.expand_path(file)
$LOADED_FEATURES << file unless $LOADED_FEATURES.include?(file)
loaded[name] = [file, File.mtime(file)]
end
# @api private
def changed?(name, env)
name = cleanpath(name).chomp('.rb')
return true unless loaded.include?(name)
file, old_mtime = loaded[name]
return true unless file == get_file(name, env)
begin
old_mtime.to_i != File.mtime(file).to_i
rescue Errno::ENOENT
true
end
end
# Load a single plugin by name. We use 'load' here so we can reload a
# given plugin.
def load_file(name, env)
file = get_file(name.to_s, env)
return false unless file
begin
mark_loaded(name, file)
Kernel.load file
return true
rescue SystemExit,NoMemoryError
raise
rescue Exception => detail
message = _("Could not autoload %{name}: %{detail}") % { name: name, detail: detail }
Puppet.log_exception(detail, message)
raise Puppet::Error, message, detail.backtrace
end
end
def loadall(path, env)
# Load every instance of everything we can find.
files_to_load(path, env).each do |file|
name = file.chomp(".rb")
load_file(name, env) unless loaded?(name)
end
end
def reload_changed(env)
loaded.keys.each do |file|
if changed?(file, env)
load_file(file, env)
end
end
end
# Get the correct file to load for a given path
# returns nil if no file is found
# @api private
def get_file(name, env)
name = name + '.rb' unless name =~ /\.rb$/
path = search_directories(env).find { |dir| Puppet::FileSystem.exist?(File.join(dir, name)) }
path and File.join(path, name)
end
def files_to_load(path, env)
search_directories(env).map {|dir| files_in_dir(dir, path) }.flatten.uniq
end
# @api private
def files_in_dir(dir, path)
dir = Pathname.new(Puppet::FileSystem.expand_path(dir))
Dir.glob(File.join(dir, path, "*.rb")).collect do |file|
Pathname.new(file).relative_path_from(dir).to_s
end
end
# @api private
def module_directories(env)
raise ArgumentError, "Autoloader requires an environment" unless env
Puppet::Util::ModuleDirectoriesAdapter.adapt(env).directories
end
# @api private
def gem_directories
gem_source.directories
end
# @api private
def search_directories(env)
# This is a little bit of a hack. Basically, the autoloader is being
# called indirectly during application bootstrapping when we do things
# such as check "features". However, during bootstrapping, we haven't
# yet parsed all of the command line parameters nor the config files,
# and thus we don't yet know with certainty what the module path is.
# This should be irrelevant during bootstrapping, because anything that
# we are attempting to load during bootstrapping should be something
# that we ship with puppet, and thus the module path is irrelevant.
#
# In the long term, I think the way that we want to handle this is to
# have the autoloader ignore the module path in all cases where it is
# not specifically requested (e.g., by a constructor param or
# something)... because there are very few cases where we should
# actually be loading code from the module path. However, until that
# happens, we at least need a way to prevent the autoloader from
# attempting to access the module path before it is initialized. For
# now we are accomplishing that by calling the
# "app_defaults_initialized?" method on the main puppet Settings object.
# --cprice 2012-03-16
if Puppet.settings.app_defaults_initialized?
gem_directories + module_directories(env) + $LOAD_PATH
else
gem_directories + $LOAD_PATH
end
end
# Normalize a path. This converts ALT_SEPARATOR to SEPARATOR on Windows
# and eliminates unnecessary parts of a path.
def cleanpath(path)
Pathname.new(path).cleanpath.to_s
end
end
attr_accessor :object, :path
def initialize(obj, path)
@path = path.to_s
raise ArgumentError, _("Autoload paths cannot be fully qualified") if Puppet::Util.absolute_path?(@path)
@object = obj
end
def load(name, env)
self.class.load_file(expand(name), env)
end
# Load all instances from a path of Autoload.search_directories matching the
# relative path this Autoloader was initialized with. For example, if we
# have created a Puppet::Util::Autoload for Puppet::Type::User with a path of
# 'puppet/provider/user', the search_directories path will be searched for
# all ruby files matching puppet/provider/user/*.rb and they will then be
# loaded from the first directory in the search path providing them. So
# earlier entries in the search path may shadow later entries.
#
# This uses require, rather than load, so that already-loaded files don't get
# reloaded unnecessarily.
def loadall(env)
self.class.loadall(@path, env)
end
def loaded?(name)
self.class.loaded?(expand(name))
end
# @api private
def changed?(name, env)
self.class.changed?(expand(name), env)
end
def files_to_load(env)
self.class.files_to_load(@path, env)
end
def expand(name)
::File.join(@path, name.to_s)
end
end
|