diff --git a/lib/puppet/pops/loaders.rb b/lib/puppet/pops/loaders.rb index 5fa1d3c61..6109fcb13 100644 --- a/lib/puppet/pops/loaders.rb +++ b/lib/puppet/pops/loaders.rb @@ -1,238 +1,238 @@ class Puppet::Pops::Loaders class LoaderError < Puppet::Error; end attr_reader :static_loader attr_reader :puppet_system_loader attr_reader :public_environment_loader attr_reader :private_environment_loader def initialize(environment) # The static loader can only be changed after a reboot @@static_loader ||= Puppet::Pops::Loader::StaticLoader.new() # Create the set of loaders # 1. Puppet, loads from the "running" puppet - i.e. bundled functions, types, extension points and extensions # Does not change without rebooting the service running puppet. # @@puppet_system_loader ||= create_puppet_system_loader() # 2. Environment loader - i.e. what is bound across the environment, may change for each setup # TODO: loaders need to work when also running in an agent doing catalog application. There is no # concept of environment the same way as when running as a master (except when doing apply). # The creation mechanisms should probably differ between the two. # @private_environment_loader = create_environment_loader(environment) # 3. module loaders are set up from the create_environment_loader, they register themselves end # Clears the cached static and puppet_system loaders (to enable testing) # def self.clear @@static_loader = nil @@puppet_system_loader = nil end def static_loader @@static_loader end def puppet_system_loader @@puppet_system_loader end def public_loader_for_module(module_name) md = @module_resolver[module_name] || (return nil) # Note, this loader is not resolved until there is interest in the visibility of entities from the # perspective of something contained in the module. (Many request may pass through a module loader # without it loading anything. # See {#private_loader_for_module}, and not in {#configure_loaders_for_modules} md.public_loader end def private_loader_for_module(module_name) md = @module_resolver[module_name] || (return nil) # Since there is interest in the visibility from the perspective of entities contained in the # module, it must be resolved (to provide this visibility). # See {#configure_loaders_for_modules} unless md.resolved? @module_resolver.resolve(md) end md.private_loader end private def create_puppet_system_loader() Puppet::Pops::Loader::ModuleLoaders.system_loader_from(static_loader, self) end def create_environment_loader(environment) # This defines where to start parsing/evaluating - the "initial import" (to use 3x terminology) # Is either a reference to a single .pp file, or a directory of manifests. If the environment becomes # a module and can hold functions, types etc. then these are available across all other modules without # them declaring this dependency - it is however valuable to be able to treat it the same way # bindings and other such system related configuration. # This is further complicated by the many options available: # - The environment may not have a directory, the code comes from one appointed 'manifest' (site.pp) # - The environment may have a directory and also point to a 'manifest' # - The code to run may be set in settings (code) # Further complication is that there is nothing specifying what the visibility is into # available modules. (3x is everyone sees everything). # Puppet binder currently reads confdir/bindings - that is bad, it should be using the new environment support. # The environment is not a namespace, so give it a nil "module_name" module_name = nil loader_name = "environment:#{environment.name}" - env_conf = Puppet.lookup(:environments).get_conf('production') + env_conf = Puppet.lookup(:environments).get_conf(environment.name) if env_conf.nil? || !env_conf.is_a?(Puppet::Settings::EnvironmentConf) # Not a real directory environment, cannot work as a module TODO: Drop when legacy env are dropped? loader = Puppet::Pops::Loader::SimpleEnvironmentLoader.new(puppet_system_loader, loader_name) else # View the environment as a module to allow loading from it - this module is always called 'environment' loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(puppet_system_loader, self, 'environment', env_conf.path_to_env) end # An environment has a module path even if it has a null loader configure_loaders_for_modules(loader, environment) # modules should see this loader @public_environment_loader = loader # Code in the environment gets to see all modules (since there is no metadata for the environment) # but since this is not given to the module loaders, they can not load global code (since they can not # have prior knowledge about this loader = Puppet::Pops::Loader::DependencyLoader.new(loader, "environment", @module_resolver.all_module_loaders()) # The module loader gets the private loader via a lazy operation to look up the module's private loader. # This does not work for an environment since it is not resolved the same way. # TODO: The EnvironmentLoader could be a specialized loader instead of using a ModuleLoader to do the work. # This is subject to future design - an Environment may move more in the direction of a Module. @public_environment_loader.private_loader = loader loader end def configure_loaders_for_modules(parent_loader, environment) @module_resolver = mr = ModuleResolver.new() environment.modules.each do |puppet_module| # Create data about this module md = LoaderModuleData.new(puppet_module) mr[puppet_module.name] = md md.public_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(parent_loader, self, md.name, md.path) end # NOTE: Do not resolve all modules here - this is wasteful if only a subset of modules / functions are used # The resolution is triggered by asking for a module's private loader, since this means there is interest # in the visibility from that perspective. # If later, it is wanted that all resolutions should be made up-front (to capture errors eagerly, this # can be introduced (better for production), but may be irritating in development mode. end # =LoaderModuleData # Information about a Module and its loaders. # TODO: should have reference to real model element containing all module data; this is faking it # TODO: Should use Puppet::Module to get the metadata (as a hash) - a somewhat blunt instrument, but that is # what is available with a reasonable API. # class LoaderModuleData attr_accessor :state attr_accessor :public_loader attr_accessor :private_loader attr_accessor :resolutions # The Puppet::Module this LoaderModuleData represents in the loader configuration attr_reader :puppet_module # @param puppet_module [Puppet::Module] the module instance for the module being represented # def initialize(puppet_module) @state = :initial @puppet_module = puppet_module @resolutions = [] @public_loader = nil @private_loader = nil end def name @puppet_module.name end def version @puppet_module.version end def path @puppet_module.path end def resolved? @state == :resolved end def restrict_to_dependencies? @puppet_module.has_metadata? end def unmet_dependencies? @puppet_module.unmet_dependencies.any? end def dependency_names @puppet_module.dependencies_as_modules.collect(&:name) end end # Resolves module loaders - resolution of model dependencies is done by Puppet::Module # class ModuleResolver def initialize() @index = {} @all_module_loaders = nil end def [](name) @index[name] end def []=(name, module_data) @index[name] = module_data end def all_module_loaders @all_module_loaders ||= @index.values.map {|md| md.public_loader } end def resolve(module_data) if module_data.resolved? return else module_data.private_loader = if module_data.restrict_to_dependencies? create_loader_with_only_dependencies_visible(module_data) else create_loader_with_all_modules_visible(module_data) end end end private def create_loader_with_all_modules_visible(from_module_data) Puppet.debug("ModuleLoader: module '#{from_module_data.name}' has unknown dependencies - it will have all other modules visible") Puppet::Pops::Loader::DependencyLoader.new(from_module_data.public_loader, from_module_data.name, all_module_loaders()) end def create_loader_with_only_dependencies_visible(from_module_data) if from_module_data.unmet_dependencies? Puppet.warning("ModuleLoader: module '#{from_module_data.name}' has unresolved dependencies"+ " - it will only see those that are resolved."+ " Use 'puppet module list --tree' to see information about modules") end dependency_loaders = from_module_data.dependency_names.collect { |name| @index[name].public_loader } Puppet::Pops::Loader::DependencyLoader.new(from_module_data.public_loader, from_module_data.name, dependency_loaders) end end end diff --git a/lib/puppet/settings/environment_conf.rb b/lib/puppet/settings/environment_conf.rb index 49d45878c..7c81bf46d 100644 --- a/lib/puppet/settings/environment_conf.rb +++ b/lib/puppet/settings/environment_conf.rb @@ -1,180 +1,181 @@ # Configuration settings for a single directory Environment. # @api private class Puppet::Settings::EnvironmentConf VALID_SETTINGS = [:modulepath, :manifest, :config_version, :environment_timeout, :environment_data_provider].freeze # Given a path to a directory environment, attempts to load and parse an # environment.conf in ini format, and return an EnvironmentConf instance. # # An environment.conf is optional, so if the file itself is missing, or # empty, an EnvironmentConf with default values will be returned. # # @note logs warnings if the environment.conf contains any ini sections, # or has settings other than the three handled for directory environments # (:manifest, :modulepath, :config_version) # # @param path_to_env [String] path to the directory environment # @param global_module_path [Array] the installation's base modulepath # setting, appended to default environment modulepaths # @return [EnvironmentConf] the parsed EnvironmentConf object def self.load_from(path_to_env, global_module_path) path_to_env = File.expand_path(path_to_env) conf_file = File.join(path_to_env, 'environment.conf') config = nil begin config = Puppet.settings.parse_file(conf_file) validate(conf_file, config) section = config.sections[:main] rescue Errno::ENOENT # environment.conf is an optional file end new(path_to_env, section, global_module_path) end # Provides a configuration object tied directly to the passed environment. # Configuration values are exactly those returned by the environment object, # without interpolation. This is a special case for the default configured # environment returned by the Puppet::Environments::StaticPrivate loader. def self.static_for(environment, environment_timeout = 0) Static.new(environment, environment_timeout) end attr_reader :section, :path_to_env, :global_modulepath # Create through EnvironmentConf.load_from() def initialize(path_to_env, section, global_module_path) @path_to_env = path_to_env @section = section @global_module_path = global_module_path end def manifest puppet_conf_manifest = Pathname.new(Puppet.settings.value(:default_manifest)) disable_per_environment_manifest = Puppet.settings.value(:disable_per_environment_manifest) fallback_manifest_directory = if puppet_conf_manifest.absolute? puppet_conf_manifest.to_s else File.join(@path_to_env, puppet_conf_manifest.to_s) end if disable_per_environment_manifest environment_conf_manifest = absolute(raw_setting(:manifest)) if environment_conf_manifest && fallback_manifest_directory != environment_conf_manifest errmsg = ["The 'disable_per_environment_manifest' setting is true, but the", "environment located at #{@path_to_env} has a manifest setting in its", "environment.conf of '#{environment_conf_manifest}' which does not match", "the default_manifest setting '#{puppet_conf_manifest}'. If this", "environment is expecting to find modules in", "'#{environment_conf_manifest}', they will not be available!"] Puppet.err(errmsg.join(' ')) end fallback_manifest_directory.to_s else get_setting(:manifest, fallback_manifest_directory) do |manifest| absolute(manifest) end end end def environment_timeout # gen env specific config or use the default value get_setting(:environment_timeout, Puppet.settings.value(:environment_timeout)) do |ttl| # munges the string form statically without really needed the settings system, only # its ability to munge "4s, 3m, 5d, and 'unlimited' into seconds - if already munged into # numeric form, the TTLSetting handles that. Puppet::Settings::TTLSetting.munge(ttl, 'environment_timeout') end end def environment_data_provider get_setting(:environment_data_provider, Puppet.settings.value(:environment_data_provider)) do |value| value end end def modulepath default_modulepath = [File.join(@path_to_env, "modules")] + @global_module_path get_setting(:modulepath, default_modulepath) do |modulepath| path = modulepath.kind_of?(String) ? modulepath.split(File::PATH_SEPARATOR) : modulepath path.map { |p| absolute(p) }.join(File::PATH_SEPARATOR) end end def config_version get_setting(:config_version) do |config_version| absolute(config_version) end end def raw_setting(setting_name) setting = section.setting(setting_name) if section setting.value if setting end private def self.validate(path_to_conf_file, config) valid = true section_keys = config.sections.keys main = config.sections[:main] if section_keys.size > 1 Puppet.warning("Invalid sections in environment.conf at '#{path_to_conf_file}'. Environment conf may not have sections. The following sections are being ignored: '#{(section_keys - [:main]).join(',')}'") valid = false end extraneous_settings = main.settings.map(&:name) - VALID_SETTINGS if !extraneous_settings.empty? Puppet.warning("Invalid settings in environment.conf at '#{path_to_conf_file}'. The following unknown setting(s) are being ignored: #{extraneous_settings.join(', ')}") valid = false end return valid end def get_setting(setting_name, default = nil) value = raw_setting(setting_name) value ||= default yield value end def absolute(path) return nil if path.nil? if path =~ /^\$/ # Path begins with $something interpolatable path else File.expand_path(path, @path_to_env) end end # Models configuration for an environment that is not loaded from a directory. # # @api private class Static - attr_reader :environment_timeout + attr_reader :environment_timeout, :environment_data_provider - def initialize(environment, environment_timeout) + def initialize(environment, environment_timeout, environment_data_provider = 'none') @environment = environment @environment_timeout = environment_timeout + @environment_data_provider = environment_data_provider end def manifest @environment.manifest end def modulepath @environment.modulepath.join(File::PATH_SEPARATOR) end def config_version @environment.config_version end end end