diff --git a/lib/puppet.rb b/lib/puppet.rb index d24740c57..0f39db031 100644 --- a/lib/puppet.rb +++ b/lib/puppet.rb @@ -1,250 +1,253 @@ require 'puppet/version' # see the bottom of the file for further inclusions # Also see the new Vendor support - towards the end # require 'facter' require 'puppet/error' require 'puppet/util' require 'puppet/util/autoload' require 'puppet/settings' require 'puppet/util/feature' require 'puppet/util/suidmanager' require 'puppet/util/run_mode' require 'puppet/external/pson/common' require 'puppet/external/pson/version' require 'puppet/external/pson/pure' #------------------------------------------------------------ # the top-level module # # all this really does is dictate how the whole system behaves, through # preferences for things like debugging # # it's also a place to find top-level commands like 'debug' # The main Puppet class. Everything is contained here. # # @api public module Puppet require 'puppet/file_system' require 'puppet/context' require 'puppet/environments' class << self include Puppet::Util attr_reader :features end # the hash that determines how our system behaves @@settings = Puppet::Settings.new # Note: It's important that these accessors (`self.settings`, `self.[]`) are # defined before we try to load any "features" (which happens a few lines below), # because the implementation of the features loading may examine the values of # settings. def self.settings @@settings end # Get the value for a setting # # @param [Symbol] param the setting to retrieve # # @api public def self.[](param) if param == :debug return Puppet::Util::Log.level == :debug else return @@settings[param] end end require 'puppet/util/logging' extend Puppet::Util::Logging # Setup facter's logging Puppet::Util::Logging.setup_facter_logging! # The feature collection @features = Puppet::Util::Feature.new('puppet/feature') # Load the base features. require 'puppet/feature/base' # Store a new default value. def self.define_settings(section, hash) @@settings.define_settings(section, hash) end # setting access and stuff def self.[]=(param,value) @@settings[param] = value end def self.clear @@settings.clear end def self.debug=(value) if value Puppet::Util::Log.level=(:debug) else Puppet::Util::Log.level=(:notice) end end def self.run_mode # This sucks (the existence of this method); there are a lot of places in our code that branch based the value of # "run mode", but there used to be some really confusing code paths that made it almost impossible to determine # when during the lifecycle of a puppet application run the value would be set properly. A lot of the lifecycle # stuff has been cleaned up now, but it still seems frightening that we rely so heavily on this value. # # I'd like to see about getting rid of the concept of "run_mode" entirely, but there are just too many places in # the code that call this method at the moment... so I've settled for isolating it inside of the Settings class # (rather than using a global variable, as we did previously...). Would be good to revisit this at some point. # # --cprice 2012-03-16 Puppet::Util::RunMode[@@settings.preferred_run_mode] end # Load all of the settings. require 'puppet/defaults' # Initialize puppet's settings. This is intended only for use by external tools that are not # built off of the Faces API or the Puppet::Util::Application class. It may also be used # to initialize state so that a Face may be used programatically, rather than as a stand-alone # command-line tool. # # @api public # @param args [Array] the command line arguments to use for initialization # @return [void] def self.initialize_settings(args = []) do_initialize_settings_for_run_mode(:user, args) end # private helper method to provide the implementation details of initializing for a run mode, # but allowing us to control where the deprecation warning is issued def self.do_initialize_settings_for_run_mode(run_mode, args) Puppet.settings.initialize_global_settings(args) run_mode = Puppet::Util::RunMode[run_mode] Puppet.settings.initialize_app_defaults(Puppet::Settings.app_defaults_for_run_mode(run_mode)) Puppet.push_context(Puppet.base_context(Puppet.settings), "Initial context after settings initialization") Puppet::Parser::Functions.reset end private_class_method :do_initialize_settings_for_run_mode # Create a new type. Just proxy to the Type class. The mirroring query # code was deprecated in 2008, but this is still in heavy use. I suppose # this can count as a soft deprecation for the next dev. --daniel 2011-04-12 def self.newtype(name, options = {}, &block) Puppet.deprecation_warning("Puppet.newtype is deprecated and will be removed in a future release. Use Puppet::Type.newtype instead.") Puppet::Type.newtype(name, options, &block) end # Load vendored (setup paths, and load what is needed upfront). # See the Vendor class for how to add additional vendored gems/code require "puppet/vendor" Puppet::Vendor.load_vendored # The bindings used for initialization of puppet + # + # @param settings [Puppet::Settings,Hash] either a Puppet::Settings instance + # or a Hash of settings key/value pairs. # @api private def self.base_context(settings) - environments = settings[:environmentpath] - modulepath = Puppet::Node::Environment.split_path(settings[:basemodulepath]) + environmentpath = settings[:environmentpath] + basemodulepath = Puppet::Node::Environment.split_path(settings[:basemodulepath]) - if environments.empty? - loaders = [Puppet::Environments::Legacy.new] + if environmentpath.nil? || environmentpath.empty? + raise(Puppet::Error, "The environmentpath setting cannot be empty or nil.") else - loaders = Puppet::Environments::Directories.from_path(environments, modulepath) + loaders = Puppet::Environments::Directories.from_path(environmentpath, basemodulepath) # in case the configured environment (used for the default sometimes) # doesn't exist default_environment = Puppet[:environment].to_sym if default_environment == :production loaders << Puppet::Environments::StaticPrivate.new( Puppet::Node::Environment.create(Puppet[:environment].to_sym, [], Puppet::Node::Environment::NO_MANIFEST)) end end { :environments => Puppet::Environments::Cached.new(Puppet::Environments::Combined.new(*loaders)), :http_pool => proc { require 'puppet/network/http' Puppet::Network::HTTP::NoCachePool.new } } end # A simple set of bindings that is just enough to limp along to # initialization where the {base_context} bindings are put in place # @api private def self.bootstrap_context root_environment = Puppet::Node::Environment.create(:'*root*', [], Puppet::Node::Environment::NO_MANIFEST) { :current_environment => root_environment, :root_environment => root_environment } end # @param overrides [Hash] A hash of bindings to be merged with the parent context. # @param description [String] A description of the context. # @api private def self.push_context(overrides, description = "") @context.push(overrides, description) end # Return to the previous context. # @raise [StackUnderflow] if the current context is the root # @api private def self.pop_context @context.pop end # Lookup a binding by name or return a default value provided by a passed block (if given). # @api private def self.lookup(name, &block) @context.lookup(name, &block) end # @param bindings [Hash] A hash of bindings to be merged with the parent context. # @param description [String] A description of the context. # @yield [] A block executed in the context of the temporarily pushed bindings. # @api private def self.override(bindings, description = "", &block) @context.override(bindings, description, &block) end # @api private def self.mark_context(name) @context.mark(name) end # @api private def self.rollback_context(name) @context.rollback(name) end require 'puppet/node' # The single instance used for normal operation @context = Puppet::Context.new(bootstrap_context) end # This feels weird to me; I would really like for us to get to a state where there is never a "require" statement # anywhere besides the very top of a file. That would not be possible at the moment without a great deal of # effort, but I think we should strive for it and revisit this at some point. --cprice 2012-03-16 require 'puppet/indirector' require 'puppet/type' require 'puppet/resource' require 'puppet/parser' require 'puppet/network' require 'puppet/ssl' require 'puppet/module' require 'puppet/data_binding' require 'puppet/util/storage' require 'puppet/status' require 'puppet/file_bucket/file' require 'puppet/plugins' diff --git a/lib/puppet/environments.rb b/lib/puppet/environments.rb index 22017325c..13739579a 100644 --- a/lib/puppet/environments.rb +++ b/lib/puppet/environments.rb @@ -1,445 +1,390 @@ # @api private module Puppet::Environments class EnvironmentNotFound < Puppet::Error def initialize(environment_name, original = nil) environmentpath = Puppet[:environmentpath] super("Could not find a directory environment named '#{environment_name}' anywhere in the path: #{environmentpath}. Does the directory exist?", original) end end # @api private module EnvironmentCreator # Create an anonymous environment. # # @param module_path [String] A list of module directories separated by the # PATH_SEPARATOR # @param manifest [String] The path to the manifest # @return A new environment with the `name` `:anonymous` # # @api private def for(module_path, manifest) Puppet::Node::Environment.create(:anonymous, module_path.split(File::PATH_SEPARATOR), manifest) end end # Provide any common methods that loaders should have. It requires that any # classes that include this module implement get # @api private module EnvironmentLoader # @!macro loader_get_or_fail def get!(name) environment = get(name) if environment environment else raise EnvironmentNotFound, name end end end # @!macro [new] loader_search_paths # A list of indicators of where the loader is getting its environments from. # @return [Array] The URIs of the load locations # # @!macro [new] loader_list # @return [Array] All of the environments known # to the loader # # @!macro [new] loader_get # Find a named environment # # @param name [String,Symbol] The name of environment to find # @return [Puppet::Node::Environment, nil] the requested environment or nil # if it wasn't found # # @!macro [new] loader_get_conf # Attempt to obtain the initial configuration for the environment. Not all # loaders can provide this. # # @param name [String,Symbol] The name of the environment whose configuration # we are looking up # @return [Puppet::Setting::EnvironmentConf, nil] the configuration for the # requested environment, or nil if not found or no configuration is available # # @!macro [new] loader_get_or_fail # Find a named environment or raise # Puppet::Environments::EnvironmentNotFound when the named environment is # does not exist. # # @param name [String,Symbol] The name of environment to find # @return [Puppet::Node::Environment] the requested environment # A source of pre-defined environments. # # @api private class Static include EnvironmentCreator include EnvironmentLoader def initialize(*environments) @environments = environments end # @!macro loader_search_paths def search_paths ["data:text/plain,internal"] end # @!macro loader_list def list @environments end # @!macro loader_get def get(name) @environments.find do |env| env.name == name.intern end end # Returns a basic environment configuration object tied to the environment's # implementation values. Will not interpolate. # # @!macro loader_get_conf def get_conf(name) env = get(name) if env Puppet::Settings::EnvironmentConf.static_for(env) else nil end end end # A source of unlisted pre-defined environments. # # Used only for internal bootstrapping environments which are not relevant # to an end user (such as the fall back 'configured' environment). # # @api private class StaticPrivate < Static # Unlisted # # @!macro loader_list def list [] end end - # Old-style environments that come either from explicit stanzas in - # puppet.conf or from dynamic environments created from use of `$environment` - # in puppet.conf. - # - # @example Explicit Stanza - # [environment_name] - # modulepath=/var/my_env/modules - # - # @example Dynamic Environments - # [master] - # modulepath=/var/$environment/modules - # - # @api private - class Legacy - include EnvironmentCreator - - # @!macro loader_search_paths - def search_paths - ["file://#{Puppet[:config]}"] - end - - # @note The list of environments for the Legacy environments is always - # empty. - # - # @!macro loader_list - def list - [] - end - - # @note Because the Legacy system cannot list out all of its environments, - # get is able to return environments that are not returned by a call to - # {#list}. - # - # @!macro loader_get - def get(name) - Puppet::Node::Environment.new(name) - end - - # @note Because the Legacy system cannot list out all of its environments, - # this method will never fail and is only calling get directly. - # - # @!macro loader_get_or_fail - def get!(name) - get(name) - end - - # @note we could return something here, but since legacy environments - # are deprecated, there is no point. - # - # @!macro loader_get_conf - def get_conf(name) - nil - end - end - # Reads environments from a directory on disk. Each environment is # represented as a sub-directory. The environment's manifest setting is the # `manifest` directory of the environment directory. The environment's # modulepath setting is the global modulepath (from the `[master]` section # for the master) prepended with the `modules` directory of the environment # directory. # # @api private class Directories include EnvironmentLoader def initialize(environment_dir, global_module_path) @environment_dir = environment_dir @global_module_path = global_module_path end # Generate an array of directory loaders from a path string. # @param path [String] path to environment directories # @param global_module_path [Array] the global modulepath setting # @return [Array] An array # of configured directory loaders. def self.from_path(path, global_module_path) environments = path.split(File::PATH_SEPARATOR) environments.map do |dir| Puppet::Environments::Directories.new(dir, global_module_path) end end # @!macro loader_search_paths def search_paths ["file://#{@environment_dir}"] end # @!macro loader_list def list valid_directories.collect do |envdir| name = Puppet::FileSystem.basename_string(envdir).intern setting_values = Puppet.settings.values(name, Puppet.settings.preferred_run_mode) env = Puppet::Node::Environment.create( name, Puppet::Node::Environment.split_path(setting_values.interpolate(:modulepath)), setting_values.interpolate(:manifest), setting_values.interpolate(:config_version) ) env.watching = false env end end # @!macro loader_get def get(name) list.find { |env| env.name == name.intern } end # @!macro loader_get_conf def get_conf(name) valid_directories.each do |envdir| envname = Puppet::FileSystem.basename_string(envdir) if envname == name.to_s return Puppet::Settings::EnvironmentConf.load_from(envdir, @global_module_path) end end nil end private def valid_directories if Puppet::FileSystem.directory?(@environment_dir) Puppet::FileSystem.children(@environment_dir).select do |child| name = Puppet::FileSystem.basename_string(child) Puppet::FileSystem.directory?(child) && Puppet::Node::Environment.valid_name?(name) end else [] end end end # Combine together multiple loaders to act as one. # @api private class Combined include EnvironmentLoader def initialize(*loaders) @loaders = loaders end # @!macro loader_search_paths def search_paths @loaders.collect(&:search_paths).flatten end # @!macro loader_list def list @loaders.collect(&:list).flatten end # @!macro loader_get def get(name) @loaders.each do |loader| if env = loader.get(name) return env end end nil end # @!macro loader_get_conf def get_conf(name) @loaders.each do |loader| if conf = loader.get_conf(name) return conf end end nil end end class Cached include EnvironmentLoader class DefaultCacheExpirationService def created(env) end def expired?(env_name) false end def evicted(env_name) end end def self.cache_expiration_service=(service) @cache_expiration_service = service end def self.cache_expiration_service @cache_expiration_service || DefaultCacheExpirationService.new end def initialize(loader) @loader = loader @cache = {} @cache_expiration_service = Puppet::Environments::Cached.cache_expiration_service end # @!macro loader_list def list @loader.list end # @!macro loader_search_paths def search_paths @loader.search_paths end # @!macro loader_get def get(name) evict_if_expired(name) if result = @cache[name] return result.value elsif (result = @loader.get(name)) @cache[name] = entry(result) result end end # Clears the cache of the environment with the given name. # (The intention is that this could be used from a MANUAL cache eviction command (TBD) def clear(name) @cache.delete(name) end # Clears all cached environments. # (The intention is that this could be used from a MANUAL cache eviction command (TBD) def clear_all() @cache = {} end # This implementation evicts the cache, and always gets the current # configuration of the environment # # TODO: While this is wasteful since it # needs to go on a search for the conf, it is too disruptive to optimize # this. # # @!macro loader_get_conf def get_conf(name) evict_if_expired(name) @loader.get_conf(name) end # Creates a suitable cache entry given the time to live for one environment # def entry(env) @cache_expiration_service.created(env) ttl = (conf = get_conf(env.name)) ? conf.environment_timeout : Puppet.settings.value(:environment_timeout) Puppet.debug("Caching environment '#{env.name}' (cache ttl: #{ttl})") case ttl when 0 NotCachedEntry.new(env) # Entry that is always expired (avoids syscall to get time) when Float::INFINITY Entry.new(env) # Entry that never expires (avoids syscall to get time) else TTLEntry.new(env, ttl) end end # Evicts the entry if it has expired # Also clears caches in Settings that may prevent the entry from being updated def evict_if_expired(name) if (result = @cache[name]) && (result.expired? || @cache_expiration_service.expired?(name)) Puppet.debug("Evicting cache entry for environment '#{name}'") @cache.delete(name) @cache_expiration_service.evicted(name) Puppet.settings.clear_environment_settings(name) end end # Never evicting entry class Entry attr_reader :value def initialize(value) @value = value end def expired? false end end # Always evicting entry class NotCachedEntry < Entry def expired? true end end # Time to Live eviction policy entry class TTLEntry < Entry def initialize(value, ttl_seconds) super value @ttl = Time.now + ttl_seconds end def expired? Time.now > @ttl end end end end diff --git a/spec/integration/environments/default_manifest_spec.rb b/spec/integration/environments/default_manifest_spec.rb index 84c9e970f..c1eb38ad6 100644 --- a/spec/integration/environments/default_manifest_spec.rb +++ b/spec/integration/environments/default_manifest_spec.rb @@ -1,250 +1,215 @@ require 'spec_helper' module EnvironmentsDefaultManifestsSpec describe "default manifests" do context "puppet with default_manifest settings" do let(:confdir) { Puppet[:confdir] } let(:environmentpath) { File.expand_path("envdir", confdir) } context "relative default" do let(:testingdir) { File.join(environmentpath, "testing") } before(:each) do FileUtils.mkdir_p(testingdir) end it "reads manifest from ./manifest of a basic directory environment" do manifestsdir = File.join(testingdir, "manifests") FileUtils.mkdir_p(manifestsdir) File.open(File.join(manifestsdir, "site.pp"), "w") do |f| f.puts("notify { 'ManifestFromRelativeDefault': }") end File.open(File.join(confdir, "puppet.conf"), "w") do |f| f.puts("environmentpath=#{environmentpath}") end expect(a_catalog_compiled_for_environment('testing')).to( include_resource('Notify[ManifestFromRelativeDefault]') ) end end context "set absolute" do let(:testingdir) { File.join(environmentpath, "testing") } before(:each) do FileUtils.mkdir_p(testingdir) end it "reads manifest from an absolute default_manifest" do manifestsdir = File.expand_path("manifests", confdir) FileUtils.mkdir_p(manifestsdir) File.open(File.join(confdir, "puppet.conf"), "w") do |f| f.puts(<<-EOF) environmentpath=#{environmentpath} default_manifest=#{manifestsdir} EOF end File.open(File.join(manifestsdir, "site.pp"), "w") do |f| f.puts("notify { 'ManifestFromAbsoluteDefaultManifest': }") end expect(a_catalog_compiled_for_environment('testing')).to( include_resource('Notify[ManifestFromAbsoluteDefaultManifest]') ) end it "reads manifest from directory environment manifest when environment.conf manifest set" do default_manifestsdir = File.expand_path("manifests", confdir) File.open(File.join(confdir, "puppet.conf"), "w") do |f| f.puts(<<-EOF) environmentpath=#{environmentpath} default_manifest=#{default_manifestsdir} EOF end manifestsdir = File.join(testingdir, "special_manifests") FileUtils.mkdir_p(manifestsdir) File.open(File.join(manifestsdir, "site.pp"), "w") do |f| f.puts("notify { 'ManifestFromEnvironmentConfManifest': }") end File.open(File.join(testingdir, "environment.conf"), "w") do |f| f.puts("manifest=./special_manifests") end expect(a_catalog_compiled_for_environment('testing')).to( include_resource('Notify[ManifestFromEnvironmentConfManifest]') ) expect(Puppet[:default_manifest]).to eq(default_manifestsdir) end it "ignores manifests in the local ./manifests if default_manifest specifies another directory" do default_manifestsdir = File.expand_path("manifests", confdir) FileUtils.mkdir_p(default_manifestsdir) File.open(File.join(confdir, "puppet.conf"), "w") do |f| f.puts(<<-EOF) environmentpath=#{environmentpath} default_manifest=#{default_manifestsdir} EOF end File.open(File.join(default_manifestsdir, "site.pp"), "w") do |f| f.puts("notify { 'ManifestFromAbsoluteDefaultManifest': }") end implicit_manifestsdir = File.join(testingdir, "manifests") FileUtils.mkdir_p(implicit_manifestsdir) File.open(File.join(implicit_manifestsdir, "site.pp"), "w") do |f| f.puts("notify { 'ManifestFromImplicitRelativeEnvironmentManifestDirectory': }") end expect(a_catalog_compiled_for_environment('testing')).to( include_resource('Notify[ManifestFromAbsoluteDefaultManifest]') ) end end context "with disable_per_environment_manifest true" do let(:manifestsdir) { File.expand_path("manifests", confdir) } let(:testingdir) { File.join(environmentpath, "testing") } before(:each) do FileUtils.mkdir_p(testingdir) end before(:each) do FileUtils.mkdir_p(manifestsdir) File.open(File.join(confdir, "puppet.conf"), "w") do |f| f.puts(<<-EOF) environmentpath=#{environmentpath} default_manifest=#{manifestsdir} disable_per_environment_manifest=true EOF end File.open(File.join(manifestsdir, "site.pp"), "w") do |f| f.puts("notify { 'ManifestFromAbsoluteDefaultManifest': }") end end it "reads manifest from the default manifest setting" do expect(a_catalog_compiled_for_environment('testing')).to( include_resource('Notify[ManifestFromAbsoluteDefaultManifest]') ) end it "refuses to compile if environment.conf specifies a different manifest" do File.open(File.join(testingdir, "environment.conf"), "w") do |f| f.puts("manifest=./special_manifests") end expect { a_catalog_compiled_for_environment('testing') }.to( raise_error(Puppet::Error, /disable_per_environment_manifest.*environment.conf.*manifest.*conflict/) ) end it "reads manifest from default_manifest setting when environment.conf has manifest set if setting equals default_manifest setting" do File.open(File.join(testingdir, "environment.conf"), "w") do |f| f.puts("manifest=#{manifestsdir}") end expect(a_catalog_compiled_for_environment('testing')).to( include_resource('Notify[ManifestFromAbsoluteDefaultManifest]') ) end it "logs errors if environment.conf specifies a different manifest" do File.open(File.join(testingdir, "environment.conf"), "w") do |f| f.puts("manifest=./special_manifests") end Puppet.initialize_settings expect(Puppet[:environmentpath]).to eq(environmentpath) environment = Puppet.lookup(:environments).get('testing') expect(environment.manifest).to eq(manifestsdir) expect(@logs.first.to_s).to match(%r{disable_per_environment_manifest.*is true, but.*environment.*at #{testingdir}.*has.*environment.conf.*manifest.*#{testingdir}/special_manifests}) end it "raises an error if default_manifest is not absolute" do File.open(File.join(confdir, "puppet.conf"), "w") do |f| f.puts(<<-EOF) environmentpath=#{environmentpath} default_manifest=./relative disable_per_environment_manifest=true EOF end expect { Puppet.initialize_settings }.to raise_error(Puppet::Settings::ValidationError, /default_manifest.*must be.*absolute.*when.*disable_per_environment_manifest.*true/) end end - - context "in legacy environments" do - let(:environmentpath) { '' } - let(:manifestsdir) { File.expand_path("default_manifests", confdir) } - let(:legacy_manifestsdir) { File.expand_path('manifests', confdir) } - - before(:each) do - FileUtils.mkdir_p(manifestsdir) - - File.open(File.join(confdir, "puppet.conf"), "w") do |f| - f.puts(<<-EOF) - environmentpath= - default_manifest=#{manifestsdir} - disable_per_environment_manifest=true - manifest=#{legacy_manifestsdir} - EOF - end - - File.open(File.join(manifestsdir, "site.pp"), "w") do |f| - f.puts("notify { 'ManifestFromAbsoluteDefaultManifest': }") - end - end - - it "has no effect on compilation" do - FileUtils.mkdir_p(legacy_manifestsdir) - - File.open(File.join(legacy_manifestsdir, "site.pp"), "w") do |f| - f.puts("notify { 'ManifestFromLegacy': }") - end - - expect(a_catalog_compiled_for_environment('testing')).to( - include_resource('Notify[ManifestFromLegacy]') - ) - end - end end RSpec::Matchers.define :include_resource do |expected| match do |actual| actual.resources.map(&:ref).include?(expected) end def failure_message_for_should "expected #{@actual.resources.map(&:ref)} to include #{expected}" end def failure_message_for_should_not "expected #{@actual.resources.map(&:ref)} not to include #{expected}" end end def a_catalog_compiled_for_environment(envname) Puppet.initialize_settings expect(Puppet[:environmentpath]).to eq(environmentpath) node = Puppet::Node.new('testnode', :environment => 'testing') expect(node.environment).to eq(Puppet.lookup(:environments).get('testing')) Puppet::Parser::Compiler.compile(node) end end end diff --git a/spec/integration/environments/settings_interpolation_spec.rb b/spec/integration/environments/settings_interpolation_spec.rb index c7d2ee16f..1182fb064 100644 --- a/spec/integration/environments/settings_interpolation_spec.rb +++ b/spec/integration/environments/settings_interpolation_spec.rb @@ -1,146 +1,123 @@ require 'pp' require 'spec_helper' module SettingsInterpolationSpec describe "interpolating $environment" do let(:confdir) { Puppet[:confdir] } let(:cmdline_args) { ['--confdir', confdir, '--vardir', Puppet[:vardir], '--hiera_config', Puppet[:hiera_config]] } before(:each) do FileUtils.mkdir_p(confdir) end shared_examples_for "a setting that does not interpolate $environment" do before(:each) do set_puppet_conf(confdir, <<-EOF) environmentpath=$confdir/environments #{setting}=#{value} EOF end it "does not interpolate $environment" do Puppet.initialize_settings(cmdline_args) expect(Puppet[:environmentpath]).to eq("#{confdir}/environments") expect(Puppet[setting.intern]).to eq(expected) end it "displays the interpolated value in the warning" do Puppet.initialize_settings(cmdline_args) Puppet[setting.intern] expect(@logs).to have_matching_log(/cannot interpolate \$environment within '#{setting}'.*Its value will remain #{Regexp.escape(expected)}/) end end - context "when environmentpath is set" do - - describe "config_version" do - it "interpolates $environment" do - envname = 'testing' - setting = 'config_version' - value = '/some/script $environment' - expected = "#{File.expand_path('/some/script')} testing" - - set_puppet_conf(confdir, <<-EOF) - environmentpath=$confdir/environments - environment=#{envname} - EOF - - set_environment_conf("#{confdir}/environments", envname, <<-EOF) - #{setting}=#{value} - EOF - - Puppet.initialize_settings(cmdline_args) - expect(Puppet[:environmentpath]).to eq("#{confdir}/environments") - environment = Puppet.lookup(:environments).get(envname) - expect(environment.config_version).to eq(expected) - expect(@logs).to be_empty - end - end - - describe "basemodulepath" do - let(:setting) { "basemodulepath" } - let(:value) { "$confdir/environments/$environment/modules:$confdir/environments/$environment/other_modules" } - let(:expected) { "#{confdir}/environments/$environment/modules:#{confdir}/environments/$environment/other_modules" } - - it_behaves_like "a setting that does not interpolate $environment" - - it "logs a single warning for multiple instances of $environment in the setting" do - set_puppet_conf(confdir, <<-EOF) - environmentpath=$confdir/environments - #{setting}=#{value} - EOF + describe "config_version" do + it "interpolates $environment" do + envname = 'testing' + setting = 'config_version' + value = '/some/script $environment' + expected = "#{File.expand_path('/some/script')} testing" - Puppet.initialize_settings(cmdline_args) - expect(@logs.map(&:to_s).grep(/cannot interpolate \$environment within '#{setting}'/).count).to eq(1) - end - end + set_puppet_conf(confdir, <<-EOF) + environmentpath=$confdir/environments + environment=#{envname} + EOF - describe "environment" do - let(:setting) { "environment" } - let(:value) { "whatareyouthinking$environment" } - let(:expected) { value } + set_environment_conf("#{confdir}/environments", envname, <<-EOF) + #{setting}=#{value} + EOF - it_behaves_like "a setting that does not interpolate $environment" + Puppet.initialize_settings(cmdline_args) + expect(Puppet[:environmentpath]).to eq("#{confdir}/environments") + environment = Puppet.lookup(:environments).get(envname) + expect(environment.config_version).to eq(expected) + expect(@logs).to be_empty end + end - describe "the default_manifest" do - let(:setting) { "default_manifest" } - let(:value) { "$confdir/manifests/$environment" } - let(:expected) { "#{confdir}/manifests/$environment" } + describe "basemodulepath" do + let(:setting) { "basemodulepath" } + let(:value) { "$confdir/environments/$environment/modules:$confdir/environments/$environment/other_modules" } + let(:expected) { "#{confdir}/environments/$environment/modules:#{confdir}/environments/$environment/other_modules" } - it_behaves_like "a setting that does not interpolate $environment" - end - - it "does not interpolate $environment and logs a warning when interpolating environmentpath" do - setting = 'environmentpath' - value = "$confdir/environments/$environment" - expected = "#{confdir}/environments/$environment" + it_behaves_like "a setting that does not interpolate $environment" + it "logs a single warning for multiple instaces of $environment in the setting" do set_puppet_conf(confdir, <<-EOF) + environmentpath=$confdir/environments #{setting}=#{value} EOF Puppet.initialize_settings(cmdline_args) - expect(Puppet[setting.intern]).to eq(expected) - expect(@logs).to have_matching_log(/cannot interpolate \$environment within '#{setting}'/) + expect(@logs.map(&:to_s).grep(/cannot interpolate \$environment within '#{setting}'/).count).to eq(1) end end - def assert_does_interpolate_environment(setting, value, expected_interpolation) + describe "environment" do + let(:setting) { "environment" } + let(:value) { "whatareyouthinking$environment" } + let(:expected) { value } + + it_behaves_like "a setting that does not interpolate $environment" + end + + describe "the default_manifest" do + let(:setting) { "default_manifest" } + let(:value) { "$confdir/manifests/$environment" } + let(:expected) { "#{confdir}/manifests/$environment" } + + it_behaves_like "a setting that does not interpolate $environment" + end + + it "does not interpolate $environment and logs a warning when interpolating environmentpath" do + setting = 'environmentpath' + value = "$confdir/environments/$environment" + expected = "#{confdir}/environments/$environment" + set_puppet_conf(confdir, <<-EOF) - environmentpath= #{setting}=#{value} EOF Puppet.initialize_settings(cmdline_args) - expect(Puppet[:environmentpath]).to be_empty - expect(Puppet[setting.intern]).to eq(expected_interpolation) - expect(@logs).to_not have_matching_log(/cannot interpolate \$environment within '#{setting}'/) - end - - context "when environmentpath is not set" do - it "does interpolate $environment in config_version" do - value = "/some/script $environment" - expect = "/some/script production" - assert_does_interpolate_environment("config_version", value, expect) - end + expect(Puppet[setting.intern]).to eq(expected) + expect(@logs).to have_matching_log(/cannot interpolate \$environment within '#{setting}'/) end def set_puppet_conf(confdir, settings) write_file(File.join(confdir, "puppet.conf"), settings) end def set_environment_conf(environmentpath, environment, settings) envdir = File.join(environmentpath, environment) FileUtils.mkdir_p(envdir) write_file(File.join(envdir, 'environment.conf'), settings) end def write_file(file, contents) File.open(file, "w") do |f| f.puts(contents) end end end end diff --git a/spec/unit/module_tool_spec.rb b/spec/unit/module_tool_spec.rb index dee8e13bf..25d41d4b8 100755 --- a/spec/unit/module_tool_spec.rb +++ b/spec/unit/module_tool_spec.rb @@ -1,330 +1,329 @@ #! /usr/bin/env ruby # encoding: UTF-8 require 'spec_helper' require 'puppet/module_tool' describe Puppet::ModuleTool do describe '.is_module_root?' do it 'should return true if directory has a Modulefile file' do FileTest.expects(:file?).with(responds_with(:to_s, '/a/b/c/metadata.json')). returns(false) FileTest.expects(:file?).with(responds_with(:to_s, '/a/b/c/Modulefile')). returns(true) subject.is_module_root?(Pathname.new('/a/b/c')).should be_true end it 'should return true if directory has a metadata.json file' do FileTest.expects(:file?).with(responds_with(:to_s, '/a/b/c/metadata.json')). returns(true) subject.is_module_root?(Pathname.new('/a/b/c')).should be_true end it 'should return false if directory does not have a metadata.json or a Modulefile file' do FileTest.expects(:file?).with(responds_with(:to_s, '/a/b/c/metadata.json')). returns(false) FileTest.expects(:file?).with(responds_with(:to_s, '/a/b/c/Modulefile')). returns(false) subject.is_module_root?(Pathname.new('/a/b/c')).should be_false end end describe '.find_module_root' do let(:sample_path) { Pathname.new('/a/b/c').expand_path } it 'should return the first path as a pathname when it contains a module file' do Puppet::ModuleTool.expects(:is_module_root?).with(sample_path). returns(true) subject.find_module_root(sample_path).should == sample_path end it 'should return a parent path as a pathname when it contains a module file' do Puppet::ModuleTool.expects(:is_module_root?). with(responds_with(:to_s, File.expand_path('/a/b/c'))).returns(false) Puppet::ModuleTool.expects(:is_module_root?). with(responds_with(:to_s, File.expand_path('/a/b'))).returns(true) subject.find_module_root(sample_path).should == Pathname.new('/a/b').expand_path end it 'should return nil when no module root can be found' do Puppet::ModuleTool.expects(:is_module_root?).at_least_once.returns(false) subject.find_module_root(sample_path).should be_nil end end describe '.format_tree' do it 'should return an empty tree when given an empty list' do subject.format_tree([]).should == '' end it 'should return a shallow when given a list without dependencies' do list = [ { :text => 'first' }, { :text => 'second' }, { :text => 'third' } ] subject.format_tree(list).should == <<-TREE ├── first ├── second └── third TREE end it 'should return a deeply nested tree when given a list with deep dependencies' do list = [ { :text => 'first', :dependencies => [ { :text => 'second', :dependencies => [ { :text => 'third' } ] } ] }, ] subject.format_tree(list).should == <<-TREE └─┬ first └─┬ second └── third TREE end it 'should show connectors when deep dependencies are not on the last node of the top level' do list = [ { :text => 'first', :dependencies => [ { :text => 'second', :dependencies => [ { :text => 'third' } ] } ] }, { :text => 'fourth' } ] subject.format_tree(list).should == <<-TREE ├─┬ first │ └─┬ second │ └── third └── fourth TREE end it 'should show connectors when deep dependencies are not on the last node of any level' do list = [ { :text => 'first', :dependencies => [ { :text => 'second', :dependencies => [ { :text => 'third' } ] }, { :text => 'fourth' } ] } ] subject.format_tree(list).should == <<-TREE └─┬ first ├─┬ second │ └── third └── fourth TREE end it 'should show connectors in every case when deep dependencies are not on the last node' do list = [ { :text => 'first', :dependencies => [ { :text => 'second', :dependencies => [ { :text => 'third' } ] }, { :text => 'fourth' } ] }, { :text => 'fifth' } ] subject.format_tree(list).should == <<-TREE ├─┬ first │ ├─┬ second │ │ └── third │ └── fourth └── fifth TREE end end describe '.set_option_defaults' do let(:options) { {} } let(:modulepath) { ['/env/module/path', '/global/module/path'] } let(:environment_name) { :current_environment } let(:environment) { Puppet::Node::Environment.create(environment_name, modulepath) } subject do described_class.set_option_defaults(options) options end around do |example| envs = Puppet::Environments::Combined.new( Puppet::Environments::Static.new(environment), - Puppet::Environments::Legacy.new ) Puppet.override(:environments => envs) do example.run end end describe ':environment' do context 'as String' do let(:options) { { :environment => "#{environment_name}" } } it 'assigns the environment with the given name to :environment_instance' do expect(subject).to include :environment_instance => environment end end context 'as Symbol' do let(:options) { { :environment => :"#{environment_name}" } } it 'assigns the environment with the given name to :environment_instance' do expect(subject).to include :environment_instance => environment end end context 'as Puppet::Node::Environment' do let(:env) { Puppet::Node::Environment.create('anonymous', []) } let(:options) { { :environment => env } } it 'assigns the given environment to :environment_instance' do expect(subject).to include :environment_instance => env end end end describe ':modulepath' do let(:options) do { :modulepath => %w[bar foo baz].join(File::PATH_SEPARATOR) } end let(:paths) { options[:modulepath].split(File::PATH_SEPARATOR).map { |dir| File.expand_path(dir) } } it 'is expanded to an absolute path' do expect(subject[:environment_instance].full_modulepath).to eql paths end it 'is used to compute :target_dir' do expect(subject).to include :target_dir => paths.first end context 'conflicts with :environment' do let(:options) do { :modulepath => %w[bar foo baz].join(File::PATH_SEPARATOR), :environment => environment_name } end it 'replaces the modulepath of the :environment_instance' do expect(subject[:environment_instance].full_modulepath).to eql paths end it 'is used to compute :target_dir' do expect(subject).to include :target_dir => paths.first end end end describe ':target_dir' do let(:options) do { :target_dir => 'foo' } end let(:target) { File.expand_path(options[:target_dir]) } it 'is expanded to an absolute path' do expect(subject).to include :target_dir => target end it 'is prepended to the modulepath of the :environment_instance' do expect(subject[:environment_instance].full_modulepath.first).to eql target end context 'conflicts with :modulepath' do let(:options) do { :target_dir => 'foo', :modulepath => %w[bar foo baz].join(File::PATH_SEPARATOR) } end it 'is prepended to the modulepath of the :environment_instance' do expect(subject[:environment_instance].full_modulepath.first).to eql target end it 'shares the provided :modulepath via the :environment_instance' do paths = %w[foo] + options[:modulepath].split(File::PATH_SEPARATOR) paths.map! { |dir| File.expand_path(dir) } expect(subject[:environment_instance].full_modulepath).to eql paths end end context 'conflicts with :environment' do let(:options) do { :target_dir => 'foo', :environment => environment_name } end it 'is prepended to the modulepath of the :environment_instance' do expect(subject[:environment_instance].full_modulepath.first).to eql target end it 'shares the provided :modulepath via the :environment_instance' do paths = %w[foo] + environment.full_modulepath paths.map! { |dir| File.expand_path(dir) } expect(subject[:environment_instance].full_modulepath).to eql paths end end context 'when not passed' do it 'is populated with the first component of the modulepath' do expect(subject).to include :target_dir => subject[:environment_instance].full_modulepath.first end end end end describe '.parse_module_dependency' do it 'parses a dependency without a version range expression' do name, range, expr = subject.parse_module_dependency('source', 'name' => 'foo-bar') expect(name).to eql('foo-bar') expect(range).to eql(Semantic::VersionRange.parse('>= 0.0.0')) expect(expr).to eql('>= 0.0.0') end it 'parses a dependency with a version range expression' do name, range, expr = subject.parse_module_dependency('source', 'name' => 'foo-bar', 'version_requirement' => '1.2.x') expect(name).to eql('foo-bar') expect(range).to eql(Semantic::VersionRange.parse('1.2.x')) expect(expr).to eql('1.2.x') end it 'parses a dependency with a version range expression in the (deprecated) versionRange key' do name, range, expr = subject.parse_module_dependency('source', 'name' => 'foo-bar', 'versionRequirement' => '1.2.x') expect(name).to eql('foo-bar') expect(range).to eql(Semantic::VersionRange.parse('1.2.x')) expect(expr).to eql('1.2.x') end it 'does not raise an error on invalid version range expressions' do name, range, expr = subject.parse_module_dependency('source', 'name' => 'foo-bar', 'version_requirement' => 'nope') expect(name).to eql('foo-bar') expect(range).to eql(Semantic::VersionRange::EMPTY_RANGE) expect(expr).to eql('nope') end end end