diff options
Diffstat (limited to 'spec/integration')
39 files changed, 2498 insertions, 903 deletions
diff --git a/spec/integration/agent/logging_spec.rb b/spec/integration/agent/logging_spec.rb index c686397c8..1c4394a6e 100755 --- a/spec/integration/agent/logging_spec.rb +++ b/spec/integration/agent/logging_spec.rb @@ -88,6 +88,10 @@ describe 'agent logging' do else it "when evoked with #{argv}, logs to #{expected[:loggers].inspect} at level #{expected[:level]}" do + if Facter.value(:kernelmajversion).to_f < 6.0 + pending("requires win32-eventlog gem upgrade to 0.6.2 on Windows 2003") + end + # This logger is created by the Puppet::Settings object which creates and # applies a catalog to ensure that configuration files and users are in # place. diff --git a/spec/integration/application/doc_spec.rb b/spec/integration/application/doc_spec.rb index 77fc38625..040dde72d 100755 --- a/spec/integration/application/doc_spec.rb +++ b/spec/integration/application/doc_spec.rb @@ -35,11 +35,12 @@ describe Puppet::Application::Doc do end puppet = Puppet::Application[:doc] - Puppet[:modulepath] = modules_dir - Puppet[:manifest] = site_file puppet.options[:mode] = :rdoc - expect { puppet.run_command }.to exit_with 0 + env = Puppet::Node::Environment.create(:rdoc, [modules_dir], site_file) + Puppet.override(:current_environment => env) do + expect { puppet.run_command }.to exit_with 0 + end Puppet::FileSystem.exist?('doc').should be_true ensure diff --git a/spec/integration/configurer_spec.rb b/spec/integration/configurer_spec.rb index 2e0c7370e..be1f34ca3 100755 --- a/spec/integration/configurer_spec.rb +++ b/spec/integration/configurer_spec.rb @@ -6,20 +6,6 @@ require 'puppet/configurer' describe Puppet::Configurer do include PuppetSpec::Files - describe "when downloading plugins" do - it "should use the :pluginsignore setting, split on whitespace, for ignoring remote files" do - Puppet.settings.stubs(:use) - resource = Puppet::Type.type(:notify).new :name => "yay" - Puppet::Type.type(:file).expects(:new).at_most(2).with do |args| - args[:ignore] == Puppet[:pluginsignore].split(/\s+/) - end.returns resource - - configurer = Puppet::Configurer.new - configurer.stubs(:download_plugins?).returns true - configurer.download_plugins(Puppet::Node::Environment.remote(:testing)) - end - end - describe "when running" do before(:each) do @catalog = Puppet::Resource::Catalog.new("testing", Puppet.lookup(:environments).get(Puppet[:environment])) diff --git a/spec/integration/defaults_spec.rb b/spec/integration/defaults_spec.rb index 8c8432b6f..734785230 100755 --- a/spec/integration/defaults_spec.rb +++ b/spec/integration/defaults_spec.rb @@ -5,6 +5,32 @@ require 'puppet/defaults' require 'puppet/rails' describe "Puppet defaults" do + + describe "when default_manifest is set" do + it "returns ./manifests by default" do + expect(Puppet[:default_manifest]).to eq('./manifests') + end + + it "errors when $environment is part of the value" do + expect { + Puppet[:default_manifest] = '/$environment/manifest.pp' + }.to raise_error Puppet::Settings::ValidationError, /cannot interpolate.*\$environment/ + end + end + + describe "when disable_per_environment_manifest is set" do + it "returns false by default" do + expect(Puppet[:disable_per_environment_manifest]).to eq(false) + end + + it "errors when set to true and default_manifest is not an absolute path" do + expect { + Puppet[:default_manifest] = './some/relative/manifest.pp' + Puppet[:disable_per_environment_manifest] = true + }.to raise_error Puppet::Settings::ValidationError, /'default_manifest' setting must be.*absolute/ + end + end + describe "when setting the :factpath" do it "should add the :factpath to Facter's search paths" do Facter.expects(:search).with("/my/fact/path") diff --git a/spec/integration/environments/default_manifest_spec.rb b/spec/integration/environments/default_manifest_spec.rb new file mode 100644 index 000000000..6d4564037 --- /dev/null +++ b/spec/integration/environments/default_manifest_spec.rb @@ -0,0 +1,274 @@ +require 'spec_helper' + +module EnvironmentsDefaultManifestsSpec +describe "default manifests" do + FS = Puppet::FileSystem + + shared_examples_for "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 + + it "raises an exception if default_manifest has $environment in it" do + File.open(File.join(confdir, "puppet.conf"), "w") do |f| + f.puts(<<-EOF) + environmentpath=#{environmentpath} + default_manifest=/foo/$environment + EOF + end + + expect { Puppet.initialize_settings }.to raise_error(Puppet::Settings::ValidationError, /cannot interpolate.*\$environment.*in.*default_manifest/) + 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) + 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 + + describe 'using future parser' do + before :each do + Puppet[:parser] = 'future' + end + it_behaves_like 'puppet with default_manifest settings' + end + + describe 'using current parser' do + before :each do + Puppet[:parser] = 'current' + end + it_behaves_like 'puppet with default_manifest settings' + 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/faces/documentation_spec.rb b/spec/integration/faces/documentation_spec.rb index bd1f8008a..803d61599 100755 --- a/spec/integration/faces/documentation_spec.rb +++ b/spec/integration/faces/documentation_spec.rb @@ -16,10 +16,6 @@ describe "documentation of faces" do # bug in it, triggered in something the user might do. context "face help messages" do - # we need to set a bunk module path here, because without doing so, - # the autoloader will try to use it before it is initialized. - Puppet[:modulepath] = "/dev/null" - Puppet::Face.faces.sort.each do |face_name| # REVISIT: We should walk all versions of the face here... let :help do Puppet::Face[:help, :current] end diff --git a/spec/integration/file_bucket/file_spec.rb b/spec/integration/file_bucket/file_spec.rb index 2c411fdf7..f0dbecaa3 100644 --- a/spec/integration/file_bucket/file_spec.rb +++ b/spec/integration/file_bucket/file_spec.rb @@ -41,4 +41,25 @@ describe Puppet::FileBucket::File do end end end + + describe "saving binary files" do + describe "on Ruby 1.8.7", :if => RUBY_VERSION.match(/^1\.8/) do + let(:binary) { "\xD1\xF2\r\n\x81NuSc\x00" } + + it "does not error when the same contents are saved twice" do + bucket_file = Puppet::FileBucket::File.new(binary) + Puppet::FileBucket::File.indirection.save(bucket_file, bucket_file.name) + Puppet::FileBucket::File.indirection.save(bucket_file, bucket_file.name) + end + end + describe "on Ruby 1.9+", :if => RUBY_VERSION.match(/^1\.9|^2/) do + let(:binary) { "\xD1\xF2\r\n\x81NuSc\x00".force_encoding(Encoding::ASCII_8BIT) } + + it "does not error when the same contents are saved twice" do + bucket_file = Puppet::FileBucket::File.new(binary) + Puppet::FileBucket::File.indirection.save(bucket_file, bucket_file.name) + Puppet::FileBucket::File.indirection.save(bucket_file, bucket_file.name) + end + end + end end diff --git a/spec/integration/indirector/catalog/compiler_spec.rb b/spec/integration/indirector/catalog/compiler_spec.rb index 1e7f17298..11c448575 100755 --- a/spec/integration/indirector/catalog/compiler_spec.rb +++ b/spec/integration/indirector/catalog/compiler_spec.rb @@ -13,8 +13,6 @@ describe Puppet::Resource::Catalog::Compiler do @catalog.add_resource(@two = Puppet::Resource.new(:file, "/two")) end - after { Puppet.settings.clear } - it "should remove virtual resources when filtering" do @one.virtual = true Puppet::Resource::Catalog.indirection.terminus.filter(@catalog).resource_refs.should == [ @two.ref ] diff --git a/spec/integration/indirector/catalog/queue_spec.rb b/spec/integration/indirector/catalog/queue_spec.rb index fe359236f..00f3f2e53 100755 --- a/spec/integration/indirector/catalog/queue_spec.rb +++ b/spec/integration/indirector/catalog/queue_spec.rb @@ -17,8 +17,6 @@ describe "Puppet::Resource::Catalog::Queue" do Puppet[:trace] = true end - after { Puppet.settings.clear } - it "should render catalogs to pson and publish them via the queue client when catalogs are saved" do terminus = Puppet::Resource::Catalog.indirection.terminus(:queue) diff --git a/spec/integration/indirector/facts/facter_spec.rb b/spec/integration/indirector/facts/facter_spec.rb index c71ff0937..b552431f9 100644 --- a/spec/integration/indirector/facts/facter_spec.rb +++ b/spec/integration/indirector/facts/facter_spec.rb @@ -13,7 +13,7 @@ describe Puppet::Node::Facts::Facter do end end - Facter.stubs(:clear) + Facter.stubs(:reset) cat = compile_to_catalog('notify { $downcase_test: }', Puppet::Node.indirection.find('foo')) diff --git a/spec/integration/indirector/file_content/file_server_spec.rb b/spec/integration/indirector/file_content/file_server_spec.rb index 103a028a2..ee0db17a9 100755 --- a/spec/integration/indirector/file_content/file_server_spec.rb +++ b/spec/integration/indirector/file_content/file_server_spec.rb @@ -48,9 +48,9 @@ describe Puppet::Indirector::FileContent::FileServer, " when finding files" do file = File.join(modpath, "files", "myfile") File.open(file, "wb") { |f| f.write "1\r\n" } - Puppet.settings[:modulepath] = path + env = Puppet::Node::Environment.create(:foo, [path]) - result = Puppet::FileServing::Content.indirection.find("modules/mymod/myfile") + result = Puppet::FileServing::Content.indirection.find("modules/mymod/myfile", :environment => env) result.should_not be_nil result.should be_instance_of(Puppet::FileServing::Content) diff --git a/spec/integration/node/environment_spec.rb b/spec/integration/node/environment_spec.rb index f8a7ace7d..797e4105c 100755 --- a/spec/integration/node/environment_spec.rb +++ b/spec/integration/node/environment_spec.rb @@ -80,6 +80,30 @@ describe Puppet::Node::Environment do end end + shared_examples_for "the environment's initial import in the future" do |settings| + it "a manifest referring to a directory invokes recursive parsing of all its files in sorted order" do + settings.each do |name, value| + Puppet[name] = value + end + + # fixture has three files 00_a.pp, 01_b.pp, and 02_c.pp. The 'b' file + # depends on 'a' being evaluated first. The 'c' file is empty (to ensure + # empty things do not break the directory import). + # + dirname = my_fixture('sitedir2') + + # Set the manifest to the directory to make it parse and combine them when compiling + node = Puppet::Node.new('testnode', + :environment => Puppet::Node::Environment.create(:testing, [], dirname)) + + catalog = Puppet::Parser::Compiler.compile(node) + + expect(catalog).to have_resource('Class[A]') + expect(catalog).to have_resource('Class[B]') + expect(catalog).to have_resource('Notify[variables]').with_parameter(:message, "a: 10, b: 10 c: 20") + end + end + describe 'using classic parser' do it_behaves_like "the environment's initial import", :parser => 'current', @@ -92,18 +116,10 @@ describe Puppet::Node::Environment do describe 'using future parser' do it_behaves_like "the environment's initial import", :parser => 'future', - :evaluator => 'future', # Turned off because currently future parser turns on the binder which # causes lookup of facts that are uninitialized and it will fail with # errors for 'osfamily' etc. This can be turned back on when the binder # is taken out of the equation. :strict_variables => false - - context 'and evaluator current' do - it_behaves_like "the environment's initial import", - :parser => 'future', - :evaluator => 'current', - :strict_variables => false - end end end diff --git a/spec/integration/parser/catalog_spec.rb b/spec/integration/parser/catalog_spec.rb index e37eb591a..39aeb394e 100644 --- a/spec/integration/parser/catalog_spec.rb +++ b/spec/integration/parser/catalog_spec.rb @@ -75,6 +75,14 @@ describe "A catalog" do expect(resources_in(agent_catalog)).to_not include(*exported_resources) end end + end + + describe 'using classic parser' do + before :each do + Puppet[:parser] = 'current' + end + it_behaves_like 'when compiled' do + end it "compiles resource creation from appended array as two separate resources" do # moved here from acceptance test "jeff_append_to_array.rb" @@ -92,14 +100,6 @@ describe "A catalog" do end end - describe 'using classic parser' do - before :each do - Puppet[:parser] = 'current' - end - it_behaves_like 'when compiled' do - end - end - describe 'using future parser' do before :each do Puppet[:parser] = 'future' @@ -113,9 +113,9 @@ describe "A catalog" do end def master_and_agent_catalogs_for(manifest) - master_catalog = Puppet::Resource::Catalog::Compiler.new.filter(compile_to_catalog(manifest)) + compiler = Puppet::Resource::Catalog::Compiler.new + master_catalog = compiler.filter(compile_to_catalog(manifest)) agent_catalog = Puppet::Resource::Catalog.convert_from(:pson, master_catalog.render(:pson)) - [master_catalog, agent_catalog] end diff --git a/spec/integration/parser/class_spec.rb b/spec/integration/parser/class_spec.rb new file mode 100644 index 000000000..9f63eb083 --- /dev/null +++ b/spec/integration/parser/class_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' +require 'puppet_spec/language' + +describe "Class expressions" do + extend PuppetSpec::Language + + before :each do + Puppet[:parser] = 'future' + end + + produces( + "class hi { }" => '!defined(Class[Hi])', + + "class hi { } include hi" => 'defined(Class[Hi])', + "include(hi) class hi { }" => 'defined(Class[Hi])', + + "class hi { } class { hi: }" => 'defined(Class[Hi])', + "class { hi: } class hi { }" => 'defined(Class[Hi])', + + "class bye { } class hi inherits bye { } include hi" => 'defined(Class[Hi]) and defined(Class[Bye])') + + produces(<<-EXAMPLE => 'defined(Notify[foo]) and defined(Notify[bar]) and !defined(Notify[foo::bar])') + class bar { notify { 'bar': } } + class foo::bar { notify { 'foo::bar': } } + class foo inherits bar { notify { 'foo': } } + + include foo + EXAMPLE + + produces(<<-EXAMPLE => 'defined(Notify[foo]) and defined(Notify[bar]) and !defined(Notify[foo::bar])') + class bar { notify { 'bar': } } + class foo::bar { notify { 'foo::bar': } } + class foo inherits ::bar { notify { 'foo': } } + + include foo + EXAMPLE +end diff --git a/spec/integration/parser/collector_spec.rb b/spec/integration/parser/collector_spec.rb index 49ce74583..55ac66a5b 100755 --- a/spec/integration/parser/collector_spec.rb +++ b/spec/integration/parser/collector_spec.rb @@ -14,104 +14,263 @@ describe Puppet::Parser::Collector do messages.should include(*expected_messages) end - it "matches on title" do - expect_the_message_to_be(["the message"], <<-MANIFEST) - @notify { "testing": message => "the message" } + shared_examples_for "virtual resource collection" do + it "matches everything when no query given" do + expect_the_message_to_be(["the other message", "the message"], <<-MANIFEST) + @notify { "testing": message => "the message" } + @notify { "other": message => "the other message" } - Notify <| title == "testing" |> - MANIFEST - end + Notify <| |> + MANIFEST + end - it "matches on other parameters" do - expect_the_message_to_be(["the message"], <<-MANIFEST) - @notify { "testing": message => "the message" } - @notify { "other testing": message => "the wrong message" } + it "matches regular resources " do + expect_the_message_to_be(["changed", "changed"], <<-MANIFEST) + notify { "testing": message => "the message" } + notify { "other": message => "the other message" } - Notify <| message == "the message" |> - MANIFEST - end + Notify <| |> { message => "changed" } + MANIFEST + end - it "allows criteria to be combined with 'and'" do - expect_the_message_to_be(["the message"], <<-MANIFEST) - @notify { "testing": message => "the message" } - @notify { "other": message => "the message" } + it "matches on tags" do + expect_the_message_to_be(["wanted"], <<-MANIFEST) + @notify { "testing": tag => ["one"], message => "wanted" } + @notify { "other": tag => ["two"], message => "unwanted" } - Notify <| title == "testing" and message == "the message" |> - MANIFEST - end + Notify <| tag == one |> + MANIFEST + end - it "allows criteria to be combined with 'or'" do - expect_the_message_to_be(["the message", "other message"], <<-MANIFEST) - @notify { "testing": message => "the message" } - @notify { "other": message => "other message" } - @notify { "yet another": message => "different message" } + it "matches on title" do + expect_the_message_to_be(["the message"], <<-MANIFEST) + @notify { "testing": message => "the message" } - Notify <| title == "testing" or message == "other message" |> - MANIFEST - end + Notify <| title == "testing" |> + MANIFEST + end - it "allows criteria to be combined with 'or'" do - expect_the_message_to_be(["the message", "other message"], <<-MANIFEST) - @notify { "testing": message => "the message" } - @notify { "other": message => "other message" } - @notify { "yet another": message => "different message" } + it "matches on other parameters" do + expect_the_message_to_be(["the message"], <<-MANIFEST) + @notify { "testing": message => "the message" } + @notify { "other testing": message => "the wrong message" } - Notify <| title == "testing" or message == "other message" |> - MANIFEST - end + Notify <| message == "the message" |> + MANIFEST + end - it "allows criteria to be grouped with parens" do - expect_the_message_to_be(["the message", "different message"], <<-MANIFEST) - @notify { "testing": message => "different message", withpath => true } - @notify { "other": message => "the message" } - @notify { "yet another": message => "the message", withpath => true } + it "matches against elements of an array valued parameter" do + expect_the_message_to_be([["the", "message"]], <<-MANIFEST) + @notify { "testing": message => ["the", "message"] } + @notify { "other testing": message => ["not", "here"] } - Notify <| (title == "testing" or message == "the message") and withpath == true |> - MANIFEST - end + Notify <| message == "message" |> + MANIFEST + end - it "does not do anything if nothing matches" do - expect_the_message_to_be([], <<-MANIFEST) - @notify { "testing": message => "different message" } + it "allows criteria to be combined with 'and'" do + expect_the_message_to_be(["the message"], <<-MANIFEST) + @notify { "testing": message => "the message" } + @notify { "other": message => "the message" } - Notify <| title == "does not exist" |> - MANIFEST - end + Notify <| title == "testing" and message == "the message" |> + MANIFEST + end - it "excludes items with inequalities" do - expect_the_message_to_be(["good message"], <<-MANIFEST) - @notify { "testing": message => "good message" } - @notify { "the wrong one": message => "bad message" } + it "allows criteria to be combined with 'or'" do + expect_the_message_to_be(["the message", "other message"], <<-MANIFEST) + @notify { "testing": message => "the message" } + @notify { "other": message => "other message" } + @notify { "yet another": message => "different message" } - Notify <| title != "the wrong one" |> - MANIFEST - end + Notify <| title == "testing" or message == "other message" |> + MANIFEST + end - context "issue #10963" do - it "collects with override when inside a class" do - expect_the_message_to_be(["overridden message"], <<-MANIFEST) - @notify { "testing": message => "original message" } + it "allows criteria to be combined with 'or'" do + expect_the_message_to_be(["the message", "other message"], <<-MANIFEST) + @notify { "testing": message => "the message" } + @notify { "other": message => "other message" } + @notify { "yet another": message => "different message" } - include collector_test - class collector_test { - Notify <| |> { - message => "overridden message" - } - } + Notify <| title == "testing" or message == "other message" |> + MANIFEST + end + + it "allows criteria to be grouped with parens" do + expect_the_message_to_be(["the message", "different message"], <<-MANIFEST) + @notify { "testing": message => "different message", withpath => true } + @notify { "other": message => "the message" } + @notify { "yet another": message => "the message", withpath => true } + + Notify <| (title == "testing" or message == "the message") and withpath == true |> MANIFEST end - it "collects with override when inside a define" do - expect_the_message_to_be(["overridden message"], <<-MANIFEST) - @notify { "testing": message => "original message" } + it "does not do anything if nothing matches" do + expect_the_message_to_be([], <<-MANIFEST) + @notify { "testing": message => "different message" } + + Notify <| title == "does not exist" |> + MANIFEST + end + + it "excludes items with inequalities" do + expect_the_message_to_be(["good message"], <<-MANIFEST) + @notify { "testing": message => "good message" } + @notify { "the wrong one": message => "bad message" } + + Notify <| title != "the wrong one" |> + MANIFEST + end + + it "does not exclude resources with unequal arrays" do + expect_the_message_to_be(["message", ["not this message", "or this one"]], <<-MANIFEST) + @notify { "testing": message => "message" } + @notify { "the wrong one": message => ["not this message", "or this one"] } + + Notify <| message != "not this message" |> + MANIFEST + end + + it "does not exclude tags with inequalities" do + expect_the_message_to_be(["wanted message", "the way it works"], <<-MANIFEST) + @notify { "testing": tag => ["wanted"], message => "wanted message" } + @notify { "other": tag => ["why"], message => "the way it works" } + + Notify <| tag != "why" |> + MANIFEST + end + + it "does not collect classes" do + node = Puppet::Node.new('the node') + expect do + catalog = compile_to_catalog(<<-MANIFEST, node) + class theclass { + @notify { "testing": message => "good message" } + } + Class <| |> + MANIFEST + end.to raise_error(/Classes cannot be collected/) + end + + context "overrides" do + it "modifies an existing array" do + expect_the_message_to_be([["original message", "extra message"]], <<-MANIFEST) + @notify { "testing": message => ["original message"] } - collector_test { testing: } - define collector_test() { Notify <| |> { - message => "overridden message" + message +> "extra message" } - } - MANIFEST + MANIFEST + end + + it "converts a scalar to an array" do + expect_the_message_to_be([["original message", "extra message"]], <<-MANIFEST) + @notify { "testing": message => "original message" } + + Notify <| |> { + message +> "extra message" + } + MANIFEST + end + + it "collects with override when inside a class (#10963)" do + expect_the_message_to_be(["overridden message"], <<-MANIFEST) + @notify { "testing": message => "original message" } + + include collector_test + class collector_test { + Notify <| |> { + message => "overridden message" + } + } + MANIFEST + end + + it "collects with override when inside a define (#10963)" do + expect_the_message_to_be(["overridden message"], <<-MANIFEST) + @notify { "testing": message => "original message" } + + collector_test { testing: } + define collector_test() { + Notify <| |> { + message => "overridden message" + } + } + MANIFEST + end + + # Catches regression in implemented behavior, this is not to be taken as this is the wanted behavior + # but it has been this way for a long time. + it "collects and overrides user defined resources immediately (before queue is evaluated)" do + expect_the_message_to_be(["overridden"], <<-MANIFEST) + define foo($message) { + notify { "testing": message => $message } + } + foo { test: message => 'given' } + Foo <| |> { message => 'overridden' } + MANIFEST + end + + # Catches regression in implemented behavior, this is not to be taken as this is the wanted behavior + # but it has been this way for a long time. + it "collects and overrides user defined resources immediately (virtual resources not queued)" do + expect_the_message_to_be(["overridden"], <<-MANIFEST) + define foo($message) { + @notify { "testing": message => $message } + } + foo { test: message => 'given' } + Notify <| |> # must be collected or the assertion does not find it + Foo <| |> { message => 'overridden' } + MANIFEST + end + + # Catches regression in implemented behavior, this is not to be taken as this is the wanted behavior + # but it has been this way for a long time. + # Note difference from none +> case where the override takes effect + it "collects and overrides user defined resources with +>" do + expect_the_message_to_be([["given", "overridden"]], <<-MANIFEST) + define foo($message) { + notify { "$name": message => $message } + } + foo { test: message => ['given'] } + Notify <| |> { message +> ['overridden'] } + MANIFEST + end + + it "collects and overrides virtual resources multiple times using multiple collects" do + expect_the_message_to_be(["overridden2"], <<-MANIFEST) + @notify { "testing": message => "original" } + Notify <| |> { message => 'overridden1' } + Notify <| |> { message => 'overridden2' } + MANIFEST + end + + it "collects and overrides non virtual resources multiple times using multiple collects" do + expect_the_message_to_be(["overridden2"], <<-MANIFEST) + notify { "testing": message => "original" } + Notify <| |> { message => 'overridden1' } + Notify <| |> { message => 'overridden2' } + MANIFEST + end + end end + + describe "in the current parser" do + before :each do + Puppet[:parser] = 'current' + end + + it_behaves_like "virtual resource collection" + end + + describe "in the future parser" do + before :each do + Puppet[:parser] = 'future' + end + + it_behaves_like "virtual resource collection" + end end diff --git a/spec/integration/parser/compiler_spec.rb b/spec/integration/parser/compiler_spec.rb index f10ce1adc..e6069d834 100755 --- a/spec/integration/parser/compiler_spec.rb +++ b/spec/integration/parser/compiler_spec.rb @@ -15,499 +15,511 @@ describe "Puppet::Parser::Compiler" do @scope = stub 'scope', :resource => @scope_resource, :source => mock("source") end - after do - Puppet.settings.clear - end - - # shared because tests are invoked both for classic and future parser - # - shared_examples_for "the compiler" do - it "should be able to determine the configuration version from a local version control repository" do - pending("Bug #14071 about semantics of Puppet::Util::Execute on Windows", :if => Puppet.features.microsoft_windows?) do - # This should always work, because we should always be - # in the puppet repo when we run this. - version = %x{git rev-parse HEAD}.chomp + it "should be able to determine the configuration version from a local version control repository" do + pending("Bug #14071 about semantics of Puppet::Util::Execute on Windows", :if => Puppet.features.microsoft_windows?) do + # This should always work, because we should always be + # in the puppet repo when we run this. + version = %x{git rev-parse HEAD}.chomp - Puppet.settings[:config_version] = 'git rev-parse HEAD' + Puppet.settings[:config_version] = 'git rev-parse HEAD' - @parser = Puppet::Parser::ParserFactory.parser "development" - @compiler = Puppet::Parser::Compiler.new(@node) + @parser = Puppet::Parser::ParserFactory.parser "development" + @compiler = Puppet::Parser::Compiler.new(@node) - @compiler.catalog.version.should == version - end + @compiler.catalog.version.should == version end + end + + it "should not create duplicate resources when a class is referenced both directly and indirectly by the node classifier (4792)" do + Puppet[:code] = <<-PP + class foo + { + notify { foo_notify: } + include bar + } + class bar + { + notify { bar_notify: } + } + PP + + @node.stubs(:classes).returns(['foo', 'bar']) + + catalog = Puppet::Parser::Compiler.compile(@node) + + catalog.resource("Notify[foo_notify]").should_not be_nil + catalog.resource("Notify[bar_notify]").should_not be_nil + end - it "should not create duplicate resources when a class is referenced both directly and indirectly by the node classifier (4792)" do + describe "when resolving class references" do + it "should favor local scope, even if there's an included class in topscope" do Puppet[:code] = <<-PP - class foo - { - notify { foo_notify: } - include bar + class experiment { + class baz { + } + notify {"x" : require => Class[Baz] } } - class bar - { - notify { bar_notify: } + class baz { } + include baz + include experiment + include experiment::baz PP - @node.stubs(:classes).returns(['foo', 'bar']) + catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) - catalog = Puppet::Parser::Compiler.compile(@node) + notify_resource = catalog.resource( "Notify[x]" ) - catalog.resource("Notify[foo_notify]").should_not be_nil - catalog.resource("Notify[bar_notify]").should_not be_nil + notify_resource[:require].title.should == "Experiment::Baz" end - describe "when resolving class references" do - it "should favor local scope, even if there's an included class in topscope" do - Puppet[:code] = <<-PP - class experiment { - class baz { - } - notify {"x" : require => Class[Baz] } - } + it "should favor local scope, even if there's an unincluded class in topscope" do + Puppet[:code] = <<-PP + class experiment { class baz { } - include baz - include experiment - include experiment::baz - PP - - catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) + notify {"x" : require => Class[Baz] } + } + class baz { + } + include experiment + include experiment::baz + PP - notify_resource = catalog.resource( "Notify[x]" ) + catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) - notify_resource[:require].title.should == "Experiment::Baz" - end + notify_resource = catalog.resource( "Notify[x]" ) - it "should favor local scope, even if there's an unincluded class in topscope" do - Puppet[:code] = <<-PP - class experiment { - class baz { + notify_resource[:require].title.should == "Experiment::Baz" + end + end + describe "(ticket #13349) when explicitly specifying top scope" do + ["class {'::bar::baz':}", "include ::bar::baz"].each do |include| + describe "with #{include}" do + it "should find the top level class" do + Puppet[:code] = <<-MANIFEST + class { 'foo::test': } + class foo::test { + #{include} } - notify {"x" : require => Class[Baz] } - } - class baz { - } - include experiment - include experiment::baz - PP - - catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) + class bar::baz { + notify { 'good!': } + } + class foo::bar::baz { + notify { 'bad!': } + } + MANIFEST - notify_resource = catalog.resource( "Notify[x]" ) + catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) - notify_resource[:require].title.should == "Experiment::Baz" - end - end - describe "(ticket #13349) when explicitly specifying top scope" do - ["class {'::bar::baz':}", "include ::bar::baz"].each do |include| - describe "with #{include}" do - it "should find the top level class" do - Puppet[:code] = <<-MANIFEST - class { 'foo::test': } - class foo::test { - #{include} - } - class bar::baz { - notify { 'good!': } - } - class foo::bar::baz { - notify { 'bad!': } - } - MANIFEST - - catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) - - catalog.resource("Class[Bar::Baz]").should_not be_nil - catalog.resource("Notify[good!]").should_not be_nil - catalog.resource("Class[Foo::Bar::Baz]").should be_nil - catalog.resource("Notify[bad!]").should be_nil - end + catalog.resource("Class[Bar::Baz]").should_not be_nil + catalog.resource("Notify[good!]").should_not be_nil + catalog.resource("Class[Foo::Bar::Baz]").should be_nil + catalog.resource("Notify[bad!]").should be_nil end end end + end - it "should recompute the version after input files are re-parsed" do - Puppet[:code] = 'class foo { }' - Time.stubs(:now).returns(1) - node = Puppet::Node.new('mynode') - Puppet::Parser::Compiler.compile(node).version.should == 1 - Time.stubs(:now).returns(2) - Puppet::Parser::Compiler.compile(node).version.should == 1 # no change because files didn't change - Puppet::Resource::TypeCollection.any_instance.stubs(:stale?).returns(true).then.returns(false) # pretend change - Puppet::Parser::Compiler.compile(node).version.should == 2 - end - - ['class', 'define', 'node'].each do |thing| - it "should not allow '#{thing}' inside evaluated conditional constructs" do - Puppet[:code] = <<-PP - if true { - #{thing} foo { - } - notify { decoy: } - } - PP - - begin - Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) - raise "compilation should have raised Puppet::Error" - rescue Puppet::Error => e - e.message.should =~ /at line 2/ - end - end - end + it "should recompute the version after input files are re-parsed" do + Puppet[:code] = 'class foo { }' + Time.stubs(:now).returns(1) + node = Puppet::Node.new('mynode') + Puppet::Parser::Compiler.compile(node).version.should == 1 + Time.stubs(:now).returns(2) + Puppet::Parser::Compiler.compile(node).version.should == 1 # no change because files didn't change + Puppet::Resource::TypeCollection.any_instance.stubs(:stale?).returns(true).then.returns(false) # pretend change + Puppet::Parser::Compiler.compile(node).version.should == 2 + end - it "should not allow classes inside unevaluated conditional constructs" do + ['class', 'define', 'node'].each do |thing| + it "should not allow '#{thing}' inside evaluated conditional constructs" do Puppet[:code] = <<-PP - if false { - class foo { + if true { + #{thing} foo { } + notify { decoy: } } PP - lambda { Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) }.should raise_error(Puppet::Error) + begin + Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) + raise "compilation should have raised Puppet::Error" + rescue Puppet::Error => e + e.message.should =~ /at line 2/ + end end + end - describe "when defining relationships" do - def extract_name(ref) - ref.sub(/File\[(\w+)\]/, '\1') - end + it "should not allow classes inside unevaluated conditional constructs" do + Puppet[:code] = <<-PP + if false { + class foo { + } + } + PP - let(:node) { Puppet::Node.new('mynode') } - let(:code) do - <<-MANIFEST - file { [a,b,c]: - mode => 0644, - } - file { [d,e]: - mode => 0755, - } - MANIFEST - end - let(:expected_relationships) { [] } - let(:expected_subscriptions) { [] } + lambda { Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) }.should raise_error(Puppet::Error) + end - before :each do - Puppet[:code] = code - end + describe "when defining relationships" do + def extract_name(ref) + ref.sub(/File\[(\w+)\]/, '\1') + end + + let(:node) { Puppet::Node.new('mynode') } + let(:code) do + <<-MANIFEST + file { [a,b,c]: + mode => '0644', + } + file { [d,e]: + mode => '0755', + } + MANIFEST + end + let(:expected_relationships) { [] } + let(:expected_subscriptions) { [] } - after :each do - catalog = Puppet::Parser::Compiler.compile(node) + before :each do + Puppet[:code] = code + end - resources = catalog.resources.select { |res| res.type == 'File' } + after :each do + catalog = Puppet::Parser::Compiler.compile(node) - actual_relationships, actual_subscriptions = [:before, :notify].map do |relation| - resources.map do |res| - dependents = Array(res[relation]) - dependents.map { |ref| [res.title, extract_name(ref)] } - end.inject(&:concat) - end + resources = catalog.resources.select { |res| res.type == 'File' } - actual_relationships.should =~ expected_relationships - actual_subscriptions.should =~ expected_subscriptions + actual_relationships, actual_subscriptions = [:before, :notify].map do |relation| + resources.map do |res| + dependents = Array(res[relation]) + dependents.map { |ref| [res.title, extract_name(ref)] } + end.inject(&:concat) end - it "should create a relationship" do - code << "File[a] -> File[b]" + actual_relationships.should =~ expected_relationships + actual_subscriptions.should =~ expected_subscriptions + end - expected_relationships << ['a','b'] - end + it "should create a relationship" do + code << "File[a] -> File[b]" - it "should create a subscription" do - code << "File[a] ~> File[b]" + expected_relationships << ['a','b'] + end - expected_subscriptions << ['a', 'b'] - end + it "should create a subscription" do + code << "File[a] ~> File[b]" - it "should create relationships using title arrays" do - code << "File[a,b] -> File[c,d]" + expected_subscriptions << ['a', 'b'] + end - expected_relationships.concat [ - ['a', 'c'], - ['b', 'c'], - ['a', 'd'], - ['b', 'd'], - ] - end + it "should create relationships using title arrays" do + code << "File[a,b] -> File[c,d]" - it "should create relationships using collection expressions" do - code << "File <| mode == 0644 |> -> File <| mode == 0755 |>" - - expected_relationships.concat [ - ['a', 'd'], - ['b', 'd'], - ['c', 'd'], - ['a', 'e'], - ['b', 'e'], - ['c', 'e'], - ] - end + expected_relationships.concat [ + ['a', 'c'], + ['b', 'c'], + ['a', 'd'], + ['b', 'd'], + ] + end - it "should create relationships using resource names" do - code << "'File[a]' -> 'File[b]'" + it "should create relationships using collection expressions" do + code << "File <| mode == 0644 |> -> File <| mode == 0755 |>" + + expected_relationships.concat [ + ['a', 'd'], + ['b', 'd'], + ['c', 'd'], + ['a', 'e'], + ['b', 'e'], + ['c', 'e'], + ] + end - expected_relationships << ['a', 'b'] - end + it "should create relationships using resource names" do + code << "'File[a]' -> 'File[b]'" - it "should create relationships using variables" do - code << <<-MANIFEST - $var = File[a] - $var -> File[b] - MANIFEST + expected_relationships << ['a', 'b'] + end - expected_relationships << ['a', 'b'] - end + it "should create relationships using variables" do + code << <<-MANIFEST + $var = File[a] + $var -> File[b] + MANIFEST - it "should create relationships using case statements" do - code << <<-MANIFEST - $var = 10 - case $var { - 10: { - file { s1: } - } - 12: { - file { s2: } - } + expected_relationships << ['a', 'b'] + end + + it "should create relationships using case statements" do + code << <<-MANIFEST + $var = 10 + case $var { + 10: { + file { s1: } } - -> - case $var + 2 { - 10: { - file { t1: } - } - 12: { - file { t2: } - } + 12: { + file { s2: } } - MANIFEST + } + -> + case $var + 2 { + 10: { + file { t1: } + } + 12: { + file { t2: } + } + } + MANIFEST - expected_relationships << ['s1', 't2'] - end + expected_relationships << ['s1', 't2'] + end - it "should create relationships using array members" do - code << <<-MANIFEST - $var = [ [ [ File[a], File[b] ] ] ] - $var[0][0][0] -> $var[0][0][1] - MANIFEST + it "should create relationships using array members" do + code << <<-MANIFEST + $var = [ [ [ File[a], File[b] ] ] ] + $var[0][0][0] -> $var[0][0][1] + MANIFEST - expected_relationships << ['a', 'b'] - end + expected_relationships << ['a', 'b'] + end - it "should create relationships using hash members" do - code << <<-MANIFEST - $var = {'foo' => {'bar' => {'source' => File[a], 'target' => File[b]}}} - $var[foo][bar][source] -> $var[foo][bar][target] - MANIFEST + it "should create relationships using hash members" do + code << <<-MANIFEST + $var = {'foo' => {'bar' => {'source' => File[a], 'target' => File[b]}}} + $var[foo][bar][source] -> $var[foo][bar][target] + MANIFEST - expected_relationships << ['a', 'b'] - end + expected_relationships << ['a', 'b'] + end - it "should create relationships using resource declarations" do - code << "file { l: } -> file { r: }" + it "should create relationships using resource declarations" do + code << "file { l: } -> file { r: }" - expected_relationships << ['l', 'r'] - end + expected_relationships << ['l', 'r'] + end - it "should chain relationships" do - code << "File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]" + it "should chain relationships" do + code << "File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]" - expected_relationships << ['a', 'b'] << ['d', 'c'] - expected_subscriptions << ['b', 'c'] << ['e', 'd'] - end + expected_relationships << ['a', 'b'] << ['d', 'c'] + expected_subscriptions << ['b', 'c'] << ['e', 'd'] end + end - context 'when working with immutable node data' do - context 'and have opted in to immutable_node_data' do - before :each do - Puppet[:immutable_node_data] = true - end + context 'when working with immutable node data' do + context 'and have opted in to immutable_node_data' do + before :each do + Puppet[:immutable_node_data] = true + end - def node_with_facts(facts) - Puppet[:facts_terminus] = :memory - Puppet::Node::Facts.indirection.save(Puppet::Node::Facts.new("testing", facts)) - node = Puppet::Node.new("testing") - node.fact_merge - node - end + def node_with_facts(facts) + Puppet[:facts_terminus] = :memory + Puppet::Node::Facts.indirection.save(Puppet::Node::Facts.new("testing", facts)) + node = Puppet::Node.new("testing") + node.fact_merge + node + end - matcher :fail_compile_with do |node, message_regex| - match do |manifest| - @error = nil - begin - compile_to_catalog(manifest, node) - false - rescue Puppet::Error => e - @error = e - message_regex.match(e.message) - end + matcher :fail_compile_with do |node, message_regex| + match do |manifest| + @error = nil + begin + PuppetSpec::Compiler.compile_to_catalog(manifest, node) + false + rescue Puppet::Error => e + @error = e + message_regex.match(e.message) end + end - failure_message_for_should do - if @error - "failed with #{@error}\n#{@error.backtrace}" - else - "did not fail" - end + failure_message_for_should do + if @error + "failed with #{@error}\n#{@error.backtrace}" + else + "did not fail" end end + end - it 'should make $facts available' do - node = node_with_facts('the_facts' => 'straight') + it 'should make $facts available' do + node = node_with_facts('the_facts' => 'straight') - catalog = compile_to_catalog(<<-MANIFEST, node) - notify { 'test': message => $facts[the_facts] } - MANIFEST + catalog = compile_to_catalog(<<-MANIFEST, node) + notify { 'test': message => $facts[the_facts] } + MANIFEST - catalog.resource("Notify[test]")[:message].should == "straight" - end + catalog.resource("Notify[test]")[:message].should == "straight" + end - it 'should make $facts reserved' do - node = node_with_facts('the_facts' => 'straight') + it 'should make $facts reserved' do + node = node_with_facts('the_facts' => 'straight') - expect('$facts = {}').to fail_compile_with(node, /assign to a reserved variable name: 'facts'/) - expect('class a { $facts = {} } include a').to fail_compile_with(node, /assign to a reserved variable name: 'facts'/) - end + expect('$facts = {}').to fail_compile_with(node, /assign to a reserved variable name: 'facts'/) + expect('class a { $facts = {} } include a').to fail_compile_with(node, /assign to a reserved variable name: 'facts'/) + end - it 'should make $facts immutable' do - node = node_with_facts('string' => 'value', 'array' => ['string'], 'hash' => { 'a' => 'string' }, 'number' => 1, 'boolean' => true) + it 'should make $facts immutable' do + node = node_with_facts('string' => 'value', 'array' => ['string'], 'hash' => { 'a' => 'string' }, 'number' => 1, 'boolean' => true) - expect('$i=inline_template("<% @facts[%q{new}] = 2 %>")').to fail_compile_with(node, /frozen Hash/i) - expect('$i=inline_template("<% @facts[%q{string}].chop! %>")').to fail_compile_with(node, /frozen String/i) + expect('$i=inline_template("<% @facts[%q{new}] = 2 %>")').to fail_compile_with(node, /frozen Hash/i) + expect('$i=inline_template("<% @facts[%q{string}].chop! %>")').to fail_compile_with(node, /frozen String/i) - expect('$i=inline_template("<% @facts[%q{array}][0].chop! %>")').to fail_compile_with(node, /frozen String/i) - expect('$i=inline_template("<% @facts[%q{array}][1] = 2 %>")').to fail_compile_with(node, /frozen Array/i) + expect('$i=inline_template("<% @facts[%q{array}][0].chop! %>")').to fail_compile_with(node, /frozen String/i) + expect('$i=inline_template("<% @facts[%q{array}][1] = 2 %>")').to fail_compile_with(node, /frozen Array/i) - expect('$i=inline_template("<% @facts[%q{hash}][%q{a}].chop! %>")').to fail_compile_with(node, /frozen String/i) - expect('$i=inline_template("<% @facts[%q{hash}][%q{b}] = 2 %>")').to fail_compile_with(node, /frozen Hash/i) - end + expect('$i=inline_template("<% @facts[%q{hash}][%q{a}].chop! %>")').to fail_compile_with(node, /frozen String/i) + expect('$i=inline_template("<% @facts[%q{hash}][%q{b}] = 2 %>")').to fail_compile_with(node, /frozen Hash/i) + end - it 'should make $facts available even if there are no facts' do - Puppet[:facts_terminus] = :memory - node = Puppet::Node.new("testing2") - node.fact_merge + it 'should make $facts available even if there are no facts' do + Puppet[:facts_terminus] = :memory + node = Puppet::Node.new("testing2") + node.fact_merge - catalog = compile_to_catalog(<<-MANIFEST, node) - notify { 'test': message => $facts } - MANIFEST + catalog = compile_to_catalog(<<-MANIFEST, node) + notify { 'test': message => $facts } + MANIFEST - expect(catalog).to have_resource("Notify[test]").with_parameter(:message, {}) - end + expect(catalog).to have_resource("Notify[test]").with_parameter(:message, {}) end + end - context 'and have not opted in to immutable_node_data' do - before :each do - Puppet[:immutable_node_data] = false - end + context 'and have not opted in to immutable_node_data' do + before :each do + Puppet[:immutable_node_data] = false + end - it 'should not make $facts available' do - Puppet[:facts_terminus] = :memory - facts = Puppet::Node::Facts.new("testing", 'the_facts' => 'straight') - Puppet::Node::Facts.indirection.save(facts) - node = Puppet::Node.new("testing") - node.fact_merge + it 'should not make $facts available' do + Puppet[:facts_terminus] = :memory + facts = Puppet::Node::Facts.new("testing", 'the_facts' => 'straight') + Puppet::Node::Facts.indirection.save(facts) + node = Puppet::Node.new("testing") + node.fact_merge - catalog = compile_to_catalog(<<-MANIFEST, node) - notify { 'test': message => "An $facts space" } - MANIFEST + catalog = compile_to_catalog(<<-MANIFEST, node) + notify { 'test': message => "An $facts space" } + MANIFEST - catalog.resource("Notify[test]")[:message].should == "An space" - end + catalog.resource("Notify[test]")[:message].should == "An space" end end + end - context 'when working with the trusted data hash' do - context 'and have opted in to trusted_node_data' do - before :each do - Puppet[:trusted_node_data] = true - end - - it 'should make $trusted available' do - node = Puppet::Node.new("testing") - node.trusted_data = { "data" => "value" } + context 'when working with the trusted data hash' do + context 'and have opted in to trusted_node_data' do + before :each do + Puppet[:trusted_node_data] = true + end - catalog = compile_to_catalog(<<-MANIFEST, node) - notify { 'test': message => $trusted[data] } - MANIFEST + it 'should make $trusted available' do + node = Puppet::Node.new("testing") + node.trusted_data = { "data" => "value" } - catalog.resource("Notify[test]")[:message].should == "value" - end + catalog = compile_to_catalog(<<-MANIFEST, node) + notify { 'test': message => $trusted[data] } + MANIFEST - it 'should not allow assignment to $trusted' do - node = Puppet::Node.new("testing") - node.trusted_data = { "data" => "value" } - - expect do - catalog = compile_to_catalog(<<-MANIFEST, node) - $trusted = 'changed' - notify { 'test': message => $trusted == 'changed' } - MANIFEST - catalog.resource("Notify[test]")[:message].should == true - end.to raise_error(Puppet::Error, /Attempt to assign to a reserved variable name: 'trusted'/) - end + catalog.resource("Notify[test]")[:message].should == "value" + end - it 'should not allow addition to $trusted hash' do - node = Puppet::Node.new("testing") - node.trusted_data = { "data" => "value" } - - expect do - catalog = compile_to_catalog(<<-MANIFEST, node) - $trusted['extra'] = 'added' - notify { 'test': message => $trusted['extra'] == 'added' } - MANIFEST - catalog.resource("Notify[test]")[:message].should == true - # different errors depending on regular or future parser - end.to raise_error(Puppet::Error, /(can't modify frozen [hH]ash)|(Illegal attempt to assign)/) - end + it 'should not allow assignment to $trusted' do + node = Puppet::Node.new("testing") + node.trusted_data = { "data" => "value" } - it 'should not allow addition to $trusted hash via Ruby inline template' do - node = Puppet::Node.new("testing") - node.trusted_data = { "data" => "value" } - - expect do - catalog = compile_to_catalog(<<-MANIFEST, node) - $dummy = inline_template("<% @trusted['extra'] = 'added' %> lol") - notify { 'test': message => $trusted['extra'] == 'added' } - MANIFEST - catalog.resource("Notify[test]")[:message].should == true - end.to raise_error(Puppet::Error, /can't modify frozen [hH]ash/) - end + expect do + catalog = compile_to_catalog(<<-MANIFEST, node) + $trusted = 'changed' + notify { 'test': message => $trusted == 'changed' } + MANIFEST + catalog.resource("Notify[test]")[:message].should == true + end.to raise_error(Puppet::Error, /Attempt to assign to a reserved variable name: 'trusted'/) end - context 'and have not opted in to trusted_node_data' do - before :each do - Puppet[:trusted_node_data] = false - end - - it 'should not make $trusted available' do - node = Puppet::Node.new("testing") - node.trusted_data = { "data" => "value" } + it 'should not allow addition to $trusted hash' do + node = Puppet::Node.new("testing") + node.trusted_data = { "data" => "value" } + expect do catalog = compile_to_catalog(<<-MANIFEST, node) - notify { 'test': message => $trusted == undef } + $trusted['extra'] = 'added' + notify { 'test': message => $trusted['extra'] == 'added' } MANIFEST - catalog.resource("Notify[test]")[:message].should == true - end + # different errors depending on regular or future parser + end.to raise_error(Puppet::Error, /(can't modify frozen [hH]ash)|(Illegal attempt to assign)/) + end - it 'should allow assignment to $trusted' do - node = Puppet::Node.new("testing") + it 'should not allow addition to $trusted hash via Ruby inline template' do + node = Puppet::Node.new("testing") + node.trusted_data = { "data" => "value" } + expect do catalog = compile_to_catalog(<<-MANIFEST, node) - $trusted = 'changed' - notify { 'test': message => $trusted == 'changed' } + $dummy = inline_template("<% @trusted['extra'] = 'added' %> lol") + notify { 'test': message => $trusted['extra'] == 'added' } MANIFEST - catalog.resource("Notify[test]")[:message].should == true - end + end.to raise_error(Puppet::Error, /can't modify frozen [hH]ash/) end end - end - describe 'using classic parser' do - before :each do - Puppet[:parser] = 'current' + context 'and have not opted in to trusted_node_data' do + before :each do + Puppet[:trusted_node_data] = false + end + + it 'should not make $trusted available' do + node = Puppet::Node.new("testing") + node.trusted_data = { "data" => "value" } + + catalog = compile_to_catalog(<<-MANIFEST, node) + notify { 'test': message => $trusted == undef } + MANIFEST + + catalog.resource("Notify[test]")[:message].should == true + end + + it 'should allow assignment to $trusted' do + node = Puppet::Node.new("testing") + + catalog = compile_to_catalog(<<-MANIFEST, node) + $trusted = 'changed' + notify { 'test': message => $trusted == 'changed' } + MANIFEST + + catalog.resource("Notify[test]")[:message].should == true + end end - it_behaves_like 'the compiler' do + end + + context 'when evaluating collection' do + it 'matches on container inherited tags' do + Puppet[:code] = <<-MANIFEST + class xport_test { + tag 'foo_bar' + @notify { 'nbr1': + message => 'explicitly tagged', + tag => 'foo_bar' + } + + @notify { 'nbr2': + message => 'implicitly tagged' + } + + Notify <| tag == 'foo_bar' |> { + message => 'overridden' + } + } + include xport_test + MANIFEST + + catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) + + expect(catalog).to have_resource("Notify[nbr1]").with_parameter(:message, 'overridden') + expect(catalog).to have_resource("Notify[nbr2]").with_parameter(:message, 'overridden') end end end diff --git a/spec/integration/parser/conditionals_spec.rb b/spec/integration/parser/conditionals_spec.rb new file mode 100644 index 000000000..82d950a06 --- /dev/null +++ b/spec/integration/parser/conditionals_spec.rb @@ -0,0 +1,117 @@ +require 'spec_helper' +require 'puppet_spec/compiler' +require 'matchers/resource' + +describe "Evaluation of Conditionals" do + include PuppetSpec::Compiler + include Matchers::Resource + + shared_examples_for "a catalog built with conditionals" do + it "evaluates an if block correctly" do + catalog = compile_to_catalog(<<-CODE) + if( 1 == 1) { + notify { 'if': } + } elsif(2 == 2) { + notify { 'elsif': } + } else { + notify { 'else': } + } + CODE + expect(catalog).to have_resource("Notify[if]") + end + + it "evaluates elsif block" do + catalog = compile_to_catalog(<<-CODE) + if( 1 == 3) { + notify { 'if': } + } elsif(2 == 2) { + notify { 'elsif': } + } else { + notify { 'else': } + } + CODE + expect(catalog).to have_resource("Notify[elsif]") + end + + it "reaches the else clause if no expressions match" do + catalog = compile_to_catalog(<<-CODE) + if( 1 == 2) { + notify { 'if': } + } elsif(2 == 3) { + notify { 'elsif': } + } else { + notify { 'else': } + } + CODE + expect(catalog).to have_resource("Notify[else]") + end + + it "evalutes false to false" do + catalog = compile_to_catalog(<<-CODE) + if false { + } else { + notify { 'false': } + } + CODE + expect(catalog).to have_resource("Notify[false]") + end + + it "evaluates the string 'false' as true" do + catalog = compile_to_catalog(<<-CODE) + if 'false' { + notify { 'true': } + } else { + notify { 'false': } + } + CODE + expect(catalog).to have_resource("Notify[true]") + end + + it "evaluates undefined variables as false" do + catalog = compile_to_catalog(<<-CODE) + if $undef_var { + } else { + notify { 'undef': } + } + CODE + expect(catalog).to have_resource("Notify[undef]") + end + end + + context "current parser" do + before(:each) do + Puppet[:parser] = 'current' + end + + it_behaves_like "a catalog built with conditionals" + + it "evaluates empty string as false" do + catalog = compile_to_catalog(<<-CODE) + if '' { + notify { 'true': } + } else { + notify { 'empty': } + } + CODE + expect(catalog).to have_resource("Notify[empty]") + end + end + + context "future parser" do + before(:each) do + Puppet[:parser] = 'future' + end + it_behaves_like "a catalog built with conditionals" + + it "evaluates empty string as true" do + catalog = compile_to_catalog(<<-CODE) + if '' { + notify { 'true': } + } else { + notify { 'empty': } + } + CODE + expect(catalog).to have_resource("Notify[true]") + end + end +end diff --git a/spec/integration/parser/future_compiler_spec.rb b/spec/integration/parser/future_compiler_spec.rb index 9d4d98776..d0fcfcdec 100644 --- a/spec/integration/parser/future_compiler_spec.rb +++ b/spec/integration/parser/future_compiler_spec.rb @@ -61,13 +61,73 @@ describe "Puppet::Parser::Compiler" do expect(catalog).to have_resource("Notify[check_me]").with_parameter(:message, "evoe") end + it 'Applies defaults from dynamic scopes (3x and future with reverted PUP-867)' do + catalog = compile_to_catalog(<<-CODE) + class a { + Notify { message => "defaulted" } + include b + notify { bye: } + } + class b { notify { hi: } } + + include a + CODE + expect(catalog).to have_resource("Notify[hi]").with_parameter(:message, "defaulted") + expect(catalog).to have_resource("Notify[bye]").with_parameter(:message, "defaulted") + end + + it 'gets default from inherited class (PUP-867)' do + catalog = compile_to_catalog(<<-CODE) + class a { + Notify { message => "defaulted" } + include c + notify { bye: } + } + class b { Notify { message => "inherited" } } + class c inherits b { notify { hi: } } + + include a + CODE + + expect(catalog).to have_resource("Notify[hi]").with_parameter(:message, "inherited") + expect(catalog).to have_resource("Notify[bye]").with_parameter(:message, "defaulted") + end + + it 'looks up default parameter values from inherited class (PUP-2532)' do + catalog = compile_to_catalog(<<-CODE) + class a { + Notify { message => "defaulted" } + include c + notify { bye: } + } + class b { Notify { message => "inherited" } } + class c inherits b { notify { hi: } } + + include a + notify {hi_test: message => Notify[hi][message] } + notify {bye_test: message => Notify[bye][message] } + CODE + + expect(catalog).to have_resource("Notify[hi_test]").with_parameter(:message, "inherited") + expect(catalog).to have_resource("Notify[bye_test]").with_parameter(:message, "defaulted") + end + + it 'does not allow override of class parameters using a resource override expression' do + expect do + compile_to_catalog(<<-CODE) + Class[a] { x => 2} + CODE + end.to raise_error(/Resource Override can only.*got: Class\[a\].*/) + end + describe "when resolving class references" do - it "should favor local scope, even if there's an included class in topscope" do + it "should not favor local scope (with class included in topscope)" do catalog = compile_to_catalog(<<-PP) class experiment { class baz { } notify {"x" : require => Class[Baz] } + notify {"y" : require => Class[Experiment::Baz] } } class baz { } @@ -76,15 +136,17 @@ describe "Puppet::Parser::Compiler" do include experiment::baz PP - expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Experiment::Baz]")) + expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Baz]")) + expect(catalog).to have_resource("Notify[y]").with_parameter(:require, be_resource("Class[Experiment::Baz]")) end - it "should favor local scope, even if there's an unincluded class in topscope" do + it "should not favor local scope, (with class not included in topscope)" do catalog = compile_to_catalog(<<-PP) class experiment { class baz { } notify {"x" : require => Class[Baz] } + notify {"y" : require => Class[Experiment::Baz] } } class baz { } @@ -92,7 +154,8 @@ describe "Puppet::Parser::Compiler" do include experiment::baz PP - expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Experiment::Baz]")) + expect(catalog).to have_resource("Notify[x]").with_parameter(:require, be_resource("Class[Baz]")) + expect(catalog).to have_resource("Notify[y]").with_parameter(:require, be_resource("Class[Experiment::Baz]")) end end @@ -167,10 +230,10 @@ describe "Puppet::Parser::Compiler" do def assert_creates_relationships(relationship_code, expectations) base_manifest = <<-MANIFEST file { [a,b,c]: - mode => 0644, + mode => '0644', } file { [d,e]: - mode => 0755, + mode => '0755', } MANIFEST catalog = compile_to_catalog(base_manifest + relationship_code) @@ -301,12 +364,13 @@ describe "Puppet::Parser::Compiler" do end it 'a missing variable as default value becomes undef' do + # strict variables not on catalog = compile_to_catalog(<<-MANIFEST) - class a ($b=$x) { notify {$b: message=>'meh'} } + class a ($b=$x) { notify {test: message=>"yes ${undef == $b}" } } include a MANIFEST - expect(catalog).to have_resource("Notify[undef]").with_parameter(:message, "meh") + expect(catalog).to have_resource("Notify[test]").with_parameter(:message, "yes true") end end @@ -366,5 +430,321 @@ describe "Puppet::Parser::Compiler" do end end end + + context 'when using typed parameters in definition' do + it 'accepts type compliant arguments' do + catalog = compile_to_catalog(<<-MANIFEST) + define foo(String $x) { } + foo { 'test': x =>'say friend' } + MANIFEST + expect(catalog).to have_resource("Foo[test]").with_parameter(:x, 'say friend') + end + + it 'accepts anything when parameters are untyped' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + define foo($a, $b, $c) { } + foo { 'test': a => String, b=>10, c=>undef } + MANIFEST + end.to_not raise_error() + end + + it 'denies non type compliant arguments' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + define foo(Integer $x) { } + foo { 'test': x =>'say friend' } + MANIFEST + end.to raise_error(/type Integer, got String/) + end + + it 'denies non type compliant default argument' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + define foo(Integer $x = 'pow') { } + foo { 'test': } + MANIFEST + end.to raise_error(/type Integer, got String/) + end + + it 'accepts a Resource as a Type' do + catalog = compile_to_catalog(<<-MANIFEST) + define foo(Type[Bar] $x) { + notify { 'test': message => $x[text] } + } + define bar($text) { } + bar { 'joke': text => 'knock knock' } + foo { 'test': x => Bar[joke] } + MANIFEST + expect(catalog).to have_resource("Notify[test]").with_parameter(:message, 'knock knock') + end + end + + context 'when using typed parameters in class' do + it 'accepts type compliant arguments' do + catalog = compile_to_catalog(<<-MANIFEST) + class foo(String $x) { } + class { 'foo': x =>'say friend' } + MANIFEST + expect(catalog).to have_resource("Class[Foo]").with_parameter(:x, 'say friend') + end + + it 'accepts anything when parameters are untyped' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + class foo($a, $b, $c) { } + class { 'foo': a => String, b=>10, c=>undef } + MANIFEST + end.to_not raise_error() + end + + it 'denies non type compliant arguments' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + class foo(Integer $x) { } + class { 'foo': x =>'say friend' } + MANIFEST + end.to raise_error(/type Integer, got String/) + end + + it 'denies non type compliant default argument' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + class foo(Integer $x = 'pow') { } + class { 'foo': } + MANIFEST + end.to raise_error(/type Integer, got String/) + end + + it 'accepts a Resource as a Type' do + catalog = compile_to_catalog(<<-MANIFEST) + class foo(Type[Bar] $x) { + notify { 'test': message => $x[text] } + } + define bar($text) { } + bar { 'joke': text => 'knock knock' } + class { 'foo': x => Bar[joke] } + MANIFEST + expect(catalog).to have_resource("Notify[test]").with_parameter(:message, 'knock knock') + end + end + + context 'when using typed parameters in lambdas' do + it 'accepts type compliant arguments' do + catalog = compile_to_catalog(<<-MANIFEST) + with('value') |String $x| { notify { "$x": } } + MANIFEST + expect(catalog).to have_resource("Notify[value]") + end + + it 'handles an array as a single argument' do + catalog = compile_to_catalog(<<-MANIFEST) + with(['value', 'second']) |$x| { notify { "${x[0]} ${x[1]}": } } + MANIFEST + expect(catalog).to have_resource("Notify[value second]") + end + + it 'denies when missing required arguments' do + expect do + compile_to_catalog(<<-MANIFEST) + with(1) |$x, $y| { } + MANIFEST + end.to raise_error(/Parameter \$y is required but no value was given/m) + end + + it 'accepts anything when parameters are untyped' do + catalog = compile_to_catalog(<<-MANIFEST) + ['value', 1, true, undef].each |$x| { notify { "value: $x": } } + MANIFEST + + expect(catalog).to have_resource("Notify[value: value]") + expect(catalog).to have_resource("Notify[value: 1]") + expect(catalog).to have_resource("Notify[value: true]") + expect(catalog).to have_resource("Notify[value: ]") + end + + it 'accepts type-compliant, slurped arguments' do + catalog = compile_to_catalog(<<-MANIFEST) + with(1, 2) |Integer *$x| { notify { "${$x[0] + $x[1]}": } } + MANIFEST + expect(catalog).to have_resource("Notify[3]") + end + + it 'denies non-type-compliant arguments' do + expect do + compile_to_catalog(<<-MANIFEST) + with(1) |String $x| { } + MANIFEST + end.to raise_error(/expected.*String.*actual.*Integer/m) + end + + it 'denies non-type-compliant, slurped arguments' do + expect do + compile_to_catalog(<<-MANIFEST) + with(1, "hello") |Integer *$x| { } + MANIFEST + end.to raise_error(/called with mis-matched arguments.*expected.*Integer.*actual.*Integer, String/m) + end + + it 'denies non-type-compliant default argument' do + expect do + compile_to_catalog(<<-MANIFEST) + with(1) |$x, String $defaulted = 1| { notify { "${$x + $defaulted}": }} + MANIFEST + end.to raise_error(/expected.*Any.*String.*actual.*Integer.*Integer/m) + end + + it 'raises an error when a default argument value is an incorrect type and there are no arguments passed' do + expect do + compile_to_catalog(<<-MANIFEST) + with() |String $defaulted = 1| {} + MANIFEST + end.to raise_error(/expected.*String.*actual.*Integer/m) + end + + it 'raises an error when the default argument for a slurped parameter is an incorrect type' do + expect do + compile_to_catalog(<<-MANIFEST) + with() |String *$defaulted = 1| {} + MANIFEST + end.to raise_error(/expected.*String.*actual.*Integer/m) + end + + it 'allows using an array as the default slurped value' do + catalog = compile_to_catalog(<<-MANIFEST) + with() |String *$defaulted = [hi]| { notify { $defaulted[0]: } } + MANIFEST + + expect(catalog).to have_resource('Notify[hi]') + end + + it 'allows using a value of the type as the default slurped value' do + catalog = compile_to_catalog(<<-MANIFEST) + with() |String *$defaulted = hi| { notify { $defaulted[0]: } } + MANIFEST + + expect(catalog).to have_resource('Notify[hi]') + end + + it 'allows specifying the type of a slurped parameter as an array' do + catalog = compile_to_catalog(<<-MANIFEST) + with() |Array[String] *$defaulted = hi| { notify { $defaulted[0]: } } + MANIFEST + + expect(catalog).to have_resource('Notify[hi]') + end + + it 'raises an error when the number of default values does not match the parameter\'s size specification' do + expect do + compile_to_catalog(<<-MANIFEST) + with() |Array[String, 2] *$defaulted = hi| { } + MANIFEST + end.to raise_error(/expected.*arg count \{2,\}.*actual.*arg count \{1\}/m) + end + + it 'raises an error when the number of passed values does not match the parameter\'s size specification' do + expect do + compile_to_catalog(<<-MANIFEST) + with(hi) |Array[String, 2] *$passed| { } + MANIFEST + end.to raise_error(/expected.*arg count \{2,\}.*actual.*arg count \{1\}/m) + end + + it 'matches when the number of arguments passed for a slurp parameter match the size specification' do + catalog = compile_to_catalog(<<-MANIFEST) + with(hi, bye) |Array[String, 2] *$passed| { + $passed.each |$n| { notify { $n: } } + } + MANIFEST + + expect(catalog).to have_resource('Notify[hi]') + expect(catalog).to have_resource('Notify[bye]') + end + + it 'raises an error when the number of allowed slurp parameters exceeds the size constraint' do + expect do + compile_to_catalog(<<-MANIFEST) + with(hi, bye) |Array[String, 1, 1] *$passed| { } + MANIFEST + end.to raise_error(/expected.*arg count \{1\}.*actual.*arg count \{2\}/m) + end + + it 'allows passing slurped arrays by specifying an array of arrays' do + catalog = compile_to_catalog(<<-MANIFEST) + with([hi], [bye]) |Array[Array[String, 1, 1]] *$passed| { + notify { $passed[0][0]: } + notify { $passed[1][0]: } + } + MANIFEST + + expect(catalog).to have_resource('Notify[hi]') + expect(catalog).to have_resource('Notify[bye]') + end + + it 'raises an error when a required argument follows an optional one' do + expect do + compile_to_catalog(<<-MANIFEST) + with() |$y = first, $x, Array[String, 1] *$passed = bye| {} + MANIFEST + end.to raise_error(/Parameter \$x is required/) + end + + it 'raises an error when the minimum size of a slurped argument makes it required and it follows an optional argument' do + expect do + compile_to_catalog(<<-MANIFEST) + with() |$x = first, Array[String, 1] *$passed| {} + MANIFEST + end.to raise_error(/Parameter \$passed is required/) + end + + it 'allows slurped arguments with a minimum size of 0 after an optional argument' do + catalog = compile_to_catalog(<<-MANIFEST) + with() |$x = first, Array[String, 0] *$passed| { + notify { $x: } + } + MANIFEST + + expect(catalog).to have_resource('Notify[first]') + end + + it 'accepts a Resource as a Type' do + catalog = compile_to_catalog(<<-MANIFEST) + define bar($text) { } + bar { 'joke': text => 'knock knock' } + + with(Bar[joke]) |Type[Bar] $joke| { notify { "${joke[text]}": } } + MANIFEST + expect(catalog).to have_resource("Notify[knock knock]") + end + end end + + context 'when evaluating collection' do + it 'matches on container inherited tags' do + Puppet[:code] = <<-MANIFEST + class xport_test { + tag('foo_bar') + @notify { 'nbr1': + message => 'explicitly tagged', + tag => 'foo_bar' + } + + @notify { 'nbr2': + message => 'implicitly tagged' + } + + Notify <| tag == 'foo_bar' |> { + message => 'overridden' + } + } + include xport_test + MANIFEST + + catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) + + expect(catalog).to have_resource("Notify[nbr1]").with_parameter(:message, 'overridden') + expect(catalog).to have_resource("Notify[nbr2]").with_parameter(:message, 'overridden') + end + end + end diff --git a/spec/integration/parser/node_spec.rb b/spec/integration/parser/node_spec.rb new file mode 100644 index 000000000..7ce58f152 --- /dev/null +++ b/spec/integration/parser/node_spec.rb @@ -0,0 +1,185 @@ +require 'spec_helper' +require 'puppet_spec/compiler' +require 'matchers/resource' + +describe 'node statements' do + include PuppetSpec::Compiler + include Matchers::Resource + + shared_examples_for 'nodes' do + it 'selects a node where the name is just a number' do + # Future parser doesn't allow a number in this position + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("5")) + node 5 { notify { 'matched': } } + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + end + + it 'selects the node with a matching name' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) + node noden {} + node nodename { notify { matched: } } + node name {} + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + end + + it 'prefers a node with a literal name over one with a regex' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) + node /noden.me/ { notify { ignored: } } + node nodename { notify { matched: } } + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + end + + it 'selects a node where one of the names matches' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) + node different, nodename, other { notify { matched: } } + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + end + + it 'arbitrarily selects one of the matching nodes' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) + node /not/ { notify { 'is not matched': } } + node /name.*/ { notify { 'could be matched': } } + node /na.e/ { notify { 'could also be matched': } } + MANIFEST + + expect([catalog.resource('Notify[could be matched]'), catalog.resource('Notify[could also be matched]')].compact).to_not be_empty + end + + it 'selects a node where one of the names matches with a mixture of literals and regex' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) + node different, /name/, other { notify { matched: } } + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + end + + it 'errors when two nodes with regexes collide after some regex syntax is removed' do + expect do + compile_to_catalog(<<-MANIFEST) + node /a.*(c)?/ { } + node 'a.c' { } + MANIFEST + end.to raise_error(Puppet::Error, /Node 'a.c' is already defined/) + end + + it 'provides captures from the regex in the node body' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) + node /(.*)/ { notify { "$1": } } + MANIFEST + + expect(catalog).to have_resource('Notify[nodename]') + end + + it 'selects the node with the matching regex' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) + node /node.*/ { notify { matched: } } + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + end + + it 'selects a node that is a literal string' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("node.name")) + node 'node.name' { notify { matched: } } + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + end + + it 'selects a node that is a prefix of the agent name' do + Puppet[:strict_hostname_checking] = false + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("node.name.com")) + node 'node.name' { notify { matched: } } + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + end + + it 'does not treat regex symbols as a regex inside a string literal' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodexname")) + node 'node.name' { notify { 'not matched': } } + node 'nodexname' { notify { 'matched': } } + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + end + + it 'errors when two nodes have the same name' do + expect do + compile_to_catalog(<<-MANIFEST) + node name { } + node 'name' { } + MANIFEST + end.to raise_error(Puppet::Error, /Node 'name' is already defined/) + end + end + + describe 'using classic parser' do + before :each do + Puppet[:parser] = 'current' + end + + it_behaves_like 'nodes' + + it 'includes the inherited nodes of the matching node' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) + node notmatched1 { notify { inherited: } } + node nodename inherits notmatched1 { notify { matched: } } + node notmatched2 { notify { ignored: } } + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + expect(catalog).to have_resource('Notify[inherited]') + end + + it 'raises deprecation warning for node inheritance for 3x parser' do + Puppet.expects(:warning).at_least_once + Puppet.expects(:warning).with(regexp_matches(/Deprecation notice\: Node inheritance is not supported in Puppet >= 4\.0\.0/)) + + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("1.2.3.4")) + node default {} + node '1.2.3.4' inherits default { } + MANIFEST + end + end + + describe 'using future parser' do + before :each do + Puppet[:parser] = 'future' + end + + it_behaves_like 'nodes' + + it 'is unable to parse a name that is an invalid number' do + expect do + compile_to_catalog('node 5name {} ') + end.to raise_error(Puppet::Error, /Illegal number/) + end + + it 'parses a node name that is dotted numbers' do + catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new("1.2.3.4")) + node 1.2.3.4 { notify { matched: } } + MANIFEST + + expect(catalog).to have_resource('Notify[matched]') + end + + it 'raises error for node inheritance' do + expect do + compile_to_catalog(<<-MANIFEST, Puppet::Node.new("nodename")) + node default {} + node nodename inherits default { } + MANIFEST + end.to raise_error(/Node inheritance is not supported in Puppet >= 4\.0\.0/) + end + + end +end diff --git a/spec/integration/parser/resource_expressions_spec.rb b/spec/integration/parser/resource_expressions_spec.rb new file mode 100644 index 000000000..c753f5a91 --- /dev/null +++ b/spec/integration/parser/resource_expressions_spec.rb @@ -0,0 +1,286 @@ +require 'spec_helper' +require 'puppet_spec/language' + +describe "Puppet resource expressions" do + extend PuppetSpec::Language + + describe "future parser" do + before :each do + Puppet[:parser] = 'future' + end + + produces( + "$a = notify + $b = example + $c = { message => hello } + @@Resource[$a] { + $b: + * => $c + } + realize(Resource[$a, $b]) + " => "Notify[example][message] == 'hello'") + + + context "resource titles" do + produces( + "notify { thing: }" => "defined(Notify[thing])", + "$x = thing notify { $x: }" => "defined(Notify[thing])", + + "notify { [thing]: }" => "defined(Notify[thing])", + "$x = [thing] notify { $x: }" => "defined(Notify[thing])", + + "notify { [[nested, array]]: }" => "defined(Notify[nested]) and defined(Notify[array])", + "$x = [[nested, array]] notify { $x: }" => "defined(Notify[nested]) and defined(Notify[array])", + + "notify { []: }" => [], # this asserts nothing added + "$x = [] notify { $x: }" => [], # this asserts nothing added + + "notify { default: }" => "!defined(Notify['default'])", # nothing created because this is just a local default + "$x = default notify { $x: }" => "!defined(Notify['default'])") + + fails( + "notify { '': }" => /Empty string title/, + "$x = '' notify { $x: }" => /Empty string title/, + + "notify { 1: }" => /Illegal title type.*Expected String, got Integer/, + "$x = 1 notify { $x: }" => /Illegal title type.*Expected String, got Integer/, + + "notify { [1]: }" => /Illegal title type.*Expected String, got Integer/, + "$x = [1] notify { $x: }" => /Illegal title type.*Expected String, got Integer/, + + "notify { 3.0: }" => /Illegal title type.*Expected String, got Float/, + "$x = 3.0 notify { $x: }" => /Illegal title type.*Expected String, got Float/, + + "notify { [3.0]: }" => /Illegal title type.*Expected String, got Float/, + "$x = [3.0] notify { $x: }" => /Illegal title type.*Expected String, got Float/, + + "notify { true: }" => /Illegal title type.*Expected String, got Boolean/, + "$x = true notify { $x: }" => /Illegal title type.*Expected String, got Boolean/, + + "notify { [true]: }" => /Illegal title type.*Expected String, got Boolean/, + "$x = [true] notify { $x: }" => /Illegal title type.*Expected String, got Boolean/, + + "notify { [false]: }" => /Illegal title type.*Expected String, got Boolean/, + "$x = [false] notify { $x: }" => /Illegal title type.*Expected String, got Boolean/, + + "notify { undef: }" => /Missing title.*undef/, + "$x = undef notify { $x: }" => /Missing title.*undef/, + + "notify { [undef]: }" => /Missing title.*undef/, + "$x = [undef] notify { $x: }" => /Missing title.*undef/, + + "notify { {nested => hash}: }" => /Illegal title type.*Expected String, got Hash/, + "$x = {nested => hash} notify { $x: }" => /Illegal title type.*Expected String, got Hash/, + + "notify { [{nested => hash}]: }" => /Illegal title type.*Expected String, got Hash/, + "$x = [{nested => hash}] notify { $x: }" => /Illegal title type.*Expected String, got Hash/, + + "notify { /regexp/: }" => /Illegal title type.*Expected String, got Regexp/, + "$x = /regexp/ notify { $x: }" => /Illegal title type.*Expected String, got Regexp/, + + "notify { [/regexp/]: }" => /Illegal title type.*Expected String, got Regexp/, + "$x = [/regexp/] notify { $x: }" => /Illegal title type.*Expected String, got Regexp/, + + "notify { [dupe, dupe]: }" => /The title 'dupe' has already been used/, + "notify { dupe:; dupe: }" => /The title 'dupe' has already been used/, + "notify { [dupe]:; dupe: }" => /The title 'dupe' has already been used/, + "notify { [default, default]:}" => /The title 'default' has already been used/, + "notify { default:; default:}" => /The title 'default' has already been used/, + "notify { [default]:; default:}" => /The title 'default' has already been used/) + end + + context "type names" do + produces( "notify { testing: }" => "defined(Notify[testing])") + produces( "$a = notify; Resource[$a] { testing: }" => "defined(Notify[testing])") + produces( "Resource['notify'] { testing: }" => "defined(Notify[testing])") + produces( "Resource[sprintf('%s', 'notify')] { testing: }" => "defined(Notify[testing])") + produces( "$a = ify; Resource[\"not$a\"] { testing: }" => "defined(Notify[testing])") + + produces( "Notify { testing: }" => "defined(Notify[testing])") + produces( "Resource[Notify] { testing: }" => "defined(Notify[testing])") + produces( "Resource['Notify'] { testing: }" => "defined(Notify[testing])") + + produces( "class a { notify { testing: } } class { a: }" => "defined(Notify[testing])") + produces( "class a { notify { testing: } } Class { a: }" => "defined(Notify[testing])") + produces( "class a { notify { testing: } } Resource['class'] { a: }" => "defined(Notify[testing])") + + produces( "define a::b { notify { testing: } } a::b { title: }" => "defined(Notify[testing])") + produces( "define a::b { notify { testing: } } A::B { title: }" => "defined(Notify[testing])") + produces( "define a::b { notify { testing: } } Resource['a::b'] { title: }" => "defined(Notify[testing])") + + fails( "'class' { a: }" => /Illegal Resource Type expression.*got String/) + fails( "'' { testing: }" => /Illegal Resource Type expression.*got String/) + fails( "1 { testing: }" => /Illegal Resource Type expression.*got Integer/) + fails( "3.0 { testing: }" => /Illegal Resource Type expression.*got Float/) + fails( "true { testing: }" => /Illegal Resource Type expression.*got Boolean/) + fails( "'not correct' { testing: }" => /Illegal Resource Type expression.*got String/) + + fails( "Notify[hi] { testing: }" => /Illegal Resource Type expression.*got Notify\['hi'\]/) + fails( "[Notify, File] { testing: }" => /Illegal Resource Type expression.*got Array\[Type\[Resource\]\]/) + + fails( "define a::b { notify { testing: } } 'a::b' { title: }" => /Illegal Resource Type expression.*got String/) + + fails( "Does::Not::Exist { title: }" => /Invalid resource type does::not::exist/) + end + + context "local defaults" do + produces( + "notify { example:; default: message => defaulted }" => "Notify[example][message] == 'defaulted'", + "notify { example: message => specific; default: message => defaulted }" => "Notify[example][message] == 'specific'", + "notify { example: message => undef; default: message => defaulted }" => "Notify[example][message] == undef", + "notify { [example, other]: ; default: message => defaulted }" => "Notify[example][message] == 'defaulted' and Notify[other][message] == 'defaulted'", + "notify { [example, default]: message => set; other: }" => "Notify[example][message] == 'set' and Notify[other][message] == 'set'") + end + + context "order of evaluation" do + fails("notify { hi: message => value; bye: message => Notify[hi][message] }" => /Resource not found: Notify\['hi'\]/) + + produces("notify { hi: message => (notify { param: message => set }); bye: message => Notify[param][message] }" => "defined(Notify[hi]) and Notify[bye][message] == 'set'") + fails("notify { bye: message => Notify[param][message]; hi: message => (notify { param: message => set }) }" => /Resource not found: Notify\['param'\]/) + end + + context "parameters" do + produces( + "notify { title: message => set }" => "Notify[title][message] == 'set'", + "$x = set notify { title: message => $x }" => "Notify[title][message] == 'set'", + + "notify { title: *=> { message => set } }" => "Notify[title][message] == 'set'", + + "$x = { message => set } notify { title: * => $x }" => "Notify[title][message] == 'set'", + + # picks up defaults + "$x = { owner => the_x } + $y = { mode => '0666' } + $t = '/tmp/x' + file { + default: + * => $x; + $t: + path => '/somewhere', + * => $y }" => "File[$t][mode] == '0666' and File[$t][owner] == 'the_x' and File[$t][path] == '/somewhere'", + + # explicit wins over default - no error + "$x = { owner => the_x, mode => '0777' } + $y = { mode => '0666' } + $t = '/tmp/x' + file { + default: + * => $x; + $t: + path => '/somewhere', + * => $y }" => "File[$t][mode] == '0666' and File[$t][owner] == 'the_x' and File[$t][path] == '/somewhere'") + + fails("notify { title: unknown => value }" => /Invalid parameter unknown/) + + # this really needs to be a better error message. + fails("notify { title: * => { hash => value }, message => oops }" => /Invalid parameter hash/) + + # should this be a better error message? + fails("notify { title: message => oops, * => { hash => value } }" => /Invalid parameter hash/) + + fails("notify { title: * => { unknown => value } }" => /Invalid parameter unknown/) + fails(" + $x = { mode => '0666' } + $y = { owner => the_y } + $t = '/tmp/x' + file { $t: + * => $x, + * => $y }" => /Unfolding of attributes from Hash can only be used once per resource body/) + end + + context "virtual" do + produces( + "@notify { example: }" => "!defined(Notify[example])", + + "@notify { example: } + realize(Notify[example])" => "defined(Notify[example])", + + "@notify { virtual: message => set } + notify { real: + message => Notify[virtual][message] }" => "Notify[real][message] == 'set'") + end + + context "exported" do + produces( + "@@notify { example: }" => "!defined(Notify[example])", + "@@notify { example: } realize(Notify[example])" => "defined(Notify[example])", + "@@notify { exported: message => set } notify { real: message => Notify[exported][message] }" => "Notify[real][message] == 'set'") + end + end + + describe "current parser" do + before :each do + Puppet[:parser] = 'current' + end + + produces( + "notify { thing: }" => ["Notify[thing]"], + "$x = thing notify { $x: }" => ["Notify[thing]"], + + "notify { [thing]: }" => ["Notify[thing]"], + "$x = [thing] notify { $x: }" => ["Notify[thing]"], + + "notify { [[nested, array]]: }" => ["Notify[nested]", "Notify[array]"], + "$x = [[nested, array]] notify { $x: }" => ["Notify[nested]", "Notify[array]"], + + # deprecate? + "notify { 1: }" => ["Notify[1]"], + "$x = 1 notify { $x: }" => ["Notify[1]"], + + # deprecate? + "notify { [1]: }" => ["Notify[1]"], + "$x = [1] notify { $x: }" => ["Notify[1]"], + + # deprecate? + "notify { 3.0: }" => ["Notify[3.0]"], + "$x = 3.0 notify { $x: }" => ["Notify[3.0]"], + + # deprecate? + "notify { [3.0]: }" => ["Notify[3.0]"], + "$x = [3.0] notify { $x: }" => ["Notify[3.0]"]) + + # :( + fails( "notify { true: }" => /Syntax error/) + produces("$x = true notify { $x: }" => ["Notify[true]"]) + + # this makes no sense given the [false] case + produces( + "notify { [true]: }" => ["Notify[true]"], + "$x = [true] notify { $x: }" => ["Notify[true]"]) + + # *sigh* + fails( + "notify { false: }" => /Syntax error/, + "$x = false notify { $x: }" => /No title provided and :notify is not a valid resource reference/, + + "notify { [false]: }" => /No title provided and :notify is not a valid resource reference/, + "$x = [false] notify { $x: }" => /No title provided and :notify is not a valid resource reference/) + + # works for variable value, not for literal. deprecate? + fails("notify { undef: }" => /Syntax error/) + produces( + "$x = undef notify { $x: }" => ["Notify[undef]"], + + # deprecate? + "notify { [undef]: }" => ["Notify[undef]"], + "$x = [undef] notify { $x: }" => ["Notify[undef]"]) + + fails("notify { {nested => hash}: }" => /Syntax error/) + #produces("$x = {nested => hash} notify { $x: }" => ["Notify[{nested => hash}]"]) #it is created, but isn't possible to reference the resource. deprecate? + #produces("notify { [{nested => hash}]: }" => ["Notify[{nested => hash}]"]) #it is created, but isn't possible to reference the resource. deprecate? + #produces("$x = [{nested => hash}] notify { $x: }" => ["Notify[{nested => hash}]"]) #it is created, but isn't possible to reference the resource. deprecate? + + fails( + "notify { /regexp/: }" => /Syntax error/, + "$x = /regexp/ notify { $x: }" => /Syntax error/, + + "notify { [/regexp/]: }" => /Syntax error/, + "$x = [/regexp/] notify { $x: }" => /Syntax error/, + + "notify { default: }" => /Syntax error/, + "$x = default notify { $x: }" => /Syntax error/, + + "notify { [default]: }" => /Syntax error/, + "$x = [default] notify { $x: }" => /Syntax error/) + end +end diff --git a/spec/integration/parser/ruby_manifest_spec.rb b/spec/integration/parser/ruby_manifest_spec.rb index d0bd5f0e7..29e9c6379 100755 --- a/spec/integration/parser/ruby_manifest_spec.rb +++ b/spec/integration/parser/ruby_manifest_spec.rb @@ -11,10 +11,6 @@ describe "Pure ruby manifests" do @test_dir = tmpdir('ruby_manifest_test') end - after do - Puppet.settings.clear - end - def write_file(name, contents) path = File.join(@test_dir, name) File.open(path, "w") { |f| f.write(contents) } diff --git a/spec/integration/parser/scope_spec.rb b/spec/integration/parser/scope_spec.rb index 1fd84f421..c10caa7f0 100644 --- a/spec/integration/parser/scope_spec.rb +++ b/spec/integration/parser/scope_spec.rb @@ -39,23 +39,27 @@ describe "Two step scoping for variables" do Puppet[:parser] = 'future' end - describe "using plussignment to change in a new scope" do - it "does not change a string in the parent scope" do - # Expects to be able to concatenate string using += + describe "using unsupported operators" do + it "issues an error for +=" do expect do - catalog = compile_to_catalog(<<-MANIFEST, Puppet::Node.new('the node')) - $var = "top_msg" - class override { - $var += "override" - include foo - } - class foo { - notify { 'something': message => $var, } + catalog = compile_to_catalog(<<-MANIFEST) + $var = ["top_msg"] + node default { + $var += ["override"] } + MANIFEST + end.to raise_error(/The operator '\+=' is no longer supported/) + end - include override + it "issues an error for -=" do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + $var = ["top_msg"] + node default { + $var -= ["top_msg"] + } MANIFEST - end.to raise_error(/The value 'top_msg' cannot be converted to Numeric/) + end.to raise_error(/The operator '-=' is no longer supported/) end end @@ -184,54 +188,6 @@ describe "Two step scoping for variables" do end describe "when using shadowing and inheritance" do - it "finds value define in the inherited node" do - expect_the_message_to_be('parent_msg') do <<-MANIFEST - $var = "top_msg" - node parent { - $var = "parent_msg" - } - node default inherits parent { - include foo - } - class foo { - notify { 'something': message => $var, } - } - MANIFEST - end - end - - it "finds top scope when the class is included before the node defines the var" do - expect_the_message_to_be('top_msg') do <<-MANIFEST - $var = "top_msg" - node parent { - include foo - } - node default inherits parent { - $var = "default_msg" - } - class foo { - notify { 'something': message => $var, } - } - MANIFEST - end - end - - it "finds top scope when the class is included before the node defines the var" do - expect_the_message_to_be('top_msg') do <<-MANIFEST - $var = "top_msg" - node parent { - include foo - } - node default inherits parent { - $var = "default_msg" - } - class foo { - notify { 'something': message => $var, } - } - MANIFEST - end - end - it "finds values in its local scope" do expect_the_message_to_be('local_msg') do <<-MANIFEST node default { @@ -363,7 +319,7 @@ describe "Two step scoping for variables" do $var = "inner baz" } - class bar inherits baz { + class bar inherits foo::baz { notify { 'something': message => $var, } } } @@ -651,80 +607,6 @@ describe "Two step scoping for variables" do end end - describe "using plussignment to change in a new scope" do - - it "does not change an array in the parent scope" do - expect_the_message_to_be('top_msg') do <<-MANIFEST - $var = ["top_msg"] - class override { - $var += ["override"] - include foo - } - class foo { - notify { 'something': message => $var, } - } - - include override - MANIFEST - end - end - - it "concatenates two arrays" do - expect_the_message_to_be(['top_msg', 'override']) do <<-MANIFEST - $var = ["top_msg"] - class override { - $var += ["override"] - notify { 'something': message => $var, } - } - - include override - MANIFEST - end - end - - it "leaves an array of arrays unflattened" do - expect_the_message_to_be([['top_msg'], ['override']]) do <<-MANIFEST - $var = [["top_msg"]] - class override { - $var += [["override"]] - notify { 'something': message => $var, } - } - - include override - MANIFEST - end - end - - it "does not change a hash in the parent scope" do - expect_the_message_to_be({"key"=>"top_msg"}) do <<-MANIFEST - $var = { "key" => "top_msg" } - class override { - $var += { "other" => "override" } - include foo - } - class foo { - notify { 'something': message => $var, } - } - - include override - MANIFEST - end - end - - it "replaces a value of a key in the hash instead of merging the values" do - expect_the_message_to_be({"key"=>"override"}) do <<-MANIFEST - $var = { "key" => "top_msg" } - class override { - $var += { "key" => "override" } - notify { 'something': message => $var, } - } - - include override - MANIFEST - end - end - end - describe "when using an enc" do it "places enc parameters in top scope" do enc_node = Puppet::Node.new("the node", { :parameters => { "var" => 'from_enc' } }) @@ -757,20 +639,16 @@ describe "Two step scoping for variables" do end end - it "evaluates enc classes in the node scope when there is a matching node" do - enc_node = Puppet::Node.new("the_node", { :classes => ['foo'] }) - - expect_the_message_to_be('from matching node', enc_node) do <<-MANIFEST - node inherited { - $var = "from inherited" - } + it "overrides enc variables from a node scope var" do + enc_node = Puppet::Node.new("the_node", { :classes => ['foo'], :parameters => { 'enc_var' => 'Set from ENC.' } }) - node the_node inherits inherited { - $var = "from matching node" + expect_the_message_to_be('ENC overridden in node', enc_node) do <<-MANIFEST + node the_node { + $enc_var = "ENC overridden in node" } class foo { - notify { 'something': message => $var, } + notify { 'something': message => $enc_var, } } MANIFEST end @@ -782,7 +660,74 @@ describe "Two step scoping for variables" do before :each do Puppet[:parser] = 'current' end - it_behaves_like 'the scope' do + + it_behaves_like 'the scope' + + it "finds value define in the inherited node" do + expect_the_message_to_be('parent_msg') do <<-MANIFEST + $var = "top_msg" + node parent { + $var = "parent_msg" + } + node default inherits parent { + include foo + } + class foo { + notify { 'something': message => $var, } + } + MANIFEST + end + end + + it "finds top scope when the class is included before the node defines the var" do + expect_the_message_to_be('top_msg') do <<-MANIFEST + $var = "top_msg" + node parent { + include foo + } + node default inherits parent { + $var = "default_msg" + } + class foo { + notify { 'something': message => $var, } + } + MANIFEST + end + end + + it "finds top scope when the class is included before the node defines the var" do + expect_the_message_to_be('top_msg') do <<-MANIFEST + $var = "top_msg" + node parent { + include foo + } + node default inherits parent { + $var = "default_msg" + } + class foo { + notify { 'something': message => $var, } + } + MANIFEST + end + end + + it "evaluates enc classes in the node scope when there is a matching node" do + enc_node = Puppet::Node.new("the_node", { :classes => ['foo'] }) + + expect_the_message_to_be('from matching node', enc_node) do <<-MANIFEST + node inherited { + $var = "from inherited" + } + + node the_node inherits inherited { + $var = "from matching node" + } + + class foo { + notify { 'something': message => $var, } + } + MANIFEST + end end end @@ -790,9 +735,7 @@ describe "Two step scoping for variables" do before :each do Puppet[:parser] = 'future' end - it_behaves_like 'the scope' do - end - end + it_behaves_like 'the scope' + end end - diff --git a/spec/integration/provider/cron/crontab_spec.rb b/spec/integration/provider/cron/crontab_spec.rb index a88eb1c76..e75523cac 100644 --- a/spec/integration/provider/cron/crontab_spec.rb +++ b/spec/integration/provider/cron/crontab_spec.rb @@ -2,9 +2,11 @@ require 'spec_helper' require 'puppet/file_bucket/dipper' +require 'puppet_spec/compiler' describe Puppet::Type.type(:cron).provider(:crontab), '(integration)', :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files + include PuppetSpec::Compiler before :each do Puppet::Type.type(:cron).stubs(:defaultprovider).returns described_class @@ -33,20 +35,6 @@ describe Puppet::Type.type(:cron).provider(:crontab), '(integration)', :unless = tmpfile('cron_integration_specs') end - def run_in_catalog(*resources) - catalog = Puppet::Resource::Catalog.new - catalog.host_config = false - resources.each do |resource| - resource.expects(:err).never - catalog.add_resource(resource) - end - - # the resources are not properly contained and generated resources - # will end up with dangling edges without this stubbing: - catalog.stubs(:container_of).returns resources[0] - catalog.apply - end - def expect_output(fixture_name) File.read(crontab_user1).should == File.read(my_fixture(fixture_name)) end @@ -54,56 +42,53 @@ describe Puppet::Type.type(:cron).provider(:crontab), '(integration)', :unless = describe "when managing a cron entry" do it "should be able to purge unmanaged entries" do - resource = Puppet::Type.type(:cron).new( - :name => 'only managed entry', - :ensure => :present, - :command => '/bin/true', - :target => crontab_user1, - :user => crontab_user1 - ) - resources = Puppet::Type.type(:resources).new( - :name => 'cron', - :purge => 'true' - ) - run_in_catalog(resource, resources) + apply_with_error_check(<<-MANIFEST) + cron { + 'only managed entry': + ensure => 'present', + command => '/bin/true', + target => '#{crontab_user1}', + } + resources { 'cron': purge => 'true' } + MANIFEST expect_output('purged') end describe "with ensure absent" do it "should do nothing if entry already absent" do - resource = Puppet::Type.type(:cron).new( - :name => 'no_such_entry', - :ensure => :absent, - :target => crontab_user1, - :user => crontab_user1 - ) - run_in_catalog(resource) + apply_with_error_check(<<-MANIFEST) + cron { + 'no_such_entry': + ensure => 'absent', + target => '#{crontab_user1}', + } + MANIFEST expect_output('crontab_user1') end it "should remove the resource from crontab if present" do - resource = Puppet::Type.type(:cron).new( - :name => 'My daily failure', - :ensure => :absent, - :target => crontab_user1, - :user => crontab_user1 - ) - run_in_catalog(resource) + apply_with_error_check(<<-MANIFEST) + cron { + 'My daily failure': + ensure => 'absent', + target => '#{crontab_user1}', + } + MANIFEST expect_output('remove_named_resource') end it "should remove a matching cronentry if present" do - resource = Puppet::Type.type(:cron).new( - :name => 'no_such_named_resource_in_crontab', - :ensure => :absent, - :minute => [ '17-19', '22' ], - :hour => [ '0-23/2' ], - :weekday => 'Tue', - :command => '/bin/unnamed_regular_command', - :target => crontab_user1, - :user => crontab_user1 - ) - run_in_catalog(resource) + apply_with_error_check(<<-MANIFEST) + cron { + 'no_such_named_resource_in_crontab': + ensure => absent, + minute => [ '17-19', '22' ], + hour => [ '0-23/2' ], + weekday => 'Tue', + command => '/bin/unnamed_regular_command', + target => '#{crontab_user1}', + } + MANIFEST expect_output('remove_unnamed_resource') end end @@ -112,137 +97,141 @@ describe Puppet::Type.type(:cron).provider(:crontab), '(integration)', :unless = context "and no command specified" do it "should work if the resource is already present" do - resource = Puppet::Type.type(:cron).new( - :name => 'My daily failure', - :special => 'daily', - :target => crontab_user1, - :user => crontab_user1 - ) - run_in_catalog(resource) + apply_with_error_check(<<-MANIFEST) + cron { + 'My daily failure': + special => 'daily', + target => '#{crontab_user1}', + } + MANIFEST expect_output('crontab_user1') end it "should fail if the resource needs creating" do - resource = Puppet::Type.type(:cron).new( - :name => 'Entirely new resource', - :special => 'daily', - :target => crontab_user1, - :user => crontab_user1 - ) - resource.expects(:err).with(regexp_matches(/no command/)) - run_in_catalog(resource) + manifest = <<-MANIFEST + cron { + 'Entirely new resource': + special => 'daily', + target => '#{crontab_user1}', + } + MANIFEST + apply_compiled_manifest(manifest) do |res| + if res.ref == 'Cron[Entirely new resource]' + res.expects(:err).with(regexp_matches(/no command/)) + else + res.expects(:err).never + end + end end end it "should do nothing if entry already present" do - resource = Puppet::Type.type(:cron).new( - :name => 'My daily failure', - :special => 'daily', - :command => '/bin/false', - :target => crontab_user1, - :user => crontab_user1 - ) - run_in_catalog(resource) + apply_with_error_check(<<-MANIFEST) + cron { + 'My daily failure': + special => 'daily', + command => '/bin/false', + target => '#{crontab_user1}', + } + MANIFEST expect_output('crontab_user1') end it "should work correctly when managing 'target' but not 'user'" do - resource = Puppet::Type.type(:cron).new( - :name => 'My daily failure', - :special => 'daily', - :command => '/bin/false', - :target => crontab_user1 - ) - run_in_catalog(resource) + apply_with_error_check(<<-MANIFEST) + cron { + 'My daily failure': + special => 'daily', + command => '/bin/false', + target => '#{crontab_user1}', + } + MANIFEST expect_output('crontab_user1') end it "should do nothing if a matching entry already present" do - resource = Puppet::Type.type(:cron).new( - :name => 'no_such_named_resource_in_crontab', - :ensure => :present, - :minute => [ '17-19', '22' ], - :hour => [ '0-23/2' ], - :command => '/bin/unnamed_regular_command', - :target => crontab_user1, - :user => crontab_user1 - ) - run_in_catalog(resource) + apply_with_error_check(<<-MANIFEST) + cron { + 'no_such_named_resource_in_crontab': + ensure => present, + minute => [ '17-19', '22' ], + hour => [ '0-23/2' ], + command => '/bin/unnamed_regular_command', + target => '#{crontab_user1}', + } + MANIFEST expect_output('crontab_user1') end it "should add a new normal entry if currently absent" do - resource = Puppet::Type.type(:cron).new( - :name => 'new entry', - :ensure => :present, - :minute => '12', - :weekday => 'Tue', - :command => '/bin/new', - :environment => [ - 'MAILTO=""', - 'SHELL=/bin/bash' - ], - :target => crontab_user1, - :user => crontab_user1 - ) - run_in_catalog(resource) + apply_with_error_check(<<-MANIFEST) + cron { + 'new entry': + ensure => present, + minute => '12', + weekday => 'Tue', + command => '/bin/new', + environment => [ + 'MAILTO=""', + 'SHELL=/bin/bash' + ], + target => '#{crontab_user1}', + } + MANIFEST expect_output('create_normal_entry') end it "should add a new special entry if currently absent" do - resource = Puppet::Type.type(:cron).new( - :name => 'new special entry', - :ensure => :present, - :special => 'reboot', - :command => 'echo "Booted" 1>&2', - :environment => 'MAILTO=bob@company.com', - :target => crontab_user1, - :user => crontab_user1 - ) - run_in_catalog(resource) + apply_with_error_check(<<-MANIFEST) + cron { + 'new special entry': + ensure => present, + special => 'reboot', + command => 'echo "Booted" 1>&2', + environment => 'MAILTO=bob@company.com', + target => '#{crontab_user1}', + } + MANIFEST expect_output('create_special_entry') end it "should change existing entry if out of sync" do - resource = Puppet::Type.type(:cron).new( - :name => 'Monthly job', - :ensure => :present, - :special => 'monthly', -# :minute => ['22'], - :command => '/usr/bin/monthly', - :environment => [], - :target => crontab_user1, - :user => crontab_user1 - ) - run_in_catalog(resource) + apply_with_error_check(<<-MANIFEST) + cron { + 'Monthly job': + ensure => present, + special => 'monthly', + #minute => ['22'], + command => '/usr/bin/monthly', + environment => [], + target => '#{crontab_user1}', + } + MANIFEST expect_output('modify_entry') end it "should change a special schedule to numeric if requested" do - resource = Puppet::Type.type(:cron).new( - :name => 'My daily failure', - :special => 'absent', - :command => '/bin/false', - :target => crontab_user1, - :user => crontab_user1 - ) - run_in_catalog(resource) + apply_with_error_check(<<-MANIFEST) + cron { + 'My daily failure': + special => 'absent', + command => '/bin/false', + target => '#{crontab_user1}', + } + MANIFEST expect_output('unspecialized') end it "should not try to move an entry from one file to another" do # force the parsedfile provider to also parse user1's crontab - random_resource = Puppet::Type.type(:cron).new( - :name => 'foo', - :ensure => :absent, - :target => crontab_user1, - :user => crontab_user1 - ) - resource = Puppet::Type.type(:cron).new( - :name => 'My daily failure', - :special => 'daily', - :command => "/bin/false", - :target => crontab_user2, - :user => crontab_user2 - ) - run_in_catalog(resource) + apply_with_error_check(<<-MANIFEST) + cron { + 'foo': + ensure => absent, + target => '#{crontab_user1}'; + 'My daily failure': + special => 'daily', + command => "/bin/false", + target => '#{crontab_user2}', + } + MANIFEST File.read(crontab_user1).should == File.read(my_fixture('moved_cronjob_input1')) File.read(crontab_user2).should == File.read(my_fixture('moved_cronjob_input2')) end diff --git a/spec/integration/ssl/certificate_authority_spec.rb b/spec/integration/ssl/certificate_authority_spec.rb index acbc38cbc..3cf494afa 100755 --- a/spec/integration/ssl/certificate_authority_spec.rb +++ b/spec/integration/ssl/certificate_authority_spec.rb @@ -99,6 +99,32 @@ describe Puppet::SSL::CertificateAuthority, :unless => Puppet.features.microsoft end end + describe "when revoking certificate" do + it "should work for one certificate" do + certificate_request_for("luke.madstop.com") + + ca.sign("luke.madstop.com") + ca.revoke("luke.madstop.com") + + expect { ca.verify("luke.madstop.com") }.to raise_error( + Puppet::SSL::CertificateAuthority::CertificateVerificationError, + "certificate revoked" + ) + end + + it "should work for several certificates" do + 3.times.each do |c| + certificate_request_for("luke.madstop.com") + ca.sign("luke.madstop.com") + ca.destroy("luke.madstop.com") + end + ca.revoke("luke.madstop.com") + + ca.crl.content.revoked.map { |r| r.serial }.should == [2,3,4] # ca has serial 1 + end + + end + it "allows autosigning certificates concurrently", :unless => Puppet::Util::Platform.windows? do Puppet[:autosign] = true hosts = (0..4).collect { |i| certificate_request_for("host#{i}") } diff --git a/spec/integration/ssl/certificate_request_spec.rb b/spec/integration/ssl/certificate_request_spec.rb index 4a035d532..eeb29da79 100755 --- a/spec/integration/ssl/certificate_request_spec.rb +++ b/spec/integration/ssl/certificate_request_spec.rb @@ -10,8 +10,6 @@ describe Puppet::SSL::CertificateRequest do # Get a safe temporary file dir = tmpdir("csr_integration_testing") - Puppet.settings.clear - Puppet.settings[:confdir] = dir Puppet.settings[:vardir] = dir Puppet.settings[:group] = Process.gid @@ -26,10 +24,6 @@ describe Puppet::SSL::CertificateRequest do Puppet::SSL::CertificateRequest.indirection.termini.clear end - after do - Puppet.settings.clear - end - it "should be able to generate CSRs" do @csr.generate(@key) end diff --git a/spec/integration/ssl/certificate_revocation_list_spec.rb b/spec/integration/ssl/certificate_revocation_list_spec.rb index 06a69a741..ec344926b 100755 --- a/spec/integration/ssl/certificate_revocation_list_spec.rb +++ b/spec/integration/ssl/certificate_revocation_list_spec.rb @@ -20,8 +20,6 @@ describe Puppet::SSL::CertificateRevocationList do after { Puppet::SSL::Host.ca_location = :none - Puppet.settings.clear - # This is necessary so the terminus instances don't lie around. Puppet::SSL::Host.indirection.termini.clear } diff --git a/spec/integration/ssl/host_spec.rb b/spec/integration/ssl/host_spec.rb index fbb108db7..18f0d17fc 100755 --- a/spec/integration/ssl/host_spec.rb +++ b/spec/integration/ssl/host_spec.rb @@ -22,8 +22,6 @@ describe Puppet::SSL::Host do after { Puppet::SSL::Host.ca_location = :none - - Puppet.settings.clear } it "should be considered a CA host if its name is equal to 'ca'" do diff --git a/spec/integration/transaction_spec.rb b/spec/integration/transaction_spec.rb index fc1fff228..35557b8f2 100755 --- a/spec/integration/transaction_spec.rb +++ b/spec/integration/transaction_spec.rb @@ -193,6 +193,22 @@ describe Puppet::Transaction do Puppet::FileSystem.exist?(file2).should be_true end + it "should apply no resources whatsoever if a pre_run_check fails" do + path = tmpfile("path") + file = Puppet::Type.type(:file).new( + :path => path, + :ensure => "file" + ) + notify = Puppet::Type.type(:notify).new( + :title => "foo" + ) + notify.expects(:pre_run_check).raises(Puppet::Error, "fail for testing") + + catalog = mk_catalog(file, notify) + catalog.apply + Puppet::FileSystem.exist?(path).should_not be_true + end + it "should not let one failed refresh result in other refreshes failing" do path = tmpfile("path") newfile = tmpfile("file") diff --git a/spec/integration/type/file_spec.rb b/spec/integration/type/file_spec.rb index ed8a9768f..d1862e241 100755 --- a/spec/integration/type/file_spec.rb +++ b/spec/integration/type/file_spec.rb @@ -78,7 +78,7 @@ describe Puppet::Type.type(:file), :uses_checksums => true do it "should not attempt to manage files that do not exist if no means of creating the file is specified" do source = tmpfile('source') - catalog.add_resource described_class.new :path => source, :mode => 0755 + catalog.add_resource described_class.new :path => source, :mode => '0755' status = catalog.apply.report.resource_statuses["File[#{source}]"] status.should_not be_failed @@ -155,7 +155,7 @@ describe Puppet::Type.type(:file), :uses_checksums => true do it "should set executable bits for existing readable directories" do set_mode(0600, target) - catalog.add_resource described_class.new(:path => target, :ensure => :directory, :mode => 0644) + catalog.add_resource described_class.new(:path => target, :ensure => :directory, :mode => '0644') catalog.apply (get_mode(target) & 07777).should == 0755 @@ -992,13 +992,13 @@ describe Puppet::Type.type(:file), :uses_checksums => true do describe "on Windows systems", :if => Puppet.features.microsoft_windows? do def expects_sid_granted_full_access_explicitly(path, sid) - inherited_ace = Windows::Security::INHERITED_ACE + inherited_ace = Puppet::Util::Windows::AccessControlEntry::INHERITED_ACE aces = get_aces_for_path_by_sid(path, sid) aces.should_not be_empty aces.each do |ace| - ace.mask.should == Windows::File::FILE_ALL_ACCESS + ace.mask.should == Puppet::Util::Windows::File::FILE_ALL_ACCESS (ace.flags & inherited_ace).should_not == inherited_ace end end @@ -1008,13 +1008,13 @@ describe Puppet::Type.type(:file), :uses_checksums => true do end def expects_at_least_one_inherited_ace_grants_full_access(path, sid) - inherited_ace = Windows::Security::INHERITED_ACE + inherited_ace = Puppet::Util::Windows::AccessControlEntry::INHERITED_ACE aces = get_aces_for_path_by_sid(path, sid) aces.should_not be_empty aces.any? do |ace| - ace.mask == Windows::File::FILE_ALL_ACCESS && + ace.mask == Puppet::Util::Windows::File::FILE_ALL_ACCESS && (ace.flags & inherited_ace) == inherited_ace end.should be_true end @@ -1045,10 +1045,10 @@ describe Puppet::Type.type(:file), :uses_checksums => true do describe "when processing SYSTEM ACEs" do before do @sids = { - :current_user => Puppet::Util::Windows::Security.name_to_sid(Sys::Admin.get_login), + :current_user => Puppet::Util::Windows::SID.name_to_sid(Puppet::Util::Windows::ADSI::User.current_user_name), :system => Win32::Security::SID::LocalSystem, - :admin => Puppet::Util::Windows::Security.name_to_sid("Administrator"), - :guest => Puppet::Util::Windows::Security.name_to_sid("Guest"), + :admin => Puppet::Util::Windows::SID.name_to_sid("Administrator"), + :guest => Puppet::Util::Windows::SID.name_to_sid("Guest"), :users => Win32::Security::SID::BuiltinUsers, :power_users => Win32::Security::SID::PowerUsers, :none => Win32::Security::SID::Nobody @@ -1132,7 +1132,7 @@ describe Puppet::Type.type(:file), :uses_checksums => true do system_aces.should_not be_empty system_aces.each do |ace| - ace.mask.should == Windows::File::FILE_GENERIC_READ + ace.mask.should == Puppet::Util::Windows::File::FILE_GENERIC_READ end end @@ -1173,8 +1173,9 @@ describe Puppet::Type.type(:file), :uses_checksums => true do sd = Puppet::Util::Windows::Security.get_security_descriptor(path) sd.dacl.allow( 'S-1-1-0', #everyone - Windows::File::FILE_ALL_ACCESS, - Windows::File::OBJECT_INHERIT_ACE | Windows::File::CONTAINER_INHERIT_ACE) + Puppet::Util::Windows::File::FILE_ALL_ACCESS, + Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | + Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE) Puppet::Util::Windows::Security.set_security_descriptor(path, sd) end @@ -1252,7 +1253,7 @@ describe Puppet::Type.type(:file), :uses_checksums => true do system_aces.each do |ace| # unlike files, Puppet sets execute bit on directories that are readable - ace.mask.should == Windows::File::FILE_GENERIC_READ | Windows::File::FILE_GENERIC_EXECUTE + ace.mask.should == Puppet::Util::Windows::File::FILE_GENERIC_READ | Puppet::Util::Windows::File::FILE_GENERIC_EXECUTE end end diff --git a/spec/integration/type/nagios_spec.rb b/spec/integration/type/nagios_spec.rb index 818b61649..9610daefa 100644 --- a/spec/integration/type/nagios_spec.rb +++ b/spec/integration/type/nagios_spec.rb @@ -6,9 +6,11 @@ require 'puppet/file_bucket/dipper' describe "Nagios file creation" do include PuppetSpec::Files + let(:initial_mode) { 0600 } + before :each do FileUtils.touch(target_file) - File.chmod(0600, target_file) + Puppet::FileSystem.chmod(initial_mode, target_file) Puppet::FileBucket::Dipper.any_instance.stubs(:backup) # Don't backup to filebucket end @@ -33,13 +35,6 @@ describe "Nagios file creation" do catalog.apply end - # These three helpers are from file_spec.rb - # - # @todo Define those centrally as well? - def get_mode(file) - Puppet::FileSystem.stat(file).mode - end - context "when creating a nagios config file" do context "which is not managed" do it "should choose the file mode if requested" do @@ -51,14 +46,12 @@ describe "Nagios file creation" do :mode => '0640' ) run_in_catalog(resource) - # sticky bit only applies to directories in Windows - mode = Puppet.features.microsoft_windows? ? "640" : "100640" - ( "%o" % get_mode(target_file) ).should == mode + expect_file_mode(target_file, "640") end end context "which is managed" do - it "should not the mode" do + it "should not override the mode" do file_res = Puppet::Type.type(:file).new( :name => target_file, :ensure => :present @@ -71,10 +64,8 @@ describe "Nagios file creation" do :mode => '0640' ) run_in_catalog(file_res, nag_res) - ( "%o" % get_mode(target_file) ).should_not == "100640" + expect_file_mode(target_file, initial_mode.to_s(8)) end end - end - end diff --git a/spec/integration/type/sshkey_spec.rb b/spec/integration/type/sshkey_spec.rb new file mode 100644 index 000000000..d1b1e01c7 --- /dev/null +++ b/spec/integration/type/sshkey_spec.rb @@ -0,0 +1,22 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet_spec/files' +require 'puppet_spec/compiler' + +describe Puppet::Type.type(:sshkey), '(integration)', :unless => Puppet.features.microsoft_windows? do + include PuppetSpec::Files + include PuppetSpec::Compiler + + let(:target) { tmpfile('ssh_known_hosts') } + let(:manifest) { "sshkey { 'test': + ensure => 'present', + type => 'rsa', + key => 'TESTKEY', + target => '#{target}' }" + } + + it "should create a new known_hosts file with mode 0644" do + apply_compiled_manifest(manifest) + expect_file_mode(target, "644") + end +end diff --git a/spec/integration/type/tidy_spec.rb b/spec/integration/type/tidy_spec.rb index 9c044d703..7dbefb6ca 100755 --- a/spec/integration/type/tidy_spec.rb +++ b/spec/integration/type/tidy_spec.rb @@ -23,6 +23,9 @@ describe Puppet::Type.type(:tidy) do catalog = Puppet::Resource::Catalog.new catalog.add_resource(tidy) + # avoid crude failures because of nil resources that result + # from implicit containment and lacking containers + catalog.stubs(:container_of).returns tidy catalog.apply diff --git a/spec/integration/type/user_spec.rb b/spec/integration/type/user_spec.rb new file mode 100644 index 000000000..4724fe9d5 --- /dev/null +++ b/spec/integration/type/user_spec.rb @@ -0,0 +1,36 @@ +#! /usr/bin/env ruby +require 'spec_helper' +require 'puppet_spec/files' +require 'puppet_spec/compiler' + +describe Puppet::Type.type(:user), '(integration)', :unless => Puppet.features.microsoft_windows? do + include PuppetSpec::Files + include PuppetSpec::Compiler + + context "when set to purge ssh keys from a file" do + let(:tempfile) { file_containing('user_spec', "# comment\nssh-rsa KEY-DATA key-name\nssh-rsa KEY-DATA key name\n") } + # must use an existing user, or the generated key resource + # will fail on account of an invalid user for the key + # - root should be a safe default + let(:manifest) { "user { 'root': purge_ssh_keys => '#{tempfile}' }" } + + it "should purge authorized ssh keys" do + apply_compiled_manifest(manifest) + File.read(tempfile).should_not =~ /key-name/ + end + + it "should purge keys with spaces in the comment string" do + apply_compiled_manifest(manifest) + File.read(tempfile).should_not =~ /key name/ + end + + context "with other prefetching resources evaluated first" do + let(:manifest) { "host { 'test': before => User[root] } user { 'root': purge_ssh_keys => '#{tempfile}' }" } + + it "should purge authorized ssh keys" do + apply_compiled_manifest(manifest) + File.read(tempfile).should_not =~ /key-name/ + end + end + end +end diff --git a/spec/integration/util/autoload_spec.rb b/spec/integration/util/autoload_spec.rb index bfe8b67d2..c352fea9e 100755 --- a/spec/integration/util/autoload_spec.rb +++ b/spec/integration/util/autoload_spec.rb @@ -95,12 +95,12 @@ describe Puppet::Util::Autoload do file = File.join(libdir, "plugin.rb") - Puppet[:modulepath] = modulepath - - with_loader("foo", "foo") do |dir, loader| - with_file(:plugin, file.split("/")) do - loader.load(:plugin) - loader.class.should be_loaded("foo/plugin.rb") + Puppet.override(:environments => Puppet::Environments::Static.new(Puppet::Node::Environment.create(:production, [modulepath]))) do + with_loader("foo", "foo") do |dir, loader| + with_file(:plugin, file.split("/")) do + loader.load(:plugin) + loader.class.should be_loaded("foo/plugin.rb") + end end end end diff --git a/spec/integration/util/rdoc/parser_spec.rb b/spec/integration/util/rdoc/parser_spec.rb index d3bbef45c..2fdaf8019 100755 --- a/spec/integration/util/rdoc/parser_spec.rb +++ b/spec/integration/util/rdoc/parser_spec.rb @@ -127,6 +127,13 @@ end end.should_not(be_empty, "Could not match #{content_patterns} in any of the files found in #{glob}") end + around(:each) do |example| + env = Puppet::Node::Environment.create(:doc_test_env, [modules_dir], manifests_dir) + Puppet.override({:environments => Puppet::Environments::Static.new(env), :current_environment => env}) do + example.run + end + end + before :each do prepare_manifests_and_modules Puppet.settings[:document_all] = document_all diff --git a/spec/integration/util/windows/process_spec.rb b/spec/integration/util/windows/process_spec.rb index 6dc54d228..60eae3443 100644 --- a/spec/integration/util/windows/process_spec.rb +++ b/spec/integration/util/windows/process_spec.rb @@ -18,5 +18,17 @@ describe "Puppet::Util::Windows::Process", :if => Puppet.features.microsoft_wind Puppet::Util::Windows::User.should be_admin Puppet::Util::Windows::Process.process_privilege_symlink?.should be_false end + + it "should be able to lookup a standard Windows process privilege" do + Puppet::Util::Windows::Process.lookup_privilege_value('SeShutdownPrivilege') do |luid| + luid.should_not be_nil + luid.should be_instance_of(Puppet::Util::Windows::Process::LUID) + end + end + + it "should raise an error for an unknown privilege name" do + fail_msg = /LookupPrivilegeValue\(, foo, .*\): A specified privilege does not exist/ + expect { Puppet::Util::Windows::Process.lookup_privilege_value('foo') }.to raise_error(Puppet::Util::Windows::Error, fail_msg) + end end end diff --git a/spec/integration/util/windows/security_spec.rb b/spec/integration/util/windows/security_spec.rb index fa0eadc0d..7f7aa7cb6 100755 --- a/spec/integration/util/windows/security_spec.rb +++ b/spec/integration/util/windows/security_spec.rb @@ -1,8 +1,6 @@ #!/usr/bin/env ruby require 'spec_helper' -require 'puppet/util/adsi' - if Puppet.features.microsoft_windows? class WindowsSecurityTester require 'puppet/util/windows/security' @@ -15,11 +13,11 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win before :all do @sids = { - :current_user => Puppet::Util::Windows::Security.name_to_sid(Sys::Admin.get_login), + :current_user => Puppet::Util::Windows::SID.name_to_sid(Puppet::Util::Windows::ADSI::User.current_user_name), :system => Win32::Security::SID::LocalSystem, - :admin => Puppet::Util::Windows::Security.name_to_sid("Administrator"), + :admin => Puppet::Util::Windows::SID.name_to_sid("Administrator"), :administrators => Win32::Security::SID::BuiltinAdministrators, - :guest => Puppet::Util::Windows::Security.name_to_sid("Guest"), + :guest => Puppet::Util::Windows::SID.name_to_sid("Guest"), :users => Win32::Security::SID::BuiltinUsers, :power_users => Win32::Security::SID::PowerUsers, :none => Win32::Security::SID::Nobody, @@ -31,11 +29,12 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win # (like \\localhost) to fail with unhelpful error messages. # Put a check for this upfront to aid debug should this strike again. service = Puppet::Type.type(:service).new(:name => 'lmhosts') - service.provider.status.should == :running + expect(service.provider.status).to eq(:running), 'lmhosts service is not running' end let (:sids) { @sids } let (:winsec) { WindowsSecurityTester.new } + let (:klass) { Puppet::Util::Windows::File } def set_group_depending_on_current_user(path) if sids[:current_user] == sids[:system] @@ -53,8 +52,8 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win def grant_everyone_full_access(path) sd = winsec.get_security_descriptor(path) everyone = 'S-1-1-0' - inherit = WindowsSecurityTester::OBJECT_INHERIT_ACE | WindowsSecurityTester::CONTAINER_INHERIT_ACE - sd.dacl.allow(everyone, Windows::File::FILE_ALL_ACCESS, inherit) + inherit = Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE + sd.dacl.allow(everyone, klass::FILE_ALL_ACCESS, inherit) winsec.set_security_descriptor(path, sd) end @@ -178,7 +177,7 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win # when running under SYSTEM account, multiple ACEs come back # so we only care that we have at least one of these system_aces.any? do |ace| - ace.mask == Windows::File::FILE_ALL_ACCESS + ace.mask == klass::FILE_ALL_ACCESS end.should be_true # changing the owner/group will no longer make the SD protected @@ -186,7 +185,7 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win winsec.set_owner(sids[:administrators], path) system_aces.find do |ace| - ace.mask == Windows::File::FILE_ALL_ACCESS && ace.inherited? + ace.mask == klass::FILE_ALL_ACCESS && ace.inherited? end.should_not be_nil end @@ -227,7 +226,7 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win # when running under SYSTEM account, multiple ACEs come back # so we only care that we have at least one of these system_aces.any? do |ace| - ace.mask == WindowsSecurityTester::FILE_ALL_ACCESS + ace.mask == klass::FILE_ALL_ACCESS end.should be_true # changing the mode will make the SD protected @@ -237,7 +236,7 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win # and should have a non-inherited SYSTEM ACE(s) system_aces = winsec.get_aces_for_path_by_sid(path, sids[:system]) system_aces.each do |ace| - ace.mask.should == Windows::File::FILE_ALL_ACCESS && ! ace.inherited? + ace.mask.should == klass::FILE_ALL_ACCESS && ! ace.inherited? end end @@ -259,25 +258,25 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win before :each do winsec.set_group(sids[:none], path) winsec.set_mode(0600, path) - winsec.add_attributes(path, WindowsSecurityTester::FILE_ATTRIBUTE_READONLY) - (winsec.get_attributes(path) & WindowsSecurityTester::FILE_ATTRIBUTE_READONLY).should be_nonzero + Puppet::Util::Windows::File.add_attributes(path, klass::FILE_ATTRIBUTE_READONLY) + (Puppet::Util::Windows::File.get_attributes(path) & klass::FILE_ATTRIBUTE_READONLY).should be_nonzero end it "should make them writable if any sid has write permission" do winsec.set_mode(WindowsSecurityTester::S_IWUSR, path) - (winsec.get_attributes(path) & WindowsSecurityTester::FILE_ATTRIBUTE_READONLY).should == 0 + (Puppet::Util::Windows::File.get_attributes(path) & klass::FILE_ATTRIBUTE_READONLY).should == 0 end it "should leave them read-only if no sid has write permission and should allow full access for SYSTEM" do winsec.set_mode(WindowsSecurityTester::S_IRUSR | WindowsSecurityTester::S_IXGRP, path) - (winsec.get_attributes(path) & WindowsSecurityTester::FILE_ATTRIBUTE_READONLY).should be_nonzero + (Puppet::Util::Windows::File.get_attributes(path) & klass::FILE_ATTRIBUTE_READONLY).should be_nonzero system_aces = winsec.get_aces_for_path_by_sid(path, sids[:system]) # when running under SYSTEM account, and set_group / set_owner hasn't been called # SYSTEM full access will be restored system_aces.any? do |ace| - ace.mask == Windows::File::FILE_ALL_ACCESS + ace.mask == klass::FILE_ALL_ACCESS end.should be_true end end @@ -291,7 +290,7 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win it "should report when extra aces are encounted" do sd = winsec.get_security_descriptor(path) (544..547).each do |rid| - sd.dacl.allow("S-1-5-32-#{rid}", WindowsSecurityTester::STANDARD_RIGHTS_ALL) + sd.dacl.allow("S-1-5-32-#{rid}", klass::STANDARD_RIGHTS_ALL) end winsec.set_security_descriptor(path, sd) @@ -301,12 +300,12 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win it "should return deny aces" do sd = winsec.get_security_descriptor(path) - sd.dacl.deny(sids[:guest], WindowsSecurityTester::FILE_GENERIC_WRITE) + sd.dacl.deny(sids[:guest], klass::FILE_GENERIC_WRITE) winsec.set_security_descriptor(path, sd) guest_aces = winsec.get_aces_for_path_by_sid(path, sids[:guest]) guest_aces.find do |ace| - ace.type == WindowsSecurityTester::ACCESS_DENIED_ACE_TYPE + ace.type == Puppet::Util::Windows::AccessControlEntry::ACCESS_DENIED_ACE_TYPE end.should_not be_nil end @@ -314,12 +313,12 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win sd = winsec.get_security_descriptor(path) dacl = Puppet::Util::Windows::AccessControlList.new dacl.allow( - sids[:current_user], WindowsSecurityTester::STANDARD_RIGHTS_ALL | WindowsSecurityTester::SPECIFIC_RIGHTS_ALL + sids[:current_user], klass::STANDARD_RIGHTS_ALL | klass::SPECIFIC_RIGHTS_ALL ) dacl.allow( sids[:everyone], - WindowsSecurityTester::FILE_GENERIC_READ, - WindowsSecurityTester::INHERIT_ONLY_ACE | WindowsSecurityTester::OBJECT_INHERIT_ACE + klass::FILE_GENERIC_READ, + Puppet::Util::Windows::AccessControlEntry::INHERIT_ONLY_ACE | Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE ) winsec.set_security_descriptor(path, sd) @@ -344,8 +343,8 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win it "should be present when the access control list is unprotected" do # add a bunch of aces to the parent with permission to add children - allow = WindowsSecurityTester::STANDARD_RIGHTS_ALL | WindowsSecurityTester::SPECIFIC_RIGHTS_ALL - inherit = WindowsSecurityTester::OBJECT_INHERIT_ACE | WindowsSecurityTester::CONTAINER_INHERIT_ACE + allow = klass::STANDARD_RIGHTS_ALL | klass::SPECIFIC_RIGHTS_ALL + inherit = Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE sd = winsec.get_security_descriptor(parent) sd.dacl.allow( @@ -356,7 +355,7 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win (544..547).each do |rid| sd.dacl.allow( "S-1-5-32-#{rid}", - WindowsSecurityTester::STANDARD_RIGHTS_ALL, + klass::STANDARD_RIGHTS_ALL, inherit ) end @@ -371,10 +370,12 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win describe "for an administrator", :if => Puppet.features.root? do before :each do + is_dir = Puppet::FileSystem.directory?(path) winsec.set_mode(WindowsSecurityTester::S_IRWXU | WindowsSecurityTester::S_IRWXG, path) set_group_depending_on_current_user(path) winsec.set_owner(sids[:guest], path) - lambda { File.open(path, 'r') }.should raise_error(Errno::EACCES) + expected_error = RUBY_VERSION =~ /^2\./ && is_dir ? Errno::EISDIR : Errno::EACCES + lambda { File.open(path, 'r') }.should raise_error(expected_error) end after :each do @@ -446,14 +447,14 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win describe "when the sid refers to a deleted trustee" do it "should retrieve the user sid" do sid = nil - user = Puppet::Util::ADSI::User.create("delete_me_user") + user = Puppet::Util::Windows::ADSI::User.create("delete_me_user") user.commit begin - sid = Sys::Admin::get_user(user.name).sid + sid = Puppet::Util::Windows::ADSI::User.new(user.name).sid.to_s winsec.set_owner(sid, path) winsec.set_mode(WindowsSecurityTester::S_IRWXU, path) ensure - Puppet::Util::ADSI::User.delete(user.name) + Puppet::Util::Windows::ADSI::User.delete(user.name) end winsec.get_owner(path).should == sid @@ -462,14 +463,14 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win it "should retrieve the group sid" do sid = nil - group = Puppet::Util::ADSI::Group.create("delete_me_group") + group = Puppet::Util::Windows::ADSI::Group.create("delete_me_group") group.commit begin - sid = Sys::Admin::get_group(group.name).sid + sid = Puppet::Util::Windows::ADSI::Group.new(group.name).sid.to_s winsec.set_group(sid, path) winsec.set_mode(WindowsSecurityTester::S_IRWXG, path) ensure - Puppet::Util::ADSI::Group.delete(group.name) + Puppet::Util::Windows::ADSI::Group.delete(group.name) end winsec.get_group(path).should == sid winsec.get_mode(path).should == WindowsSecurityTester::S_IRWXG @@ -813,7 +814,7 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE sd = winsec.get_security_descriptor(dir) - sd.dacl.allow(sd.owner, Windows::File::FILE_ALL_ACCESS, inherit_flags) + sd.dacl.allow(sd.owner, klass::FILE_ALL_ACCESS, inherit_flags) winsec.set_security_descriptor(dir, sd) sd = winsec.get_security_descriptor(dir) @@ -834,7 +835,7 @@ describe "Puppet::Util::Windows::Security", :if => Puppet.features.microsoft_win Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE sd = winsec.get_security_descriptor(dir) - sd.dacl.deny(sids[:guest], Windows::File::FILE_ALL_ACCESS, inherit_flags) + sd.dacl.deny(sids[:guest], klass::FILE_ALL_ACCESS, inherit_flags) winsec.set_security_descriptor(dir, sd) sd = winsec.get_security_descriptor(dir) diff --git a/spec/integration/util/windows/user_spec.rb b/spec/integration/util/windows/user_spec.rb index 0435b2cdc..4e873b34c 100755 --- a/spec/integration/util/windows/user_spec.rb +++ b/spec/integration/util/windows/user_spec.rb @@ -10,23 +10,23 @@ describe "Puppet::Util::Windows::User", :if => Puppet.features.microsoft_windows it "should be an admin if user's token contains the Administrators SID" do Puppet::Util::Windows::User.expects(:check_token_membership).returns(true) - Win32::Security.expects(:elevated_security?).never + Puppet::Util::Windows::Process.expects(:elevated_security?).never Puppet::Util::Windows::User.should be_admin end it "should not be an admin if user's token doesn't contain the Administrators SID" do Puppet::Util::Windows::User.expects(:check_token_membership).returns(false) - Win32::Security.expects(:elevated_security?).never + Puppet::Util::Windows::Process.expects(:elevated_security?).never Puppet::Util::Windows::User.should_not be_admin end it "should raise an exception if we can't check token membership" do - Puppet::Util::Windows::User.expects(:check_token_membership).raises(Win32::Security::Error, "Access denied.") - Win32::Security.expects(:elevated_security?).never + Puppet::Util::Windows::User.expects(:check_token_membership).raises(Puppet::Util::Windows::Error, "Access denied.") + Puppet::Util::Windows::Process.expects(:elevated_security?).never - lambda { Puppet::Util::Windows::User.admin? }.should raise_error(Win32::Security::Error, /Access denied./) + lambda { Puppet::Util::Windows::User.admin? }.should raise_error(Puppet::Util::Windows::Error, /Access denied./) end end @@ -36,24 +36,90 @@ describe "Puppet::Util::Windows::User", :if => Puppet.features.microsoft_windows end it "should be an admin if user is running with elevated privileges" do - Win32::Security.stubs(:elevated_security?).returns(true) + Puppet::Util::Windows::Process.stubs(:elevated_security?).returns(true) Puppet::Util::Windows::User.expects(:check_token_membership).never Puppet::Util::Windows::User.should be_admin end it "should not be an admin if user is not running with elevated privileges" do - Win32::Security.stubs(:elevated_security?).returns(false) + Puppet::Util::Windows::Process.stubs(:elevated_security?).returns(false) Puppet::Util::Windows::User.expects(:check_token_membership).never Puppet::Util::Windows::User.should_not be_admin end it "should raise an exception if the process fails to open the process token" do - Win32::Security.stubs(:elevated_security?).raises(Win32::Security::Error, "Access denied.") + Puppet::Util::Windows::Process.stubs(:elevated_security?).raises(Puppet::Util::Windows::Error, "Access denied.") Puppet::Util::Windows::User.expects(:check_token_membership).never - lambda { Puppet::Util::Windows::User.admin? }.should raise_error(Win32::Security::Error, /Access denied./) + lambda { Puppet::Util::Windows::User.admin? }.should raise_error(Puppet::Util::Windows::Error, /Access denied./) + end + end + + describe "module function" do + let(:username) { 'fabio' } + let(:bad_password) { 'goldilocks' } + let(:logon_fail_msg) { /Failed to logon user "fabio": Logon failure: unknown user name or bad password./ } + + def expect_logon_failure_error(&block) + expect { + yield + }.to raise_error { |error| + expect(error).to be_a(Puppet::Util::Windows::Error) + # http://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx + # ERROR_LOGON_FAILURE 1326 + expect(error.code).to eq(1326) + } + end + + describe "load_profile" do + it "should raise an error when provided with an incorrect username and password" do + expect_logon_failure_error { + Puppet::Util::Windows::User.load_profile(username, bad_password) + } + end + + it "should raise an error when provided with an incorrect username and nil password" do + expect_logon_failure_error { + Puppet::Util::Windows::User.load_profile(username, nil) + } + end + end + + describe "logon_user" do + it "should raise an error when provided with an incorrect username and password" do + expect_logon_failure_error { + Puppet::Util::Windows::User.logon_user(username, bad_password) + } + end + + it "should raise an error when provided with an incorrect username and nil password" do + expect_logon_failure_error { + Puppet::Util::Windows::User.logon_user(username, nil) + } + end + end + + describe "password_is?" do + it "should return false given an incorrect username and password" do + Puppet::Util::Windows::User.password_is?(username, bad_password).should be_false + end + + it "should return false given an incorrect username and nil password" do + Puppet::Util::Windows::User.password_is?(username, nil).should be_false + end + + it "should return false given a nil username and an incorrect password" do + Puppet::Util::Windows::User.password_is?(nil, bad_password).should be_false + end + end + + describe "check_token_membership" do + it "should not raise an error" do + # added just to call an FFI code path on all platforms + lambda { Puppet::Util::Windows::User.check_token_membership }.should_not raise_error + end end end end diff --git a/spec/integration/util_spec.rb b/spec/integration/util_spec.rb index d8d96aad8..84f155123 100755 --- a/spec/integration/util_spec.rb +++ b/spec/integration/util_spec.rb @@ -36,7 +36,7 @@ describe Puppet::Util do admins = 'S-1-5-32-544' dacl = Puppet::Util::Windows::AccessControlList.new - dacl.allow(admins, Windows::File::FILE_ALL_ACCESS) + dacl.allow(admins, Puppet::Util::Windows::File::FILE_ALL_ACCESS) protect = true expected_sd = Puppet::Util::Windows::SecurityDescriptor.new(admins, admins, dacl, protect) Puppet::Util::Windows::Security.set_security_descriptor(file, expected_sd) @@ -45,7 +45,7 @@ describe Puppet::Util do Puppet::Util.replace_file(file, ignored_mode) do |temp_file| ignored_sd = Puppet::Util::Windows::Security.get_security_descriptor(temp_file.path) users = 'S-1-5-11' - ignored_sd.dacl.allow(users, Windows::File::FILE_GENERIC_READ) + ignored_sd.dacl.allow(users, Puppet::Util::Windows::File::FILE_GENERIC_READ) Puppet::Util::Windows::Security.set_security_descriptor(temp_file.path, ignored_sd) end |