diff --git a/lib/puppet/node/environment.rb b/lib/puppet/node/environment.rb index f8e7b96da..b4d5531f5 100644 --- a/lib/puppet/node/environment.rb +++ b/lib/puppet/node/environment.rb @@ -1,511 +1,510 @@ require 'puppet/util' -require 'puppet/util/cacher' require 'monitor' require 'puppet/parser/parser_factory' # Just define it, so this class has fewer load dependencies. class Puppet::Node end # Puppet::Node::Environment acts as a container for all configuration # that is expected to vary between environments. # # ## The root environment # # In addition to normal environments that are defined by the user,there is a # special 'root' environment. It is defined as an instance variable on the # Puppet::Node::Environment metaclass. The environment name is `*root*` and can # be accessed by looking up the `:root_environment` using {Puppet.lookup}. # # The primary purpose of the root environment is to contain parser functions # that are not bound to a specific environment. The main case for this is for # logging functions. Logging functions are attached to the 'root' environment # when {Puppet::Parser::Functions.reset} is called. class Puppet::Node::Environment - include Puppet::Util::Cacher NO_MANIFEST = :no_manifest # The create() factory method should be used instead. # # @api private def self.new(*args) create(*args) end private_class_method :new # Create a new environment with the given name # # @param name [Symbol] the name of the environment # @param modulepath [Array] the list of paths from which to load modules # @param manifest [String] the path to the manifest for the environment or # the constant Puppet::Node::Environment::NO_MANIFEST if there is none. # @param config_version [String] path to a script whose output will be added # to report logs (optional) # @return [Puppet::Node::Environment] # # @api public def self.create(name, modulepath, manifest = NO_MANIFEST, config_version = nil) obj = self.allocate obj.send(:initialize, name.intern, expand_dirs(extralibs() + modulepath), manifest == NO_MANIFEST ? manifest : File.expand_path(manifest), config_version) obj end # A "reference" to a remote environment. The created environment instance # isn't expected to exist on the local system, but is instead a reference to # environment information on a remote system. For instance when a catalog is # being applied, this will be used on the agent. # # @note This does not provide access to the information of the remote # environment's modules, manifest, or anything else. It is simply a value # object to pass around and use as an environment. # # @param name [Symbol] The name of the remote environment # def self.remote(name) create(name, [], NO_MANIFEST) end # Instantiate a new environment # # @note {Puppet::Node::Environment.new} is private for historical reasons, as # previously it had been overridden to return memoized objects and was # replaced with {Puppet::Node::Environment.create}, so this will not be # invoked with the normal Ruby initialization semantics. # # @param name [Symbol] The environment name def initialize(name, modulepath, manifest, config_version) @name = name @modulepath = modulepath @manifest = manifest @config_version = config_version end # Creates a new Puppet::Node::Environment instance, overriding any of the passed # parameters. # # @param env_params [Hash<{Symbol => String,Array}>] new environment # parameters (:modulepath, :manifest, :config_version) # @return [Puppet::Node::Environment] def override_with(env_params) return self.class.create(name, env_params[:modulepath] || modulepath, env_params[:manifest] || manifest, env_params[:config_version] || config_version) end # Creates a new Puppet::Node::Environment instance, overriding :manifest, # :modulepath, or :config_version from the passed settings if they were # originally set from the commandline, or returns self if there is nothing to # override. # # @param settings [Puppet::Settings] an initialized puppet settings instance # @return [Puppet::Node::Environment] new overridden environment or self if # there are no commandline changes from settings. def override_from_commandline(settings) overrides = {} if settings.set_by_cli?(:modulepath) overrides[:modulepath] = self.class.split_path(settings.value(:modulepath)) end if settings.set_by_cli?(:config_version) overrides[:config_version] = settings.value(:config_version) end if settings.set_by_cli?(:manifest) overrides[:manifest] = settings.value(:manifest) end overrides.empty? ? self : self.override_with(overrides) end # @param [String] name Environment name to check for valid syntax. # @return [Boolean] true if name is valid # @api public def self.valid_name?(name) !!name.match(/\A\w+\Z/) end # @!attribute [r] name # @api public # @return [Symbol] the human readable environment name that serves as the # environment identifier attr_reader :name # @api public # @return [Array] All directories present on disk in the modulepath def modulepath @modulepath.find_all do |p| Puppet::FileSystem.directory?(p) end end # @api public # @return [Array] All directories in the modulepath (even if they are not present on disk) def full_modulepath @modulepath end # @!attribute [r] manifest # @api public # @return [String] path to the manifest file or directory. attr_reader :manifest # @!attribute [r] config_version # @api public # @return [String] path to a script whose output will be added to report logs # (optional) attr_reader :config_version # Checks to make sure that this environment did not have a manifest set in # its original environment.conf if Puppet is configured with # +disable_per_environment_manifest+ set true. If it did, the environment's # modules may not function as intended by the original authors, and we may # seek to halt a puppet compilation for a node in this environment. # # The only exception to this would be if the environment.conf manifest is an exact, # uninterpolated match for the current +default_manifest+ setting. # # @return [Boolean] true if using directory environments, and # Puppet[:disable_per_environment_manifest] is true, and this environment's # original environment.conf had a manifest setting that is not the # Puppet[:default_manifest]. # @api private def conflicting_manifest_settings? return false if !Puppet[:disable_per_environment_manifest] environment_conf = Puppet.lookup(:environments).get_conf(name) original_manifest = environment_conf.raw_setting(:manifest) !original_manifest.nil? && !original_manifest.empty? && original_manifest != Puppet[:default_manifest] end # Checks the environment and settings for any conflicts # @return [Array] an array of validation errors # @api public def validation_errors errors = [] if conflicting_manifest_settings? errors << "The 'disable_per_environment_manifest' setting is true, and the '#{name}' environment has an environment.conf manifest that conflicts with the 'default_manifest' setting." end errors end # Return an environment-specific Puppet setting. # # @api public # # @param param [String, Symbol] The environment setting to look up # @return [Object] The resolved setting value def [](param) Puppet.settings.value(param, self.name) end # @api public # @return [Puppet::Resource::TypeCollection] The current global TypeCollection def known_resource_types if @known_resource_types.nil? @known_resource_types = Puppet::Resource::TypeCollection.new(self) @known_resource_types.import_ast(perform_initial_import(), '') end @known_resource_types end # Yields each modules' plugin directory if the plugin directory (modulename/lib) # is present on the filesystem. # # @yield [String] Yields the plugin directory from each module to the block. # @api public def each_plugin_directory(&block) modules.map(&:plugin_directory).each do |lib| lib = Puppet::Util::Autoload.cleanpath(lib) yield lib if File.directory?(lib) end end # Locate a module instance by the module name alone. # # @api public # # @param name [String] The module name # @return [Puppet::Module, nil] The module if found, else nil def module(name) modules.find {|mod| mod.name == name} end # Locate a module instance by the full forge name (EG authorname/module) # # @api public # # @param forge_name [String] The module name # @return [Puppet::Module, nil] The module if found, else nil def module_by_forge_name(forge_name) author, modname = forge_name.split('/') found_mod = self.module(modname) found_mod and found_mod.forge_name == forge_name ? found_mod : nil end - # @!attribute [r] modules - # Return all modules for this environment in the order they appear in the - # modulepath. - # @note If multiple modules with the same name are present they will - # both be added, but methods like {#module} and {#module_by_forge_name} - # will return the first matching entry in this list. - # @note This value is cached so that the filesystem doesn't have to be - # re-enumerated every time this method is invoked, since that - # enumeration could be a costly operation and this method is called - # frequently. The cache expiry is determined by `Puppet[:filetimeout]`. - # @see Puppet::Util::Cacher.cached_attr - # @api public - # @return [Array] All modules for this environment - cached_attr(:modules, Puppet[:filetimeout]) do - module_references = [] - seen_modules = {} - modulepath.each do |path| - Dir.entries(path).each do |name| - next if name == "." || name == ".." - warn_about_mistaken_path(path, name) - if not seen_modules[name] - module_references << {:name => name, :path => File.join(path, name)} - seen_modules[name] = true + # Return all modules for this environment in the order they appear in the + # modulepath. + # @note If multiple modules with the same name are present they will + # both be added, but methods like {#module} and {#module_by_forge_name} + # will return the first matching entry in this list. + # @note This value is cached so that the filesystem doesn't have to be + # re-enumerated every time this method is invoked, since that + # enumeration could be a costly operation and this method is called + # frequently. The cache expiry is determined by `Puppet[:filetimeout]`. + # @api public + # @return [Array] All modules for this environment + def modules + if @modules.nil? + module_references = [] + seen_modules = {} + modulepath.each do |path| + Dir.entries(path).each do |name| + next if name == "." || name == ".." + warn_about_mistaken_path(path, name) + if not seen_modules[name] + module_references << {:name => name, :path => File.join(path, name)} + seen_modules[name] = true + end end end - end - module_references.collect do |reference| - begin - Puppet::Module.new(reference[:name], reference[:path], self) - rescue Puppet::Module::Error => e - Puppet.log_exception(e) - nil - end - end.compact + @modules = module_references.collect do |reference| + begin + Puppet::Module.new(reference[:name], reference[:path], self) + rescue Puppet::Module::Error => e + Puppet.log_exception(e) + nil + end + end.compact + end + @modules end # Generate a warning if the given directory in a module path entry is named `lib`. # # @api private # # @param path [String] The module directory containing the given directory # @param name [String] The directory name def warn_about_mistaken_path(path, name) if name == "lib" Puppet.debug("Warning: Found directory named 'lib' in module path ('#{path}/lib'); unless " + "you are expecting to load a module named 'lib', your module path may be set " + "incorrectly.") end end # Modules broken out by directory in the modulepath # # @note This method _changes_ the current working directory while enumerating # the modules. This seems rather dangerous. # # @api public # # @return [Hash>] A hash whose keys are file # paths, and whose values is an array of Puppet Modules for that path def modules_by_path modules_by_path = {} modulepath.each do |path| Dir.chdir(path) do module_names = Dir.glob('*').select do |d| FileTest.directory?(d) && (File.basename(d) =~ /\A\w+(-\w+)*\Z/) end modules_by_path[path] = module_names.sort.map do |name| Puppet::Module.new(name, File.join(path, name), self) end end end modules_by_path end # All module requirements for all modules in the environment modulepath # # @api public # # @comment This has nothing to do with an environment. It seems like it was # stuffed into the first convenient class that vaguely involved modules. # # @example # environment.module_requirements # # => { # # 'username/amodule' => [ # # { # # 'name' => 'username/moduledep', # # 'version' => '1.2.3', # # 'version_requirement' => '>= 1.0.0', # # }, # # { # # 'name' => 'username/anotherdep', # # 'version' => '4.5.6', # # 'version_requirement' => '>= 3.0.0', # # } # # ] # # } # # # # @return [Hash>>] See the method example # for an explanation of the return value. def module_requirements deps = {} modules.each do |mod| next unless mod.forge_name deps[mod.forge_name] ||= [] mod.dependencies and mod.dependencies.each do |mod_dep| dep_name = mod_dep['name'].tr('-', '/') (deps[dep_name] ||= []) << { 'name' => mod.forge_name, 'version' => mod.version, 'version_requirement' => mod_dep['version_requirement'] } end end deps.each do |mod, mod_deps| deps[mod] = mod_deps.sort_by { |d| d['name'] } end deps end # Checks if a reparse is required (cache of files is stale). # def check_for_reparse if (Puppet[:code] != @parsed_code || @known_resource_types.require_reparse?) @parsed_code = nil @known_resource_types = nil end end # @return [String] The YAML interpretation of the object # Return the name of the environment as a string interpretation of the object def to_yaml to_s.to_yaml end # @return [String] The stringified value of the `name` instance variable # @api public def to_s name.to_s end # @return [Symbol] The `name` value, cast to a string, then cast to a symbol. # # @api public # # @note the `name` instance variable is a Symbol, but this casts the value # to a String and then converts it back into a Symbol which will needlessly # create an object that needs to be garbage collected def to_sym to_s.to_sym end def self.split_path(path_string) path_string.split(File::PATH_SEPARATOR) end def ==(other) return true if other.kind_of?(Puppet::Node::Environment) && self.name == other.name && self.full_modulepath == other.full_modulepath && self.manifest == other.manifest end alias eql? == def hash [self.class, name, full_modulepath, manifest].hash end private def self.extralibs() if ENV["PUPPETLIB"] split_path(ENV["PUPPETLIB"]) else [] end end def self.expand_dirs(dirs) dirs.collect do |dir| File.expand_path(dir) end end # Reparse the manifests for the given environment # # There are two sources that can be used for the initial parse: # # 1. The value of `Puppet[:code]`: Puppet can take a string from # its settings and parse that as a manifest. This is used by various # Puppet applications to read in a manifest and pass it to the # environment as a side effect. This is attempted first. # 2. The contents of this environment's +manifest+ attribute: Puppet will # try to load the environment manifest. # # @note This method will return an empty hostclass if # `Puppet[:ignoreimport]` is set to true. # # @return [Puppet::Parser::AST::Hostclass] The AST hostclass object # representing the 'main' hostclass def perform_initial_import return empty_parse_result if Puppet[:ignoreimport] parser = Puppet::Parser::ParserFactory.parser @parsed_code = Puppet[:code] if @parsed_code != "" parser.string = @parsed_code parser.parse else file = self.manifest # if the manifest file is a reference to a directory, parse and combine # all .pp files in that directory if file == NO_MANIFEST empty_parse_result elsif File.directory?(file) parse_results = Puppet::FileSystem::PathPattern.absolute(File.join(file, '**/*.pp')).glob.sort.map do | file_to_parse | parser.file = file_to_parse parser.parse end # Use a parser type specific merger to concatenate the results Puppet::Parser::AST::Hostclass.new('', :code => Puppet::Parser::ParserFactory.code_merger.concatenate(parse_results)) else parser.file = file parser.parse end end rescue => detail @known_resource_types.parse_failed = true msg = "Could not parse for environment #{self}: #{detail}" error = Puppet::Error.new(msg) error.set_backtrace(detail.backtrace) raise error end # Return an empty toplevel hostclass to indicate that no file was loaded # # This is used as the return value of {#perform_initial_import} when # `Puppet.settings[:ignoreimport]` is true. # # @return [Puppet::Parser::AST::Hostclass] def empty_parse_result return Puppet::Parser::AST::Hostclass.new('') end # A special "null" environment # # This environment should be used when there is no specific environment in # effect. NONE = create(:none, []) end diff --git a/lib/puppet/util/cacher.rb b/lib/puppet/util/cacher.rb deleted file mode 100644 index 24017de32..000000000 --- a/lib/puppet/util/cacher.rb +++ /dev/null @@ -1,74 +0,0 @@ -module Puppet::Util::Cacher - # Our module has been extended in a class; we can only add the Instance methods, - # which become *class* methods in the class. - def self.extended(other) - class << other - extend ClassMethods - include InstanceMethods - end - end - - # Our module has been included in a class, which means the class gets the class methods - # and all of its instances get the instance methods. - def self.included(other) - other.extend(ClassMethods) - other.send(:include, InstanceMethods) - end - - # Methods that can get added to a class. - module ClassMethods - # Provide a means of defining an attribute whose value will be cached. - # Must provide a block capable of defining the value if it's flushed.. - def cached_attr(name, ttl, &block) - init_method = "init_#{name}" - define_method(init_method, &block) - - set_attr_ttl(name, ttl) - - define_method(name) do - cached_value(name) - end - - define_method(name.to_s + "=") do |value| - # Make sure the cache timestamp is set - value_cache[name] = value - set_expiration(name) - end - end - - def attr_ttl(name) - @attr_ttls[name] - end - - def set_attr_ttl(name, value) - @attr_ttls ||= {} - @attr_ttls[name] = Integer(value) - end - end - - # Methods that get added to instances. - module InstanceMethods - private - - def cached_value(name) - if value_cache[name].nil? or expired_by_ttl?(name) - value_cache[name] = send("init_#{name}") - set_expiration(name) - end - value_cache[name] - end - - def expired_by_ttl?(name) - @attr_expirations[name] < Time.now - end - - def set_expiration(name) - @attr_expirations ||= {} - @attr_expirations[name] = Time.now + self.class.attr_ttl(name) - end - - def value_cache - @value_cache ||= {} - end - end -end diff --git a/spec/unit/node/environment_spec.rb b/spec/unit/node/environment_spec.rb index 770cb9ff2..4b4e0b825 100755 --- a/spec/unit/node/environment_spec.rb +++ b/spec/unit/node/environment_spec.rb @@ -1,486 +1,482 @@ #! /usr/bin/env ruby require 'spec_helper' require 'tmpdir' require 'puppet/node/environment' require 'puppet/util/execution' require 'puppet_spec/modules' require 'puppet/parser/parser_factory' describe Puppet::Node::Environment do let(:env) { Puppet::Node::Environment.create("testing", []) } include PuppetSpec::Files context 'the environment' do it "converts an environment to string when converting to YAML" do env.to_yaml.should match(/--- testing/) end - it "uses the filetimeout for the ttl for the module list" do - expect(Puppet::Node::Environment.attr_ttl(:modules)).to eq(Integer(Puppet[:filetimeout])) - end - describe ".create" do it "creates equivalent environments whether specifying name as a symbol or a string" do expect(Puppet::Node::Environment.create(:one, [])).to eq(Puppet::Node::Environment.create("one", [])) end it "interns name" do expect(Puppet::Node::Environment.create("one", []).name).to equal(:one) end it "does not produce environment singletons" do expect(Puppet::Node::Environment.create("one", [])).to_not equal(Puppet::Node::Environment.create("one", [])) end end it "returns its name when converted to a string" do expect(env.to_s).to eq("testing") end describe "equality" do it "works as a hash key" do base = Puppet::Node::Environment.create(:first, ["modules"], "manifests") same = Puppet::Node::Environment.create(:first, ["modules"], "manifests") different = Puppet::Node::Environment.create(:first, ["different"], "manifests") hash = {} hash[base] = "base env" hash[same] = "same env" hash[different] = "different env" expect(hash[base]).to eq("same env") expect(hash[different]).to eq("different env") expect(hash).to have(2).item end it "is equal when name, modules, and manifests are the same" do base = Puppet::Node::Environment.create(:base, ["modules"], "manifests") different_name = Puppet::Node::Environment.create(:different, base.full_modulepath, base.manifest) expect(base).to_not eq("not an environment") expect(base).to eq(base) expect(base.hash).to eq(base.hash) expect(base.override_with(:modulepath => ["different"])).to_not eq(base) expect(base.override_with(:modulepath => ["different"]).hash).to_not eq(base.hash) expect(base.override_with(:manifest => "different")).to_not eq(base) expect(base.override_with(:manifest => "different").hash).to_not eq(base.hash) expect(different_name).to_not eq(base) expect(different_name.hash).to_not eq(base.hash) end end describe "overriding an existing environment" do let(:original_path) { [tmpdir('original')] } let(:new_path) { [tmpdir('new')] } let(:environment) { Puppet::Node::Environment.create(:overridden, original_path, 'orig.pp', '/config/script') } it "overrides modulepath" do overridden = environment.override_with(:modulepath => new_path) expect(overridden).to_not be_equal(environment) expect(overridden.name).to eq(:overridden) expect(overridden.manifest).to eq(File.expand_path('orig.pp')) expect(overridden.modulepath).to eq(new_path) expect(overridden.config_version).to eq('/config/script') end it "overrides manifest" do overridden = environment.override_with(:manifest => 'new.pp') expect(overridden).to_not be_equal(environment) expect(overridden.name).to eq(:overridden) expect(overridden.manifest).to eq(File.expand_path('new.pp')) expect(overridden.modulepath).to eq(original_path) expect(overridden.config_version).to eq('/config/script') end it "overrides config_version" do overridden = environment.override_with(:config_version => '/new/script') expect(overridden).to_not be_equal(environment) expect(overridden.name).to eq(:overridden) expect(overridden.manifest).to eq(File.expand_path('orig.pp')) expect(overridden.modulepath).to eq(original_path) expect(overridden.config_version).to eq('/new/script') end end describe "when managing known resource types" do before do env.stubs(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new('')) end it "creates a resource type collection if none exists" do expect(env.known_resource_types).to be_kind_of(Puppet::Resource::TypeCollection) end it "memoizes resource type collection" do expect(env.known_resource_types).to equal(env.known_resource_types) end it "performs the initial import when creating a new collection" do env.expects(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new('')) env.known_resource_types end it "generates a new TypeCollection if the current one requires reparsing" do old_type_collection = env.known_resource_types old_type_collection.stubs(:require_reparse?).returns true env.check_for_reparse new_type_collection = env.known_resource_types expect(new_type_collection).to be_a Puppet::Resource::TypeCollection expect(new_type_collection).to_not equal(old_type_collection) end end it "validates the modulepath directories" do real_file = tmpdir('moduledir') path = ['/one', '/two', real_file] env = Puppet::Node::Environment.create(:test, path) expect(env.modulepath).to eq([real_file]) end it "prefixes the value of the 'PUPPETLIB' environment variable to the module path if present" do first_puppetlib = tmpdir('puppetlib1') second_puppetlib = tmpdir('puppetlib2') first_moduledir = tmpdir('moduledir1') second_moduledir = tmpdir('moduledir2') Puppet::Util.withenv("PUPPETLIB" => [first_puppetlib, second_puppetlib].join(File::PATH_SEPARATOR)) do env = Puppet::Node::Environment.create(:testing, [first_moduledir, second_moduledir]) expect(env.modulepath).to eq([first_puppetlib, second_puppetlib, first_moduledir, second_moduledir]) end end describe "validating manifest settings" do before(:each) do Puppet[:default_manifest] = "/default/manifests/site.pp" end it "has no validation errors when disable_per_environment_manifest is false" do expect(Puppet::Node::Environment.create(:directory, [], '/some/non/default/manifest.pp').validation_errors).to be_empty end context "when disable_per_environment_manifest is true" do let(:config) { mock('config') } let(:global_modulepath) { ["/global/modulepath"] } let(:envconf) { Puppet::Settings::EnvironmentConf.new("/some/direnv", config, global_modulepath) } before(:each) do Puppet[:disable_per_environment_manifest] = true end def assert_manifest_conflict(expectation, envconf_manifest_value) config.expects(:setting).with(:manifest).returns( mock('setting', :value => envconf_manifest_value) ) environment = Puppet::Node::Environment.create(:directory, [], '/default/manifests/site.pp') loader = Puppet::Environments::Static.new(environment) loader.stubs(:get_conf).returns(envconf) Puppet.override(:environments => loader) do if expectation expect(environment.validation_errors).to have_matching_element(/The 'disable_per_environment_manifest' setting is true.*and the.*environment.*conflicts/) else expect(environment.validation_errors).to be_empty end end end it "has conflicting_manifest_settings when environment.conf manifest was set" do assert_manifest_conflict(true, '/some/envconf/manifest/site.pp') end it "does not have conflicting_manifest_settings when environment.conf manifest is empty" do assert_manifest_conflict(false, '') end it "does not have conflicting_manifest_settings when environment.conf manifest is nil" do assert_manifest_conflict(false, nil) end it "does not have conflicting_manifest_settings when environment.conf manifest is an exact, uninterpolated match of default_manifest" do assert_manifest_conflict(false, '/default/manifests/site.pp') end end end describe "when modeling a specific environment" do let(:first_modulepath) { tmpdir('firstmodules') } let(:second_modulepath) { tmpdir('secondmodules') } let(:env) { Puppet::Node::Environment.create(:modules_test, [first_modulepath, second_modulepath]) } let(:module_options) { { :environment => env, :metadata => { :author => 'puppetlabs', }, } } describe "module data" do describe ".module" do it "returns an individual module that exists in its module path" do one = PuppetSpec::Modules.create('one', first_modulepath, module_options) expect(env.module('one')).to eq(one) end it "returns nil if asked for a module that does not exist in its path" do expect(env.module("doesnotexist")).to be_nil end end describe "#modules_by_path" do it "returns an empty list if there are no modules" do expect(env.modules_by_path).to eq({ first_modulepath => [], second_modulepath => [] }) end it "includes modules even if they exist in multiple dirs in the modulepath" do one = PuppetSpec::Modules.create('one', first_modulepath, module_options) two = PuppetSpec::Modules.create('two', second_modulepath, module_options) expect(env.modules_by_path).to eq({ first_modulepath => [one], second_modulepath => [two], }) end it "ignores modules with invalid names" do PuppetSpec::Modules.generate_files('foo', first_modulepath) PuppetSpec::Modules.generate_files('foo2', first_modulepath) PuppetSpec::Modules.generate_files('foo-bar', first_modulepath) PuppetSpec::Modules.generate_files('foo_bar', first_modulepath) PuppetSpec::Modules.generate_files('foo=bar', first_modulepath) PuppetSpec::Modules.generate_files('foo bar', first_modulepath) PuppetSpec::Modules.generate_files('foo.bar', first_modulepath) PuppetSpec::Modules.generate_files('-foo', first_modulepath) PuppetSpec::Modules.generate_files('foo-', first_modulepath) PuppetSpec::Modules.generate_files('foo--bar', first_modulepath) expect(env.modules_by_path[first_modulepath].collect{|mod| mod.name}.sort).to eq(%w{foo foo-bar foo2 foo_bar}) end end describe "#module_requirements" do it "returns a list of what modules depend on other modules" do PuppetSpec::Modules.create( 'foo', first_modulepath, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => ">= 1.0.0" }] } ) PuppetSpec::Modules.create( 'bar', second_modulepath, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs/foo', "version_requirement" => "<= 2.0.0" }] } ) PuppetSpec::Modules.create( 'baz', first_modulepath, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs-bar', "version_requirement" => "3.0.0" }] } ) PuppetSpec::Modules.create( 'alpha', first_modulepath, :metadata => { :author => 'puppetlabs', :dependencies => [{ 'name' => 'puppetlabs/bar', "version_requirement" => "~3.0.0" }] } ) expect(env.module_requirements).to eq({ 'puppetlabs/alpha' => [], 'puppetlabs/foo' => [ { "name" => "puppetlabs/bar", "version" => "9.9.9", "version_requirement" => "<= 2.0.0" } ], 'puppetlabs/bar' => [ { "name" => "puppetlabs/alpha", "version" => "9.9.9", "version_requirement" => "~3.0.0" }, { "name" => "puppetlabs/baz", "version" => "9.9.9", "version_requirement" => "3.0.0" }, { "name" => "puppetlabs/foo", "version" => "9.9.9", "version_requirement" => ">= 1.0.0" } ], 'puppetlabs/baz' => [] }) end end describe ".module_by_forge_name" do it "finds modules by forge_name" do mod = PuppetSpec::Modules.create( 'baz', first_modulepath, module_options, ) expect(env.module_by_forge_name('puppetlabs/baz')).to eq(mod) end it "does not find modules with same name by the wrong author" do mod = PuppetSpec::Modules.create( 'baz', first_modulepath, :metadata => {:author => 'sneakylabs'}, :environment => env ) expect(env.module_by_forge_name('puppetlabs/baz')).to eq(nil) end it "returns nil when the module can't be found" do expect(env.module_by_forge_name('ima/nothere')).to be_nil end end describe ".modules" do it "returns an empty list if there are no modules" do expect(env.modules).to eq([]) end it "returns a module named for every directory in each module path" do %w{foo bar}.each do |mod_name| PuppetSpec::Modules.generate_files(mod_name, first_modulepath) end %w{bee baz}.each do |mod_name| PuppetSpec::Modules.generate_files(mod_name, second_modulepath) end expect(env.modules.collect{|mod| mod.name}.sort).to eq(%w{foo bar bee baz}.sort) end it "removes duplicates" do PuppetSpec::Modules.generate_files('foo', first_modulepath) PuppetSpec::Modules.generate_files('foo', second_modulepath) expect(env.modules.collect{|mod| mod.name}.sort).to eq(%w{foo}) end it "ignores modules with invalid names" do PuppetSpec::Modules.generate_files('foo', first_modulepath) PuppetSpec::Modules.generate_files('foo2', first_modulepath) PuppetSpec::Modules.generate_files('foo-bar', first_modulepath) PuppetSpec::Modules.generate_files('foo_bar', first_modulepath) PuppetSpec::Modules.generate_files('foo=bar', first_modulepath) PuppetSpec::Modules.generate_files('foo bar', first_modulepath) expect(env.modules.collect{|mod| mod.name}.sort).to eq(%w{foo foo-bar foo2 foo_bar}) end it "creates modules with the correct environment" do PuppetSpec::Modules.generate_files('foo', first_modulepath) env.modules.each do |mod| expect(mod.environment).to eq(env) end end it "logs an exception if a module contains invalid metadata" do PuppetSpec::Modules.generate_files( 'foo', first_modulepath, :metadata => { :author => 'puppetlabs' # missing source, version, etc } ) Puppet.expects(:log_exception).with(is_a(Puppet::Module::MissingMetadata)) env.modules end end end end describe "when performing initial import" do it "loads from Puppet[:code]" do Puppet[:code] = "define foo {}" krt = env.known_resource_types expect(krt.find_definition('', 'foo')).to be_kind_of(Puppet::Resource::Type) end it "parses from the the environment's manifests if Puppet[:code] is not set" do filename = tmpfile('a_manifest.pp') File.open(filename, 'w') do |f| f.puts("define from_manifest {}") end env = Puppet::Node::Environment.create(:testing, [], filename) krt = env.known_resource_types expect(krt.find_definition('', 'from_manifest')).to be_kind_of(Puppet::Resource::Type) end it "prefers Puppet[:code] over manifest files" do Puppet[:code] = "define from_code_setting {}" filename = tmpfile('a_manifest.pp') File.open(filename, 'w') do |f| f.puts("define from_manifest {}") end env = Puppet::Node::Environment.create(:testing, [], filename) krt = env.known_resource_types expect(krt.find_definition('', 'from_code_setting')).to be_kind_of(Puppet::Resource::Type) end it "initial import proceeds even if manifest file does not exist on disk" do filename = tmpfile('a_manifest.pp') env = Puppet::Node::Environment.create(:testing, [], filename) expect(env.known_resource_types).to be_kind_of(Puppet::Resource::TypeCollection) end it "returns an empty TypeCollection if neither code nor manifests is present" do expect(env.known_resource_types).to be_kind_of(Puppet::Resource::TypeCollection) end it "fails helpfully if there is an error importing" do Puppet[:code] = "oops {" expect do env.known_resource_types end.to raise_error(Puppet::Error, /Could not parse for environment #{env.name}/) end it "does not do anything if the ignore_import settings is set" do Puppet[:ignoreimport] = true expect(env.known_resource_types).to be_kind_of(Puppet::Resource::TypeCollection) end it "should mark the type collection as needing a reparse when there is an error parsing" do Puppet[:code] = "oops {" expect do env.known_resource_types end.to raise_error(Puppet::Error, /Syntax error at .../) expect(env.known_resource_types.require_reparse?).to be_true end end end end diff --git a/spec/unit/util/cacher_spec.rb b/spec/unit/util/cacher_spec.rb deleted file mode 100755 index 4e8bd1ec1..000000000 --- a/spec/unit/util/cacher_spec.rb +++ /dev/null @@ -1,107 +0,0 @@ -#! /usr/bin/env ruby -require 'spec_helper' - -require 'puppet/util/cacher' - -class CacheTest - @@count = 0 - - def self.count - @@count - end - - include Puppet::Util::Cacher - - cached_attr(:instance_cache, 10) do - @@count += 1 - {:number => @@count} - end -end - -describe Puppet::Util::Cacher do - before :each do - CacheTest.set_attr_ttl(:instance_cache, 10) - @object = CacheTest.new - end - - it "should return a value calculated from the provided block" do - @object.instance_cache.should == {:number => CacheTest.count} - end - - it "should return the cached value from the getter every time if the value is not expired" do - @object.instance_cache.should equal(@object.instance_cache) - end - - it "should regenerate and return a new value using the provided block if the value has expired" do - initial = @object.instance_cache - - # Ensure the value is expired immediately - CacheTest.set_attr_ttl(:instance_cache, -10) - @object.send(:set_expiration, :instance_cache) - - @object.instance_cache.should_not equal(initial) - end - - it "should be able to cache false values" do - @object.expects(:init_instance_cache).once.returns false - @object.instance_cache.should be_false - @object.instance_cache.should be_false - end - - it "should cache values again after expiration" do - initial = @object.instance_cache - - # Ensure the value is expired immediately - CacheTest.set_attr_ttl(:instance_cache, -10) - @object.send(:set_expiration, :instance_cache) - - # Reset ttl so this new value doesn't get expired - CacheTest.set_attr_ttl(:instance_cache, 10) - after_expiration = @object.instance_cache - - after_expiration.should_not == initial - @object.instance_cache.should == after_expiration - end - - it "should allow writing of the attribute" do - initial = @object.instance_cache - - @object.instance_cache = "another value" - @object.instance_cache.should == "another value" - end - - it "should update the expiration when the cached attribute is set manually" do - # Freeze time - now = Time.now - Time.stubs(:now).returns now - - @object.instance_cache - - # Set expiration to something far in the future - CacheTest.set_attr_ttl(:instance_cache, 60) - @object.send(:set_expiration, :instance_cache) - - CacheTest.set_attr_ttl(:instance_cache, 10) - - @object.instance_cache = "foo" - @object.instance_variable_get(:@attr_expirations)[:instance_cache].should == now + 10 - end - - it "should allow specification of a ttl as a string" do - klass = Class.new do - include Puppet::Util::Cacher - end - - klass.cached_attr(:myattr, "5") { 10 } - - klass.attr_ttl(:myattr).should == 5 - end - - it "should fail helpfully if the ttl cannot be converted to an integer" do - klass = Class.new do - include Puppet::Util::Cacher - end - - lambda { klass.cached_attr(:myattr, "yep") { 10 } }.should raise_error(ArgumentError) - end -end