diff --git a/spec/integration/indirector/direct_file_server_spec.rb b/spec/integration/indirector/direct_file_server_spec.rb index be0e7b65d..9533b46bc 100755 --- a/spec/integration/indirector/direct_file_server_spec.rb +++ b/spec/integration/indirector/direct_file_server_spec.rb @@ -1,64 +1,68 @@ require 'spec_helper' require 'matchers/include' require 'puppet/indirector/file_content/file' describe Puppet::Indirector::DirectFileServer, " when interacting with the filesystem and the model" do include PuppetSpec::Files before do # We just test a subclass, since it's close enough. @terminus = Puppet::Indirector::FileContent::File.new @filepath = make_absolute("/path/to/my/file") end it "should return an instance of the model" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do + if Puppet.features.microsoft_windows? + skip("porting to Windows") + else Puppet::FileSystem.expects(:exist?).with(@filepath).returns(true) - @terminus.find(@terminus.indirection.request(:find, "file://host#{@filepath}", nil)).should be_instance_of(Puppet::FileServing::Content) + expect(@terminus.find(@terminus.indirection.request(:find, "file://host#{@filepath}", nil))).to be_instance_of(Puppet::FileServing::Content) end end it "should return an instance capable of returning its content" do - pending("porting to Windows", :if => Puppet.features.microsoft_windows?) do + if Puppet.features.microsoft_windows? + skip("porting to Windows") + else filename = file_containing("testfile", "my content") instance = @terminus.find(@terminus.indirection.request(:find, "file://host#{filename}", nil)) - instance.content.should == "my content" + expect(instance.content).to eq("my content") end end end describe Puppet::Indirector::DirectFileServer, " when interacting with FileServing::Fileset and the model" do include PuppetSpec::Files include Matchers::Include matcher :file_with_content do |name, content| match do |actual| actual.full_path == name && actual.content == content end end matcher :directory_named do |name| match do |actual| actual.full_path == name end end it "should return an instance for every file in the fileset" do path = tmpdir('direct_file_server_testing') File.open(File.join(path, "one"), "w") { |f| f.print "one content" } File.open(File.join(path, "two"), "w") { |f| f.print "two content" } terminus = Puppet::Indirector::FileContent::File.new request = terminus.indirection.request(:search, "file:///#{path}", nil, :recurse => true) expect(terminus.search(request)).to include_in_any_order( file_with_content(File.join(path, "one"), "one content"), file_with_content(File.join(path, "two"), "two content"), directory_named(path)) end end diff --git a/spec/integration/provider/mount_spec.rb b/spec/integration/provider/mount_spec.rb index 54bc171d1..4c9b914f1 100755 --- a/spec/integration/provider/mount_spec.rb +++ b/spec/integration/provider/mount_spec.rb @@ -1,171 +1,174 @@ require 'spec_helper' require 'puppet/file_bucket/dipper' describe "mount provider (integration)", :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files family = Facter.value(:osfamily) def create_fake_fstab(initially_contains_entry) File.open(@fake_fstab, 'w') do |f| if initially_contains_entry f.puts("/dev/disk1s1\t/Volumes/foo_disk\tmsdos\tlocal\t0\t0") end end end before :each do @fake_fstab = tmpfile('fstab') @current_options = "local" @current_device = "/dev/disk1s1" Puppet::Type.type(:mount).defaultprovider.stubs(:default_target).returns(@fake_fstab) Facter.stubs(:value).with(:hostname).returns('some_host') Facter.stubs(:value).with(:domain).returns('some_domain') Facter.stubs(:value).with(:kernel).returns('Darwin') Facter.stubs(:value).with(:operatingsystem).returns('Darwin') Facter.stubs(:value).with(:osfamily).returns('Darwin') Puppet::Util::ExecutionStub.set do |command, options| case command[0] when %r{/s?bin/mount} if command.length == 1 if @mounted "#{@current_device} on /Volumes/foo_disk (msdos, #{@current_options})\n" else '' end else expect(command.length).to eq(4) expect(command[1]).to eq('-o') # update is a special option, used on bsd's # strip it out and track as a local bool here update = false tmp_options = command[2].split(",") if tmp_options.include?("update") update = true tmp_options.delete("update") end @current_options = tmp_options.join(",") if !update expect(@mounted).to eq(false) # verify that we don't try to call "mount" redundantly end expect(command[3]).to eq('/Volumes/foo_disk') @current_device = check_fstab(true) @mounted = true '' end when %r{/s?bin/umount} expect(command.length).to eq(2) expect(command[1]).to eq('/Volumes/foo_disk') expect(@mounted).to eq(true) # "umount" doesn't work when device not mounted (see #6632) @mounted = false '' else fail "Unexpected command #{command.inspect} executed" end end end after :each do Puppet::Type::Mount::ProviderParsed.clear # Work around bug #6628 end def check_fstab(expected_to_be_present) # Verify that the fake fstab has the expected data in it fstab_contents = File.read(@fake_fstab).split("\n").reject { |x| x =~ /^#|^$/ } if expected_to_be_present expect(fstab_contents.length()).to eq(1) device, rest_of_line = fstab_contents[0].split(/\t/,2) expect(rest_of_line).to eq("/Volumes/foo_disk\tmsdos\t#{@desired_options}\t0\t0") device else expect(fstab_contents.length()).to eq(0) nil end end def run_in_catalog(settings) resource = Puppet::Type.type(:mount).new(settings.merge(:name => "/Volumes/foo_disk", :device => "/dev/disk1s1", :fstype => "msdos")) Puppet::FileBucket::Dipper.any_instance.stubs(:backup) # Don't backup to the filebucket resource.expects(:err).never catalog = Puppet::Resource::Catalog.new catalog.host_config = false # Stop Puppet from doing a bunch of magic catalog.add_resource resource catalog.apply end [false, true].each do |initial_state| describe "When initially #{initial_state ? 'mounted' : 'unmounted'}" do before :each do @mounted = initial_state end [false, true].each do |initial_fstab_entry| describe "When there is #{initial_fstab_entry ? 'an' : 'no'} initial fstab entry" do before :each do create_fake_fstab(initial_fstab_entry) end [:defined, :present, :mounted, :unmounted, :absent].each do |ensure_setting| expected_final_state = case ensure_setting when :mounted true when :unmounted, :absent false when :defined, :present initial_state else fail "Unknown ensure_setting #{ensure_setting}" end expected_fstab_data = (ensure_setting != :absent) describe "When setting ensure => #{ensure_setting}" do ["local", "journaled"].each do |options_setting| describe "When setting options => #{options_setting}" do it "should leave the system in the #{expected_final_state ? 'mounted' : 'unmounted'} state, #{expected_fstab_data ? 'with' : 'without'} data in /etc/fstab" do - pending("Solaris: The mock :operatingsystem value does not get changed in lib/puppet/provider/mount/parsed.rb", :if => family == "Solaris") - @desired_options = options_setting - run_in_catalog(:ensure=>ensure_setting, :options => options_setting) - expect(@mounted).to eq(expected_final_state) - if expected_fstab_data - expect(check_fstab(expected_fstab_data)).to eq("/dev/disk1s1") + if family == "Solaris" + skip("Solaris: The mock :operatingsystem value does not get changed in lib/puppet/provider/mount/parsed.rb") else - expect(check_fstab(expected_fstab_data)).to eq(nil) - end - if @mounted - if ![:defined, :present].include?(ensure_setting) - expect(@current_options).to eq(@desired_options) - elsif initial_fstab_entry - expect(@current_options).to eq(@desired_options) + @desired_options = options_setting + run_in_catalog(:ensure=>ensure_setting, :options => options_setting) + expect(@mounted).to eq(expected_final_state) + if expected_fstab_data + expect(check_fstab(expected_fstab_data)).to eq("/dev/disk1s1") else - expect(@current_options).to eq('local') #Workaround for #6645 + expect(check_fstab(expected_fstab_data)).to eq(nil) + end + if @mounted + if ![:defined, :present].include?(ensure_setting) + expect(@current_options).to eq(@desired_options) + elsif initial_fstab_entry + expect(@current_options).to eq(@desired_options) + else + expect(@current_options).to eq('local') #Workaround for #6645 + end end end end end end end end end end end end describe "When the wrong device is mounted" do it "should remount the correct device" do pending "Due to bug 6309" @mounted = true @current_device = "/dev/disk2s2" create_fake_fstab(true) @desired_options = "local" run_in_catalog(:ensure=>:mounted, :options=>'local') expect(@current_device).to eq("/dev/disk1s1") expect(@mounted).to eq(true) expect(@current_options).to eq('local') expect(check_fstab(true)).to eq("/dev/disk1s1") end end end diff --git a/spec/unit/network/authstore_spec.rb b/spec/unit/network/authstore_spec.rb index a5c082944..bba64a9e6 100755 --- a/spec/unit/network/authstore_spec.rb +++ b/spec/unit/network/authstore_spec.rb @@ -1,424 +1,423 @@ #! /usr/bin/env ruby require 'spec_helper' require 'rbconfig' require 'puppet/network/authconfig' describe Puppet::Network::AuthStore do before :each do @authstore = Puppet::Network::AuthStore.new @authstore.reset_interpolation end describe "when checking if the acl has some entries" do it "should be empty if no ACE have been entered" do expect(@authstore).to be_empty end it "should not be empty if it is a global allow" do @authstore.allow('*') expect(@authstore).not_to be_empty end it "should not be empty if at least one allow has been entered" do @authstore.allow_ip('1.1.1.*') expect(@authstore).not_to be_empty end it "should not be empty if at least one deny has been entered" do @authstore.deny_ip('1.1.1.*') expect(@authstore).not_to be_empty end end describe "when checking global allow" do it "should not be enabled by default" do expect(@authstore).not_to be_globalallow expect(@authstore).not_to be_allowed('foo.bar.com', '192.168.1.1') end it "should always allow when enabled" do @authstore.allow('*') expect(@authstore).to be_globalallow expect(@authstore).to be_allowed('foo.bar.com', '192.168.1.1') end end describe "when checking a regex type of allow" do before :each do @authstore.allow('/^(test-)?host[0-9]+\.other-domain\.(com|org|net)$|some-domain\.com/') @ip = '192.168.1.1' end ['host5.other-domain.com', 'test-host12.other-domain.net', 'foo.some-domain.com'].each { |name| it "should allow the host #{name}" do expect(@authstore).to be_allowed(name, @ip) end } ['host0.some-other-domain.com',''].each { |name| it "should not allow the host #{name}" do expect(@authstore).not_to be_allowed(name, @ip) end } end end describe Puppet::Network::AuthStore::Declaration do ['100.101.99.98','100.100.100.100','1.2.3.4','11.22.33.44'].each { |ip| describe "when the pattern is a simple numeric IP such as #{ip}" do before :each do @declaration = Puppet::Network::AuthStore::Declaration.new(:allow_ip,ip) end it "should match the specified IP" do expect(@declaration).to be_match('www.testsite.org',ip) end it "should not match other IPs" do expect(@declaration).not_to be_match('www.testsite.org','200.101.99.98') end end (1..3).each { |n| describe "when the pattern is an IP mask with #{n} numeric segments and a *" do before :each do @ip_pattern = ip.split('.')[0,n].join('.')+'.*' @declaration = Puppet::Network::AuthStore::Declaration.new(:allow_ip,@ip_pattern) end it "should match an IP in the range" do expect(@declaration).to be_match('www.testsite.org',ip) end it "should not match other IPs" do expect(@declaration).not_to be_match('www.testsite.org','200.101.99.98') end it "should not match IPs that differ in the last non-wildcard segment" do other = ip.split('.') other[n-1].succ! expect(@declaration).not_to be_match('www.testsite.org',other.join('.')) end end } } describe "when the pattern is a numeric IP with a back reference" do pending("implementation of backreferences for IP") do before :each do @ip = '100.101.$1' @declaration = Puppet::Network::AuthStore::Declaration.new(:allow_ip,@ip).interpolate('12.34'.match(/(.*)/)) end it "should match an IP with the appropriate interpolation" do @declaration.should be_match('www.testsite.org',@ip.sub(/\$1/,'12.34')) end it "should not match other IPs" do @declaration.should_not be_match('www.testsite.org',@ip.sub(/\$1/,'66.34')) end end end [ "02001:0000:1234:0000:0000:C1C0:ABCD:0876", "2001:0000:1234:0000:00001:C1C0:ABCD:0876", " 2001:0000:1234:0000:0000:C1C0:ABCD:0876 0", "2001:0000:1234: 0000:0000:C1C0:ABCD:0876", "3ffe:0b00:0000:0001:0000:0000:000a", "FF02:0000:0000:0000:0000:0000:0000:0000:0001", "3ffe:b00::1::a", "1:2:3::4:5::7:8", "12345::6:7:8", "1::5:400.2.3.4", "1::5:260.2.3.4", "1::5:256.2.3.4", "1::5:1.256.3.4", "1::5:1.2.256.4", "1::5:1.2.3.256", "1::5:300.2.3.4", "1::5:1.300.3.4", "1::5:1.2.300.4", "1::5:1.2.3.300", "1::5:900.2.3.4", "1::5:1.900.3.4", "1::5:1.2.900.4", "1::5:1.2.3.900", "1::5:300.300.300.300", "1::5:3000.30.30.30", "1::400.2.3.4", "1::260.2.3.4", "1::256.2.3.4", "1::1.256.3.4", "1::1.2.256.4", "1::1.2.3.256", "1::300.2.3.4", "1::1.300.3.4", "1::1.2.300.4", "1::1.2.3.300", "1::900.2.3.4", "1::1.900.3.4", "1::1.2.900.4", "1::1.2.3.900", "1::300.300.300.300", "1::3000.30.30.30", "::400.2.3.4", "::260.2.3.4", "::256.2.3.4", "::1.256.3.4", "::1.2.256.4", "::1.2.3.256", "::300.2.3.4", "::1.300.3.4", "::1.2.300.4", "::1.2.3.300", "::900.2.3.4", "::1.900.3.4", "::1.2.900.4", "::1.2.3.900", "::300.300.300.300", "::3000.30.30.30", "2001:DB8:0:0:8:800:200C:417A:221", # unicast, full "FF01::101::2" # multicast, compressed ].each { |invalid_ip| describe "when the pattern is an invalid IPv6 address such as #{invalid_ip}" do it "should raise an exception" do expect { Puppet::Network::AuthStore::Declaration.new(:allow,invalid_ip) }.to raise_error end end } [ "1.2.3.4", "2001:0000:1234:0000:0000:C1C0:ABCD:0876", "3ffe:0b00:0000:0000:0001:0000:0000:000a", "FF02:0000:0000:0000:0000:0000:0000:0001", "0000:0000:0000:0000:0000:0000:0000:0001", "0000:0000:0000:0000:0000:0000:0000:0000", "::ffff:192.168.1.26", "2::10", "ff02::1", "fe80::", "2002::", "2001:db8::", "2001:0db8:1234::", "::ffff:0:0", "::1", "::ffff:192.168.1.1", "1:2:3:4:5:6:7:8", "1:2:3:4:5:6::8", "1:2:3:4:5::8", "1:2:3:4::8", "1:2:3::8", "1:2::8", "1::8", "1::2:3:4:5:6:7", "1::2:3:4:5:6", "1::2:3:4:5", "1::2:3:4", "1::2:3", "1::8", "::2:3:4:5:6:7", "::2:3:4:5:6", "::2:3:4:5", "::2:3:4", "::2:3", "::8", "1:2:3:4:5:6::", "1:2:3:4:5::", "1:2:3:4::", "1:2:3::", "1:2::", "1::", "1:2:3:4:5::7:8", "1:2:3:4::7:8", "1:2:3::7:8", "1:2::7:8", "1::7:8", "1:2:3:4:5:6:1.2.3.4", "1:2:3:4:5::1.2.3.4", "1:2:3:4::1.2.3.4", "1:2:3::1.2.3.4", "1:2::1.2.3.4", "1::1.2.3.4", "1:2:3:4::5:1.2.3.4", "1:2:3::5:1.2.3.4", "1:2::5:1.2.3.4", "1::5:1.2.3.4", "1::5:11.22.33.44", "fe80::217:f2ff:254.7.237.98", "fe80::217:f2ff:fe07:ed62", "2001:DB8:0:0:8:800:200C:417A", # unicast, full "FF01:0:0:0:0:0:0:101", # multicast, full "0:0:0:0:0:0:0:1", # loopback, full "0:0:0:0:0:0:0:0", # unspecified, full "2001:DB8::8:800:200C:417A", # unicast, compressed "FF01::101", # multicast, compressed "::1", # loopback, compressed, non-routable "::", # unspecified, compressed, non-routable "0:0:0:0:0:0:13.1.68.3", # IPv4-compatible IPv6 address, full, deprecated "0:0:0:0:0:FFFF:129.144.52.38", # IPv4-mapped IPv6 address, full "::13.1.68.3", # IPv4-compatible IPv6 address, compressed, deprecated "::FFFF:129.144.52.38", # IPv4-mapped IPv6 address, compressed "2001:0DB8:0000:CD30:0000:0000:0000:0000/60", # full, with prefix "2001:0DB8::CD30:0:0:0:0/60", # compressed, with prefix "2001:0DB8:0:CD30::/60", # compressed, with prefix #2 "::/128", # compressed, unspecified address type, non-routable "::1/128", # compressed, loopback address type, non-routable "FF00::/8", # compressed, multicast address type "FE80::/10", # compressed, link-local unicast, non-routable "FEC0::/10", # compressed, site-local unicast, deprecated "127.0.0.1", # standard IPv4, loopback, non-routable "0.0.0.0", # standard IPv4, unspecified, non-routable "255.255.255.255", # standard IPv4 "fe80:0000:0000:0000:0204:61ff:fe9d:f156", "fe80:0:0:0:204:61ff:fe9d:f156", "fe80::204:61ff:fe9d:f156", "fe80:0000:0000:0000:0204:61ff:254.157.241.086", "fe80:0:0:0:204:61ff:254.157.241.86", "fe80::204:61ff:254.157.241.86", "::1", "fe80::", "fe80::1" ].each { |ip| describe "when the pattern is a valid IP such as #{ip}" do before :each do @declaration = Puppet::Network::AuthStore::Declaration.new(:allow_ip,ip) end it "should match the specified IP" do expect(@declaration).to be_match('www.testsite.org',ip) end it "should not match other IPs" do expect(@declaration).not_to be_match('www.testsite.org','200.101.99.98') end end unless ip =~ /:.*\./ # Hybrid IPs aren't supported by ruby's ipaddr } [ "::2:3:4:5:6:7:8", ].each { |ip| describe "when the pattern is a valid IP such as #{ip}" do let(:declaration) do Puppet::Network::AuthStore::Declaration.new(:allow_ip,ip) end issue_7477 = !(IPAddr.new(ip) rescue false) - it "should match the specified IP" do - pending "resolution of ruby issue [7477](http://goo.gl/Bb1LU)", :if => issue_7477 - expect(declaration).to be_match('www.testsite.org',ip) - end - it "should not match other IPs" do - pending "resolution of ruby issue [7477](http://goo.gl/Bb1LU)", :if => issue_7477 - expect(declaration).not_to be_match('www.testsite.org','200.101.99.98') - end + describe "on rubies with a fix for issue [7477](http://goo.gl/Bb1LU)", :if => issue_7477 + it "should match the specified IP" do + expect(declaration).to be_match('www.testsite.org',ip) + end + it "should not match other IPs" do + expect(declaration).not_to be_match('www.testsite.org','200.101.99.98') + end end } { 'spirit.mars.nasa.gov' => 'a PQDN', 'ratchet.2ndsiteinc.com' => 'a PQDN with digits', 'a.c.ru' => 'a PQDN with short segments', }.each {|pqdn,desc| describe "when the pattern is #{desc}" do before :each do @host = pqdn @declaration = Puppet::Network::AuthStore::Declaration.new(:allow,@host) end it "should match the specified PQDN" do expect(@declaration).to be_match(@host,'200.101.99.98') end it "should not match a similar FQDN" do pending "FQDN consensus" expect(@declaration).not_to be_match(@host+'.','200.101.99.98') end end } ['abc.12seps.edu.phisher.biz','www.google.com','slashdot.org'].each { |host| (1...(host.split('.').length)).each { |n| describe "when the pattern is #{"*."+host.split('.')[-n,n].join('.')}" do before :each do @pattern = "*."+host.split('.')[-n,n].join('.') @declaration = Puppet::Network::AuthStore::Declaration.new(:allow,@pattern) end it "should match #{host}" do expect(@declaration).to be_match(host,'1.2.3.4') end it "should not match www.testsite.gov" do expect(@declaration).not_to be_match('www.testsite.gov','200.101.99.98') end it "should not match hosts that differ in the first non-wildcard segment" do other = host.split('.') other[-n].succ! expect(@declaration).not_to be_match(other.join('.'),'1.2.3.4') end end } } describe "when the pattern is a FQDN" do before :each do @host = 'spirit.mars.nasa.gov.' @declaration = Puppet::Network::AuthStore::Declaration.new(:allow,@host) end it "should match the specified FQDN" do pending "FQDN consensus" expect(@declaration).to be_match(@host,'200.101.99.98') end it "should not match a similar PQDN" do expect(@declaration).not_to be_match(@host[0..-2],'200.101.99.98') end end describe "when the pattern is an opaque string with a back reference" do before :each do @host = 'c216f41a-f902-4bfb-a222-850dd957bebb' @item = "/catalog/#{@host}" @pattern = %{^/catalog/([^/]+)$} @declaration = Puppet::Network::AuthStore::Declaration.new(:allow,'$1') end it "should match an IP with the appropriate interpolation" do expect(@declaration.interpolate(@item.match(@pattern))).to be_match(@host,'10.0.0.5') end end describe "when the pattern is an opaque string with a back reference and the matched data contains dots" do before :each do @host = 'admin.mgmt.nym1' @item = "/catalog/#{@host}" @pattern = %{^/catalog/([^/]+)$} @declaration = Puppet::Network::AuthStore::Declaration.new(:allow,'$1') end it "should match a name with the appropriate interpolation" do expect(@declaration.interpolate(@item.match(@pattern))).to be_match(@host,'10.0.0.5') end end describe "when the pattern is an opaque string with a back reference and the matched data contains dots with an initial prefix that looks like an IP address" do before :each do @host = '01.admin.mgmt.nym1' @item = "/catalog/#{@host}" @pattern = %{^/catalog/([^/]+)$} @declaration = Puppet::Network::AuthStore::Declaration.new(:allow,'$1') end it "should match a name with the appropriate interpolation" do expect(@declaration.interpolate(@item.match(@pattern))).to be_match(@host,'10.0.0.5') end end describe "when comparing patterns" do before :each do @ip = Puppet::Network::AuthStore::Declaration.new(:allow,'127.0.0.1') @host_name = Puppet::Network::AuthStore::Declaration.new(:allow,'www.hard_knocks.edu') @opaque = Puppet::Network::AuthStore::Declaration.new(:allow,'hey_dude') end it "should consider ip addresses before host names" do expect(@ip < @host_name).to be_truthy end it "should consider ip addresses before opaque strings" do expect(@ip < @opaque).to be_truthy end it "should consider host_names before opaque strings" do expect(@host_name < @opaque).to be_truthy end end end diff --git a/spec/unit/type/file_spec.rb b/spec/unit/type/file_spec.rb index 4a68fb785..f57a0fba6 100755 --- a/spec/unit/type/file_spec.rb +++ b/spec/unit/type/file_spec.rb @@ -1,1508 +1,1509 @@ #! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:file) do include PuppetSpec::Files let(:path) { tmpfile('file_testing') } let(:file) { described_class.new(:path => path, :catalog => catalog) } let(:provider) { file.provider } let(:catalog) { Puppet::Resource::Catalog.new } before do Puppet.features.stubs("posix?").returns(true) end describe "the path parameter" do describe "on POSIX systems", :if => Puppet.features.posix? do it "should remove trailing slashes" do file[:path] = "/foo/bar/baz/" expect(file[:path]).to eq("/foo/bar/baz") end it "should remove double slashes" do file[:path] = "/foo/bar//baz" expect(file[:path]).to eq("/foo/bar/baz") end it "should remove triple slashes" do file[:path] = "/foo/bar///baz" expect(file[:path]).to eq("/foo/bar/baz") end it "should remove trailing double slashes" do file[:path] = "/foo/bar/baz//" expect(file[:path]).to eq("/foo/bar/baz") end it "should leave a single slash alone" do file[:path] = "/" expect(file[:path]).to eq("/") end it "should accept and collapse a double-slash at the start of the path" do file[:path] = "//tmp/xxx" expect(file[:path]).to eq('/tmp/xxx') end it "should accept and collapse a triple-slash at the start of the path" do file[:path] = "///tmp/xxx" expect(file[:path]).to eq('/tmp/xxx') end end describe "on Windows systems", :if => Puppet.features.microsoft_windows? do it "should remove trailing slashes" do file[:path] = "X:/foo/bar/baz/" expect(file[:path]).to eq("X:/foo/bar/baz") end it "should remove double slashes" do file[:path] = "X:/foo/bar//baz" expect(file[:path]).to eq("X:/foo/bar/baz") end it "should remove trailing double slashes" do file[:path] = "X:/foo/bar/baz//" expect(file[:path]).to eq("X:/foo/bar/baz") end it "should leave a drive letter with a slash alone" do file[:path] = "X:/" expect(file[:path]).to eq("X:/") end it "should not accept a drive letter without a slash" do expect { file[:path] = "X:" }.to raise_error(/File paths must be fully qualified/) end describe "when using UNC filenames", :if => Puppet.features.microsoft_windows? do it "should remove trailing slashes" do file[:path] = "//localhost/foo/bar/baz/" expect(file[:path]).to eq("//localhost/foo/bar/baz") end it "should remove double slashes" do file[:path] = "//localhost/foo/bar//baz" expect(file[:path]).to eq("//localhost/foo/bar/baz") end it "should remove trailing double slashes" do file[:path] = "//localhost/foo/bar/baz//" expect(file[:path]).to eq("//localhost/foo/bar/baz") end it "should remove a trailing slash from a sharename" do file[:path] = "//localhost/foo/" expect(file[:path]).to eq("//localhost/foo") end it "should not modify a sharename" do file[:path] = "//localhost/foo" expect(file[:path]).to eq("//localhost/foo") end end end end describe "the backup parameter" do [false, 'false', :false].each do |value| it "should disable backup if the value is #{value.inspect}" do file[:backup] = value expect(file[:backup]).to eq(false) end end [true, 'true', '.puppet-bak'].each do |value| it "should use .puppet-bak if the value is #{value.inspect}" do file[:backup] = value expect(file[:backup]).to eq('.puppet-bak') end end it "should use the provided value if it's any other string" do file[:backup] = "over there" expect(file[:backup]).to eq("over there") end it "should fail if backup is set to anything else" do expect do file[:backup] = 97 end.to raise_error(Puppet::Error, /Invalid backup type 97/) end end describe "the recurse parameter" do it "should default to recursion being disabled" do expect(file[:recurse]).to be_falsey end [true, "true", "remote"].each do |value| it "should consider #{value} to enable recursion" do file[:recurse] = value expect(file[:recurse]).to be_truthy end end it "should not allow numbers" do expect { file[:recurse] = 10 }.to raise_error( Puppet::Error, /Parameter recurse failed on File\[[^\]]+\]: Invalid recurse value 10/) end [false, "false"].each do |value| it "should consider #{value} to disable recursion" do file[:recurse] = value expect(file[:recurse]).to be_falsey end end end describe "the recurselimit parameter" do it "should accept integers" do file[:recurselimit] = 12 expect(file[:recurselimit]).to eq(12) end it "should munge string numbers to number numbers" do file[:recurselimit] = '12' expect(file[:recurselimit]).to eq(12) end it "should fail if given a non-number" do expect do file[:recurselimit] = 'twelve' end.to raise_error(Puppet::Error, /Invalid value "twelve"/) end end describe "the replace parameter" do [true, :true, :yes].each do |value| it "should consider #{value} to be true" do file[:replace] = value expect(file[:replace]).to be_truthy end end [false, :false, :no].each do |value| it "should consider #{value} to be false" do file[:replace] = value expect(file[:replace]).to be_falsey end end end describe ".instances" do it "should return an empty array" do expect(described_class.instances).to eq([]) end end describe "#bucket" do it "should return nil if backup is off" do file[:backup] = false expect(file.bucket).to eq(nil) end it "should not return a bucket if using a file extension for backup" do file[:backup] = '.backup' expect(file.bucket).to eq(nil) end it "should return the default filebucket if using the 'puppet' filebucket" do file[:backup] = 'puppet' bucket = stub('bucket') file.stubs(:default_bucket).returns bucket expect(file.bucket).to eq(bucket) end it "should fail if using a remote filebucket and no catalog exists" do file.catalog = nil file[:backup] = 'my_bucket' expect { file.bucket }.to raise_error(Puppet::Error, "Can not find filebucket for backups without a catalog") end it "should fail if the specified filebucket isn't in the catalog" do file[:backup] = 'my_bucket' expect { file.bucket }.to raise_error(Puppet::Error, "Could not find filebucket my_bucket specified in backup") end it "should use the specified filebucket if it is in the catalog" do file[:backup] = 'my_bucket' filebucket = Puppet::Type.type(:filebucket).new(:name => 'my_bucket') catalog.add_resource(filebucket) expect(file.bucket).to eq(filebucket.bucket) end end describe "#asuser" do before :each do # Mocha won't let me just stub SUIDManager.asuser to yield and return, # but it will do exactly that if we're not root. Puppet::Util::SUIDManager.stubs(:root?).returns false end it "should return the desired owner if they can write to the parent directory" do file[:owner] = 1001 FileTest.stubs(:writable?).with(File.dirname file[:path]).returns true expect(file.asuser).to eq(1001) end it "should return nil if the desired owner can't write to the parent directory" do file[:owner] = 1001 FileTest.stubs(:writable?).with(File.dirname file[:path]).returns false expect(file.asuser).to eq(nil) end it "should return nil if not managing owner" do expect(file.asuser).to eq(nil) end end describe "#exist?" do it "should be considered existent if it can be stat'ed" do file.expects(:stat).returns mock('stat') expect(file).to be_exist end it "should be considered nonexistent if it can not be stat'ed" do file.expects(:stat).returns nil expect(file).to_not be_exist end end describe "#eval_generate" do before do @graph = stub 'graph', :add_edge => nil catalog.stubs(:relationship_graph).returns @graph end it "should recurse if recursion is enabled" do resource = stub('resource', :[] => 'resource') file.expects(:recurse).returns [resource] file[:recurse] = true expect(file.eval_generate).to eq([resource]) end it "should not recurse if recursion is disabled" do file.expects(:recurse).never file[:recurse] = false expect(file.eval_generate).to eq([]) end end describe "#ancestors" do it "should return the ancestors of the file, in ascending order" do file = described_class.new(:path => make_absolute("/tmp/foo/bar/baz/qux")) pieces = %W[#{make_absolute('/')} tmp foo bar baz] ancestors = file.ancestors expect(ancestors).not_to be_empty ancestors.reverse.each_with_index do |path,i| expect(path).to eq(File.join(*pieces[0..i])) end end end describe "#flush" do it "should flush all properties that respond to :flush" do file[:source] = File.expand_path(__FILE__) file.parameter(:source).expects(:flush) file.flush end it "should reset its stat reference" do FileUtils.touch(path) stat1 = file.stat expect(file.stat).to equal(stat1) file.flush expect(file.stat).not_to equal(stat1) end end describe "#initialize" do it "should remove a trailing slash from the title to create the path" do title = File.expand_path("/abc/\n\tdef/") file = described_class.new(:title => title) expect(file[:path]).to eq(title) end it "should set a desired 'ensure' value if none is set and 'content' is set" do file = described_class.new(:path => path, :content => "/foo/bar") expect(file[:ensure]).to eq(:file) end it "should set a desired 'ensure' value if none is set and 'target' is set", :if => described_class.defaultprovider.feature?(:manages_symlinks) do file = described_class.new(:path => path, :target => File.expand_path(__FILE__)) expect(file[:ensure]).to eq(:link) end end describe "#mark_children_for_purging" do it "should set each child's ensure to absent" do paths = %w[foo bar baz] children = paths.inject({}) do |children,child| children.merge child => described_class.new(:path => File.join(path, child), :ensure => :present) end file.mark_children_for_purging(children) expect(children.length).to eq(3) children.values.each do |child| expect(child[:ensure]).to eq(:absent) end end it "should skip children which have a source" do child = described_class.new(:path => path, :ensure => :present, :source => File.expand_path(__FILE__)) file.mark_children_for_purging('foo' => child) expect(child[:ensure]).to eq(:present) end end describe "#newchild" do it "should create a new resource relative to the parent" do child = file.newchild('bar') expect(child).to be_a(described_class) expect(child[:path]).to eq(File.join(file[:path], 'bar')) end { :ensure => :present, :recurse => true, :recurselimit => 5, :target => "some_target", :source => File.expand_path("some_source"), }.each do |param, value| it "should omit the #{param} parameter", :if => described_class.defaultprovider.feature?(:manages_symlinks) do # Make a new file, because we have to set the param at initialization # or it wouldn't be copied regardless. file = described_class.new(:path => path, param => value) child = file.newchild('bar') expect(child[param]).not_to eq(value) end end it "should copy all of the parent resource's 'should' values that were set at initialization" do parent = described_class.new(:path => path, :owner => 'root', :group => 'wheel') child = parent.newchild("my/path") expect(child[:owner]).to eq('root') expect(child[:group]).to eq('wheel') end it "should not copy default values to the new child" do child = file.newchild("my/path") expect(child.original_parameters).not_to include(:backup) end it "should not copy values to the child which were set by the source" do source = File.expand_path(__FILE__) file[:source] = source metadata = stub 'metadata', :owner => "root", :group => "root", :mode => '0755', :ftype => "file", :checksum => "{md5}whatever", :source => source file.parameter(:source).stubs(:metadata).returns metadata file.parameter(:source).copy_source_values file.class.expects(:new).with { |params| params[:group].nil? } file.newchild("my/path") end end describe "#purge?" do it "should return false if purge is not set" do expect(file).to_not be_purge end it "should return true if purge is set to true" do file[:purge] = true expect(file).to be_purge end it "should return false if purge is set to false" do file[:purge] = false expect(file).to_not be_purge end end describe "#recurse" do before do file[:recurse] = true @metadata = Puppet::FileServing::Metadata end describe "and a source is set" do it "should pass the already-discovered resources to recurse_remote" do file[:source] = File.expand_path(__FILE__) file.stubs(:recurse_local).returns(:foo => "bar") file.expects(:recurse_remote).with(:foo => "bar").returns [] file.recurse end end describe "and a target is set" do it "should use recurse_link" do file[:target] = File.expand_path(__FILE__) file.stubs(:recurse_local).returns(:foo => "bar") file.expects(:recurse_link).with(:foo => "bar").returns [] file.recurse end end it "should use recurse_local if recurse is not remote" do file.expects(:recurse_local).returns({}) file.recurse end it "should not use recurse_local if recurse is remote" do file[:recurse] = :remote file.expects(:recurse_local).never file.recurse end it "should return the generated resources as an array sorted by file path" do one = stub 'one', :[] => "/one" two = stub 'two', :[] => "/one/two" three = stub 'three', :[] => "/three" file.expects(:recurse_local).returns(:one => one, :two => two, :three => three) expect(file.recurse).to eq([one, two, three]) end describe "and purging is enabled" do before do file[:purge] = true end it "should mark each file for removal" do local = described_class.new(:path => path, :ensure => :present) file.expects(:recurse_local).returns("local" => local) file.recurse expect(local[:ensure]).to eq(:absent) end it "should not remove files that exist in the remote repository" do file[:source] = File.expand_path(__FILE__) file.expects(:recurse_local).returns({}) remote = described_class.new(:path => path, :source => File.expand_path(__FILE__), :ensure => :present) file.expects(:recurse_remote).with { |hash| hash["remote"] = remote } file.recurse expect(remote[:ensure]).not_to eq(:absent) end end end describe "#remove_less_specific_files" do it "should remove any nested files that are already in the catalog" do foo = described_class.new :path => File.join(file[:path], 'foo') bar = described_class.new :path => File.join(file[:path], 'bar') baz = described_class.new :path => File.join(file[:path], 'baz') catalog.add_resource(foo) catalog.add_resource(bar) expect(file.remove_less_specific_files([foo, bar, baz])).to eq([baz]) end end describe "#remove_less_specific_files" do it "should remove any nested files that are already in the catalog" do foo = described_class.new :path => File.join(file[:path], 'foo') bar = described_class.new :path => File.join(file[:path], 'bar') baz = described_class.new :path => File.join(file[:path], 'baz') catalog.add_resource(foo) catalog.add_resource(bar) expect(file.remove_less_specific_files([foo, bar, baz])).to eq([baz]) end end describe "#recurse?" do it "should be true if recurse is true" do file[:recurse] = true expect(file).to be_recurse end it "should be true if recurse is remote" do file[:recurse] = :remote expect(file).to be_recurse end it "should be false if recurse is false" do file[:recurse] = false expect(file).to_not be_recurse end end describe "#recurse_link" do before do @first = stub 'first', :relative_path => "first", :full_path => "/my/first", :ftype => "directory" @second = stub 'second', :relative_path => "second", :full_path => "/my/second", :ftype => "file" @resource = stub 'file', :[]= => nil end it "should pass its target to the :perform_recursion method" do file[:target] = "mylinks" file.expects(:perform_recursion).with("mylinks").returns [@first] file.stubs(:newchild).returns @resource file.recurse_link({}) end it "should ignore the recursively-found '.' file and configure the top-level file to create a directory" do @first.stubs(:relative_path).returns "." file[:target] = "mylinks" file.expects(:perform_recursion).with("mylinks").returns [@first] file.stubs(:newchild).never file.expects(:[]=).with(:ensure, :directory) file.recurse_link({}) end it "should create a new child resource for each generated metadata instance's relative path that doesn't already exist in the children hash" do file.expects(:perform_recursion).returns [@first, @second] file.expects(:newchild).with(@first.relative_path).returns @resource file.recurse_link("second" => @resource) end it "should not create a new child resource for paths that already exist in the children hash" do file.expects(:perform_recursion).returns [@first] file.expects(:newchild).never file.recurse_link("first" => @resource) end it "should set the target to the full path of discovered file and set :ensure to :link if the file is not a directory", :if => described_class.defaultprovider.feature?(:manages_symlinks) do file.stubs(:perform_recursion).returns [@first, @second] file.recurse_link("first" => @resource, "second" => file) expect(file[:ensure]).to eq(:link) expect(file[:target]).to eq("/my/second") end it "should :ensure to :directory if the file is a directory" do file.stubs(:perform_recursion).returns [@first, @second] file.recurse_link("first" => file, "second" => @resource) expect(file[:ensure]).to eq(:directory) end it "should return a hash with both created and existing resources with the relative paths as the hash keys" do file.expects(:perform_recursion).returns [@first, @second] file.stubs(:newchild).returns file expect(file.recurse_link("second" => @resource)).to eq({"second" => @resource, "first" => file}) end end describe "#recurse_local" do before do @metadata = stub 'metadata', :relative_path => "my/file" end it "should pass its path to the :perform_recursion method" do file.expects(:perform_recursion).with(file[:path]).returns [@metadata] file.stubs(:newchild) file.recurse_local end it "should return an empty hash if the recursion returns nothing" do file.expects(:perform_recursion).returns nil expect(file.recurse_local).to eq({}) end it "should create a new child resource with each generated metadata instance's relative path" do file.expects(:perform_recursion).returns [@metadata] file.expects(:newchild).with(@metadata.relative_path).returns "fiebar" file.recurse_local end it "should not create a new child resource for the '.' directory" do @metadata.stubs(:relative_path).returns "." file.expects(:perform_recursion).returns [@metadata] file.expects(:newchild).never file.recurse_local end it "should return a hash of the created resources with the relative paths as the hash keys" do file.expects(:perform_recursion).returns [@metadata] file.expects(:newchild).with("my/file").returns "fiebar" expect(file.recurse_local).to eq({"my/file" => "fiebar"}) end it "should set checksum_type to none if this file checksum is none" do file[:checksum] = :none Puppet::FileServing::Metadata.indirection.expects(:search).with { |path,params| params[:checksum_type] == :none }.returns [@metadata] file.expects(:newchild).with("my/file").returns "fiebar" file.recurse_local end end describe "#recurse_remote", :uses_checksums => true do let(:my) { File.expand_path('/my') } before do file[:source] = "puppet://foo/bar" @first = Puppet::FileServing::Metadata.new(my, :relative_path => "first") @second = Puppet::FileServing::Metadata.new(my, :relative_path => "second") @first.stubs(:ftype).returns "directory" @second.stubs(:ftype).returns "directory" @parameter = stub 'property', :metadata= => nil @resource = stub 'file', :[]= => nil, :parameter => @parameter end it "should pass its source to the :perform_recursion method" do data = Puppet::FileServing::Metadata.new(File.expand_path("/whatever"), :relative_path => "foobar") file.expects(:perform_recursion).with("puppet://foo/bar").returns [data] file.stubs(:newchild).returns @resource file.recurse_remote({}) end it "should not recurse when the remote file is not a directory" do data = Puppet::FileServing::Metadata.new(File.expand_path("/whatever"), :relative_path => ".") data.stubs(:ftype).returns "file" file.expects(:perform_recursion).with("puppet://foo/bar").returns [data] file.expects(:newchild).never file.recurse_remote({}) end it "should set the source of each returned file to the searched-for URI plus the found relative path" do @first.expects(:source=).with File.join("puppet://foo/bar", @first.relative_path) file.expects(:perform_recursion).returns [@first] file.stubs(:newchild).returns @resource file.recurse_remote({}) end it "should create a new resource for any relative file paths that do not already have a resource" do file.stubs(:perform_recursion).returns [@first] file.expects(:newchild).with("first").returns @resource expect(file.recurse_remote({})).to eq({"first" => @resource}) end it "should not create a new resource for any relative file paths that do already have a resource" do file.stubs(:perform_recursion).returns [@first] file.expects(:newchild).never file.recurse_remote("first" => @resource) end it "should set the source of each resource to the source of the metadata" do file.stubs(:perform_recursion).returns [@first] @resource.stubs(:[]=) @resource.expects(:[]=).with(:source, File.join("puppet://foo/bar", @first.relative_path)) file.recurse_remote("first" => @resource) end # LAK:FIXME This is a bug, but I can't think of a fix for it. Fortunately it's already # filed, and when it's fixed, we'll just fix the whole flow. with_digest_algorithms do it "it should set the checksum type to #{metadata[:digest_algorithm]} if the remote file is a file" do @first.stubs(:ftype).returns "file" file.stubs(:perform_recursion).returns [@first] @resource.stubs(:[]=) @resource.expects(:[]=).with(:checksum, digest_algorithm.intern) file.recurse_remote("first" => @resource) end end it "should store the metadata in the source property for each resource so the source does not have to requery the metadata" do file.stubs(:perform_recursion).returns [@first] @resource.expects(:parameter).with(:source).returns @parameter @parameter.expects(:metadata=).with(@first) file.recurse_remote("first" => @resource) end it "should not create a new resource for the '.' file" do @first.stubs(:relative_path).returns "." file.stubs(:perform_recursion).returns [@first] file.expects(:newchild).never file.recurse_remote({}) end it "should store the metadata in the main file's source property if the relative path is '.'" do @first.stubs(:relative_path).returns "." file.stubs(:perform_recursion).returns [@first] file.parameter(:source).expects(:metadata=).with @first file.recurse_remote("first" => @resource) end describe "and multiple sources are provided" do let(:sources) do h = {} %w{/a /b /c /d}.each do |key| h[key] = URI.unescape(Puppet::Util.path_to_uri(File.expand_path(key)).to_s) end h end describe "and :sourceselect is set to :first" do it "should create file instances for the results for the first source to return any values" do data = Puppet::FileServing::Metadata.new(File.expand_path("/whatever"), :relative_path => "foobar") file[:source] = sources.keys.sort.map { |key| File.expand_path(key) } file.expects(:perform_recursion).with(sources['/a']).returns nil file.expects(:perform_recursion).with(sources['/b']).returns [] file.expects(:perform_recursion).with(sources['/c']).returns [data] file.expects(:perform_recursion).with(sources['/d']).never file.expects(:newchild).with("foobar").returns @resource file.recurse_remote({}) end end describe "and :sourceselect is set to :all" do before do file[:sourceselect] = :all end it "should return every found file that is not in a previous source" do klass = Puppet::FileServing::Metadata file[:source] = abs_path = %w{/a /b /c /d}.map {|f| File.expand_path(f) } file.stubs(:newchild).returns @resource one = [klass.new(abs_path[0], :relative_path => "a")] file.expects(:perform_recursion).with(sources['/a']).returns one file.expects(:newchild).with("a").returns @resource two = [klass.new(abs_path[1], :relative_path => "a"), klass.new(abs_path[1], :relative_path => "b")] file.expects(:perform_recursion).with(sources['/b']).returns two file.expects(:newchild).with("b").returns @resource three = [klass.new(abs_path[2], :relative_path => "a"), klass.new(abs_path[2], :relative_path => "c")] file.expects(:perform_recursion).with(sources['/c']).returns three file.expects(:newchild).with("c").returns @resource file.expects(:perform_recursion).with(sources['/d']).returns [] file.recurse_remote({}) end end end end describe "#perform_recursion" do it "should use Metadata to do its recursion" do Puppet::FileServing::Metadata.indirection.expects(:search) file.perform_recursion(file[:path]) end it "should use the provided path as the key to the search" do Puppet::FileServing::Metadata.indirection.expects(:search).with { |key, options| key == "/foo" } file.perform_recursion("/foo") end it "should return the results of the metadata search" do Puppet::FileServing::Metadata.indirection.expects(:search).returns "foobar" expect(file.perform_recursion(file[:path])).to eq("foobar") end it "should pass its recursion value to the search" do file[:recurse] = true Puppet::FileServing::Metadata.indirection.expects(:search).with { |key, options| options[:recurse] == true } file.perform_recursion(file[:path]) end it "should pass true if recursion is remote" do file[:recurse] = :remote Puppet::FileServing::Metadata.indirection.expects(:search).with { |key, options| options[:recurse] == true } file.perform_recursion(file[:path]) end it "should pass its recursion limit value to the search" do file[:recurselimit] = 10 Puppet::FileServing::Metadata.indirection.expects(:search).with { |key, options| options[:recurselimit] == 10 } file.perform_recursion(file[:path]) end it "should configure the search to ignore or manage links" do file[:links] = :manage Puppet::FileServing::Metadata.indirection.expects(:search).with { |key, options| options[:links] == :manage } file.perform_recursion(file[:path]) end it "should pass its 'ignore' setting to the search if it has one" do file[:ignore] = %w{.svn CVS} Puppet::FileServing::Metadata.indirection.expects(:search).with { |key, options| options[:ignore] == %w{.svn CVS} } file.perform_recursion(file[:path]) end end describe "#remove_existing" do it "should do nothing if the file doesn't exist" do expect(file.remove_existing(:file)).to eq(false) end it "should fail if it can't backup the file" do file.stubs(:stat).returns stub('stat', :ftype => 'file') file.stubs(:perform_backup).returns false expect { file.remove_existing(:file) }.to raise_error(Puppet::Error, /Could not back up; will not replace/) end describe "backing up directories" do it "should not backup directories if force is false" do file[:force] = false file.stubs(:stat).returns stub('stat', :ftype => 'directory') file.expects(:perform_backup).never expect(file.remove_existing(:file)).to eq(false) end it "should backup directories if force is true" do file[:force] = true FileUtils.expects(:rmtree).with(file[:path]) file.stubs(:stat).returns stub('stat', :ftype => 'directory') file.expects(:perform_backup).once.returns(true) expect(file.remove_existing(:file)).to eq(true) end end it "should not do anything if the file is already the right type and not a link" do file.stubs(:stat).returns stub('stat', :ftype => 'file') expect(file.remove_existing(:file)).to eq(false) end it "should not remove directories and should not invalidate the stat unless force is set" do # Actually call stat to set @needs_stat to nil file.stat file.stubs(:stat).returns stub('stat', :ftype => 'directory') file.remove_existing(:file) expect(file.instance_variable_get(:@stat)).to eq(nil) expect(@logs).to be_any {|log| log.level == :notice and log.message =~ /Not removing directory; use 'force' to override/} end it "should remove a directory if force is set" do file[:force] = true file.stubs(:stat).returns stub('stat', :ftype => 'directory') FileUtils.expects(:rmtree).with(file[:path]) expect(file.remove_existing(:file)).to eq(true) end it "should remove an existing file" do file.stubs(:perform_backup).returns true FileUtils.touch(path) expect(file.remove_existing(:directory)).to eq(true) expect(Puppet::FileSystem.exist?(file[:path])).to eq(false) end it "should remove an existing link", :if => described_class.defaultprovider.feature?(:manages_symlinks) do file.stubs(:perform_backup).returns true target = tmpfile('link_target') FileUtils.touch(target) Puppet::FileSystem.symlink(target, path) file[:target] = target expect(file.remove_existing(:directory)).to eq(true) expect(Puppet::FileSystem.exist?(file[:path])).to eq(false) end it "should fail if the file is not a file, link, or directory" do file.stubs(:stat).returns stub('stat', :ftype => 'socket') expect { file.remove_existing(:file) }.to raise_error(Puppet::Error, /Could not back up files of type socket/) end it "should invalidate the existing stat of the file" do # Actually call stat to set @needs_stat to nil file.stat file.stubs(:stat).returns stub('stat', :ftype => 'file') Puppet::FileSystem.stubs(:unlink) expect(file.remove_existing(:directory)).to eq(true) expect(file.instance_variable_get(:@stat)).to eq(:needs_stat) end end describe "#retrieve" do it "should copy the source values if the 'source' parameter is set" do file[:source] = File.expand_path('/foo/bar') file.parameter(:source).expects(:copy_source_values) file.retrieve end end describe "#should_be_file?" do it "should have a method for determining if the file should be a normal file" do expect(file).to respond_to(:should_be_file?) end it "should be a file if :ensure is set to :file" do file[:ensure] = :file expect(file).to be_should_be_file end it "should be a file if :ensure is set to :present and the file exists as a normal file" do file.stubs(:stat).returns(mock('stat', :ftype => "file")) file[:ensure] = :present expect(file).to be_should_be_file end it "should not be a file if :ensure is set to something other than :file" do file[:ensure] = :directory expect(file).to_not be_should_be_file end it "should not be a file if :ensure is set to :present and the file exists but is not a normal file" do file.stubs(:stat).returns(mock('stat', :ftype => "directory")) file[:ensure] = :present expect(file).to_not be_should_be_file end it "should be a file if :ensure is not set and :content is" do file[:content] = "foo" expect(file).to be_should_be_file end it "should be a file if neither :ensure nor :content is set but the file exists as a normal file" do file.stubs(:stat).returns(mock("stat", :ftype => "file")) expect(file).to be_should_be_file end it "should not be a file if neither :ensure nor :content is set but the file exists but not as a normal file" do file.stubs(:stat).returns(mock("stat", :ftype => "directory")) expect(file).to_not be_should_be_file end end describe "#stat", :if => described_class.defaultprovider.feature?(:manages_symlinks) do before do target = tmpfile('link_target') FileUtils.touch(target) Puppet::FileSystem.symlink(target, path) file[:target] = target file[:links] = :manage # so we always use :lstat end it "should stat the target if it is following links" do file[:links] = :follow expect(file.stat.ftype).to eq('file') end it "should stat the link if is it not following links" do file[:links] = :manage expect(file.stat.ftype).to eq('link') end it "should return nil if the file does not exist" do file[:path] = make_absolute('/foo/bar/baz/non-existent') expect(file.stat).to be_nil end it "should return nil if the file cannot be stat'ed" do dir = tmpfile('link_test_dir') child = File.join(dir, 'some_file') Dir.mkdir(dir) File.chmod(0, dir) file[:path] = child expect(file.stat).to be_nil # chmod it back so we can clean it up File.chmod(0777, dir) end it "should return nil if parts of path are no directories" do regular_file = tmpfile('ENOTDIR_test') FileUtils.touch(regular_file) impossible_child = File.join(regular_file, 'some_file') file[:path] = impossible_child expect(file.stat).to be_nil end it "should return the stat instance" do expect(file.stat).to be_a(File::Stat) end it "should cache the stat instance" do expect(file.stat).to equal(file.stat) end end describe "#write" do describe "when validating the checksum" do before { file.stubs(:validate_checksum?).returns(true) } it "should fail if the checksum parameter and content checksums do not match" do checksum = stub('checksum_parameter', :sum => 'checksum_b', :sum_file => 'checksum_b') file.stubs(:parameter).with(:checksum).returns(checksum) property = stub('content_property', :actual_content => "something", :length => "something".length, :write => 'checksum_a') file.stubs(:property).with(:content).returns(property) expect { file.write :NOTUSED }.to raise_error(Puppet::Error) end end describe "when not validating the checksum" do before { file.stubs(:validate_checksum?).returns(false) } it "should not fail if the checksum property and content checksums do not match" do checksum = stub('checksum_parameter', :sum => 'checksum_b') file.stubs(:parameter).with(:checksum).returns(checksum) property = stub('content_property', :actual_content => "something", :length => "something".length, :write => 'checksum_a') file.stubs(:property).with(:content).returns(property) expect { file.write :NOTUSED }.to_not raise_error end end describe "when resource mode is supplied" do before { file.stubs(:property_fix) } context "and writing temporary files" do before { file.stubs(:write_temporary_file?).returns(true) } it "should convert symbolic mode to int" do file[:mode] = 'oga=r' Puppet::Util.expects(:replace_file).with(file[:path], 0444) file.write :NOTUSED end it "should support int modes" do file[:mode] = '0444' Puppet::Util.expects(:replace_file).with(file[:path], 0444) file.write :NOTUSED end end context "and not writing temporary files" do before { file.stubs(:write_temporary_file?).returns(false) } it "should set a umask of 0" do file[:mode] = 'oga=r' Puppet::Util.expects(:withumask).with(0) file.write :NOTUSED end it "should convert symbolic mode to int" do file[:mode] = 'oga=r' File.expects(:open).with(file[:path], anything, 0444) file.write :NOTUSED end it "should support int modes" do file[:mode] = '0444' File.expects(:open).with(file[:path], anything, 0444) file.write :NOTUSED end end end describe "when resource mode is not supplied" do context "and content is supplied" do it "should default to 0644 mode" do file = described_class.new(:path => path, :content => "file content") file.write :NOTUSED expect(File.stat(file[:path]).mode & 0777).to eq(0644) end end context "and no content is supplied" do it "should use puppet's default umask of 022" do file = described_class.new(:path => path) umask_from_the_user = 0777 Puppet::Util.withumask(umask_from_the_user) do file.write :NOTUSED end expect(File.stat(file[:path]).mode & 0777).to eq(0644) end end end end describe "#fail_if_checksum_is_wrong" do it "should fail if the checksum of the file doesn't match the expected one" do expect do file.instance_eval do parameter(:checksum).stubs(:sum_file).returns('wrong!!') fail_if_checksum_is_wrong(self[:path], 'anything!') end end.to raise_error(Puppet::Error, /File written to disk did not match checksum/) end it "should not fail if the checksum is correct" do expect do file.instance_eval do parameter(:checksum).stubs(:sum_file).returns('anything!') fail_if_checksum_is_wrong(self[:path], 'anything!') end end.not_to raise_error end it "should not fail if the checksum is absent" do expect do file.instance_eval do parameter(:checksum).stubs(:sum_file).returns(nil) fail_if_checksum_is_wrong(self[:path], 'anything!') end end.not_to raise_error end end describe "#write_content" do it "should delegate writing the file to the content property" do io = stub('io') file[:content] = "some content here" file.property(:content).expects(:write).with(io) file.send(:write_content, io) end end describe "#write_temporary_file?" do it "should be true if the file has specified content" do file[:content] = 'some content' expect(file.send(:write_temporary_file?)).to be_truthy end it "should be true if the file has specified source" do file[:source] = File.expand_path('/tmp/foo') expect(file.send(:write_temporary_file?)).to be_truthy end it "should be false if the file has neither content nor source" do expect(file.send(:write_temporary_file?)).to be_falsey end end describe "#property_fix" do { :mode => '0777', :owner => 'joeuser', :group => 'joeusers', :seluser => 'seluser', :selrole => 'selrole', :seltype => 'seltype', :selrange => 'selrange' }.each do |name,value| it "should sync the #{name} property if it's not in sync" do file[name] = value prop = file.property(name) prop.expects(:retrieve) prop.expects(:safe_insync?).returns false prop.expects(:sync) file.send(:property_fix) end end end describe "when autorequiring" do describe "target" do it "should require file resource when specified with the target property", :if => described_class.defaultprovider.feature?(:manages_symlinks) do file = described_class.new(:path => File.expand_path("/foo"), :ensure => :directory) link = described_class.new(:path => File.expand_path("/bar"), :ensure => :link, :target => File.expand_path("/foo")) catalog.add_resource file catalog.add_resource link reqs = link.autorequire expect(reqs.size).to eq(1) expect(reqs[0].source).to eq(file) expect(reqs[0].target).to eq(link) end it "should require file resource when specified with the ensure property" do file = described_class.new(:path => File.expand_path("/foo"), :ensure => :directory) link = described_class.new(:path => File.expand_path("/bar"), :ensure => File.expand_path("/foo")) catalog.add_resource file catalog.add_resource link reqs = link.autorequire expect(reqs.size).to eq(1) expect(reqs[0].source).to eq(file) expect(reqs[0].target).to eq(link) end it "should not require target if target is not managed", :if => described_class.defaultprovider.feature?(:manages_symlinks) do link = described_class.new(:path => File.expand_path('/foo'), :ensure => :link, :target => '/bar') catalog.add_resource link expect(link.autorequire.size).to eq(0) end end describe "directories" do it "should autorequire its parent directory" do dir = described_class.new(:path => File.dirname(path)) catalog.add_resource file catalog.add_resource dir reqs = file.autorequire expect(reqs[0].source).to eq(dir) expect(reqs[0].target).to eq(file) end it "should autorequire its nearest ancestor directory" do dir = described_class.new(:path => File.dirname(path)) grandparent = described_class.new(:path => File.dirname(File.dirname(path))) catalog.add_resource file catalog.add_resource dir catalog.add_resource grandparent reqs = file.autorequire expect(reqs.length).to eq(1) expect(reqs[0].source).to eq(dir) expect(reqs[0].target).to eq(file) end it "should not autorequire anything when there is no nearest ancestor directory" do catalog.add_resource file expect(file.autorequire).to be_empty end it "should not autorequire its parent dir if its parent dir is itself" do file[:path] = File.expand_path('/') catalog.add_resource file expect(file.autorequire).to be_empty end describe "on Windows systems", :if => Puppet.features.microsoft_windows? do describe "when using UNC filenames" do it "should autorequire its parent directory" do file[:path] = '//localhost/foo/bar/baz' dir = described_class.new(:path => "//localhost/foo/bar") catalog.add_resource file catalog.add_resource dir reqs = file.autorequire expect(reqs[0].source).to eq(dir) expect(reqs[0].target).to eq(file) end it "should autorequire its nearest ancestor directory" do file = described_class.new(:path => "//localhost/foo/bar/baz/qux") dir = described_class.new(:path => "//localhost/foo/bar/baz") grandparent = described_class.new(:path => "//localhost/foo/bar") catalog.add_resource file catalog.add_resource dir catalog.add_resource grandparent reqs = file.autorequire expect(reqs.length).to eq(1) expect(reqs[0].source).to eq(dir) expect(reqs[0].target).to eq(file) end it "should not autorequire anything when there is no nearest ancestor directory" do file = described_class.new(:path => "//localhost/foo/bar/baz/qux") catalog.add_resource file expect(file.autorequire).to be_empty end it "should not autorequire its parent dir if its parent dir is itself" do file = described_class.new(:path => "//localhost/foo") catalog.add_resource file puts file.autorequire expect(file.autorequire).to be_empty end end end end end describe "when managing links", :if => Puppet.features.manages_symlinks? do require 'tempfile' before :each do Dir.mkdir(path) @target = File.join(path, "target") @link = File.join(path, "link") target = described_class.new( :ensure => :file, :path => @target, :catalog => catalog, :content => 'yayness', :mode => '0644') catalog.add_resource target @link_resource = described_class.new( :ensure => :link, :path => @link, :target => @target, :catalog => catalog, :mode => '0755') catalog.add_resource @link_resource # to prevent the catalog from trying to write state.yaml Puppet::Util::Storage.stubs(:store) end it "should preserve the original file mode and ignore the one set by the link" do @link_resource[:links] = :manage # default catalog.apply # I convert them to strings so they display correctly if there's an error. expect((Puppet::FileSystem.stat(@target).mode & 007777).to_s(8)).to eq('644') end it "should manage the mode of the followed link" do - pending("Windows cannot presently manage the mode when following symlinks", - :if => Puppet.features.microsoft_windows?) do + if Puppet.features.microsoft_windows? + skip "Windows cannot presently manage the mode when following symlinks" + else @link_resource[:links] = :follow catalog.apply - (Puppet::FileSystem.stat(@target).mode & 007777).to_s(8).should == '755' + expect((Puppet::FileSystem.stat(@target).mode & 007777).to_s(8)).to eq('755') end end end describe "when using source" do before do file[:source] = File.expand_path('/one') end Puppet::Type::File::ParameterChecksum.value_collection.values.reject {|v| v == :none}.each do |checksum_type| describe "with checksum '#{checksum_type}'" do before do file[:checksum] = checksum_type end it 'should validate' do expect { file.validate }.to_not raise_error end end end describe "with checksum 'none'" do before do file[:checksum] = :none end it 'should raise an exception when validating' do expect { file.validate }.to raise_error(/You cannot specify source when using checksum 'none'/) end end end describe "when using content" do before do file[:content] = 'file contents' end (Puppet::Type::File::ParameterChecksum.value_collection.values - SOURCE_ONLY_CHECKSUMS).each do |checksum_type| describe "with checksum '#{checksum_type}'" do before do file[:checksum] = checksum_type end it 'should validate' do expect { file.validate }.to_not raise_error end end end SOURCE_ONLY_CHECKSUMS.each do |checksum_type| describe "with checksum '#{checksum_type}'" do it 'should raise an exception when validating' do file[:checksum] = checksum_type expect { file.validate }.to raise_error(/You cannot specify content when using checksum '#{checksum_type}'/) end end end end describe "when auditing" do before :each do # to prevent the catalog from trying to write state.yaml Puppet::Util::Storage.stubs(:store) end it "should not fail if creating a new file if group is not set" do file = described_class.new(:path => path, :audit => 'all', :content => 'content') catalog.add_resource(file) report = catalog.apply.report expect(report.resource_statuses["File[#{path}]"]).not_to be_failed expect(File.read(path)).to eq('content') end it "should not log errors if creating a new file with ensure present and no content" do file[:audit] = 'content' file[:ensure] = 'present' catalog.add_resource(file) catalog.apply expect(Puppet::FileSystem.exist?(path)).to be_truthy expect(@logs).not_to be_any {|l| l.level != :notice } end end describe "when specifying both source and checksum" do it 'should use the specified checksum when source is first' do file[:source] = File.expand_path('/foo') file[:checksum] = :md5lite expect(file[:checksum]).to eq(:md5lite) end it 'should use the specified checksum when source is last' do file[:checksum] = :md5lite file[:source] = File.expand_path('/foo') expect(file[:checksum]).to eq(:md5lite) end end describe "when validating" do [[:source, :target], [:source, :content], [:target, :content]].each do |prop1,prop2| it "should fail if both #{prop1} and #{prop2} are specified" do file[prop1] = prop1 == :source ? File.expand_path("prop1 value") : "prop1 value" file[prop2] = "prop2 value" expect do file.validate end.to raise_error(Puppet::Error, /You cannot specify more than one of/) end end end end