diff --git a/lib/puppet/util/windows/access_control_list.rb b/lib/puppet/util/windows/access_control_list.rb index 88e635ea2..ce0e8e6bd 100644 --- a/lib/puppet/util/windows/access_control_list.rb +++ b/lib/puppet/util/windows/access_control_list.rb @@ -1,113 +1,113 @@ # Windows Access Control List # # Represents a list of access control entries (ACEs). # # @see http://msdn.microsoft.com/en-us/library/windows/desktop/aa374872(v=vs.85).aspx # @api private class Puppet::Util::Windows::AccessControlList include Enumerable ACCESS_ALLOWED_ACE_TYPE = 0x0 ACCESS_DENIED_ACE_TYPE = 0x1 # Construct an ACL. # # @param acl [Enumerable] A list of aces to copy from. def initialize(acl = nil) if acl @aces = acl.map(&:dup) else @aces = [] end end # Enumerate each ACE in the list. # # @yieldparam ace [Hash] the ace def each @aces.each {|ace| yield ace} end # Allow the +sid+ to access a resource with the specified access +mask+. # # @param sid [String] The SID that the ACE is granting access to # @param mask [int] The access mask granted to the SID # @param flags [int] The flags assigned to the ACE, e.g. +INHERIT_ONLY_ACE+ def allow(sid, mask, flags = 0) @aces << Puppet::Util::Windows::AccessControlEntry.new(sid, mask, flags, ACCESS_ALLOWED_ACE_TYPE) end # Deny the +sid+ access to a resource with the specified access +mask+. # # @param sid [String] The SID that the ACE is denying access to # @param mask [int] The access mask denied to the SID # @param flags [int] The flags assigned to the ACE, e.g. +INHERIT_ONLY_ACE+ def deny(sid, mask, flags = 0) @aces << Puppet::Util::Windows::AccessControlEntry.new(sid, mask, flags, ACCESS_DENIED_ACE_TYPE) end # Reassign all ACEs currently assigned to +old_sid+ to +new_sid+ instead. # If an ACE is inherited or is not assigned to +old_sid+, then it will # be copied as-is to the new ACL, preserving its order within the ACL. # # @param old_sid [String] The old SID, e.g. 'S-1-5-18' # @param new_sid [String] The new SID # @return [AccessControlList] The copied ACL. def reassign!(old_sid, new_sid) new_aces = [] prepend_needed = false aces_to_prepend = [] @aces.each do |ace| new_ace = ace.dup if ace.sid == old_sid if ace.inherited? # create an explicit ACE granting or denying the # new_sid the rights that the inherited ACE # granted or denied the old_sid. We mask off all # flags except those affecting inheritance of the # ACE we're creating. - inherit_mask = Windows::Security::CONTAINER_INHERIT_ACE | - Windows::Security::OBJECT_INHERIT_ACE | - Windows::Security::INHERIT_ONLY_ACE + inherit_mask = Puppet::Util::Windows::AccessControlEntry::CONTAINER_INHERIT_ACE | + Puppet::Util::Windows::AccessControlEntry::OBJECT_INHERIT_ACE | + Puppet::Util::Windows::AccessControlEntry::INHERIT_ONLY_ACE explicit_ace = Puppet::Util::Windows::AccessControlEntry.new(new_sid, ace.mask, ace.flags & inherit_mask, ace.type) aces_to_prepend << explicit_ace else new_ace.sid = new_sid prepend_needed = old_sid == Win32::Security::SID::LocalSystem end end new_aces << new_ace end @aces = [] if prepend_needed - mask = Windows::Security::STANDARD_RIGHTS_ALL | Windows::Security::SPECIFIC_RIGHTS_ALL + mask = Puppet::Util::Windows::File::STANDARD_RIGHTS_ALL | Puppet::Util::Windows::File::SPECIFIC_RIGHTS_ALL ace = Puppet::Util::Windows::AccessControlEntry.new( Win32::Security::SID::LocalSystem, mask) @aces << ace end @aces.concat(aces_to_prepend) @aces.concat(new_aces) end def inspect str = "" @aces.each do |ace| str << " #{ace.inspect}\n" end str end def ==(other) self.class == other.class && self.to_a == other.to_a end alias eql? == end diff --git a/lib/puppet/util/windows/error.rb b/lib/puppet/util/windows/error.rb index 913258624..dbfb19a75 100644 --- a/lib/puppet/util/windows/error.rb +++ b/lib/puppet/util/windows/error.rb @@ -1,87 +1,90 @@ require 'puppet/util/windows' # represents an error resulting from a Win32 error code class Puppet::Util::Windows::Error < Puppet::Error require 'ffi' extend FFI::Library attr_reader :code def initialize(message, code = @@GetLastError.call(), original = nil) super(message + ": #{self.class.format_error_code(code)}", original) @code = code end # Helper method that wraps FormatMessage that returns a human readable string. def self.format_error_code(code) # specifying 0 will look for LANGID in the following order # 1.Language neutral # 2.Thread LANGID, based on the thread's locale value # 3.User default LANGID, based on the user's default locale value # 4.System default LANGID, based on the system default locale value # 5.US English dwLanguageId = 0 flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK error_string = '' # this pointer actually points to a :lpwstr (pointer) since we're letting Windows allocate for us FFI::MemoryPointer.new(:pointer, 1) do |buffer_ptr| length = FormatMessageW(flags, FFI::Pointer::NULL, code, dwLanguageId, buffer_ptr, 0, FFI::Pointer::NULL) if length == FFI::WIN32_FALSE # can't raise same error type here or potentially recurse infinitely raise Puppet::Error.new("FormatMessageW could not format code #{code}") end # returns an FFI::Pointer with autorelease set to false, which is what we want buffer_ptr.read_win32_local_pointer do |wide_string_ptr| if wide_string_ptr.null? raise Puppet::Error.new("FormatMessageW failed to allocate buffer for code #{code}") end error_string = wide_string_ptr.read_wide_string(length) end end error_string end + ERROR_FILE_NOT_FOUND = 2 + ERROR_ACCESS_DENIED = 5 + FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100 FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200 FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000 FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000 FORMAT_MESSAGE_MAX_WIDTH_MASK = 0x000000FF ffi_convention :stdcall # NOTE: It seems like FFI.errno is already implemented as GetLastError... or is it? # http://msdn.microsoft.com/en-us/library/windows/desktop/ms679360(v=vs.85).aspx # DWORD WINAPI GetLastError(void); # HACK: unfortunately using FFI.errno or attach_function to hook GetLastError in # FFI like the following will not work. Something internal to FFI appears to # be stomping out the value of GetLastError when calling via FFI. # attach_function_private :GetLastError, [], :dword require 'Win32API' @@GetLastError = Win32API.new('kernel32', 'GetLastError', [], 'L') # http://msdn.microsoft.com/en-us/library/windows/desktop/ms679351(v=vs.85).aspx # DWORD WINAPI FormatMessage( # _In_ DWORD dwFlags, # _In_opt_ LPCVOID lpSource, # _In_ DWORD dwMessageId, # _In_ DWORD dwLanguageId, # _Out_ LPTSTR lpBuffer, # _In_ DWORD nSize, # _In_opt_ va_list *Arguments # ); # NOTE: since we're not preallocating the buffer, use a :pointer for lpBuffer ffi_lib :kernel32 attach_function_private :FormatMessageW, [:dword, :lpcvoid, :dword, :dword, :pointer, :dword, :pointer], :dword end diff --git a/spec/integration/type/file_spec.rb b/spec/integration/type/file_spec.rb index 16f44a4b0..e63349406 100755 --- a/spec/integration/type/file_spec.rb +++ b/spec/integration/type/file_spec.rb @@ -1,1377 +1,1378 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' if Puppet.features.microsoft_windows? require 'puppet/util/windows' class WindowsSecurity extend Puppet::Util::Windows::Security end end describe Puppet::Type.type(:file), :uses_checksums => true do include PuppetSpec::Files let(:catalog) { Puppet::Resource::Catalog.new } let(:path) do # we create a directory first so backups of :path that are stored in # the same directory will also be removed after the tests parent = tmpdir('file_spec') File.join(parent, 'file_testing') end let(:dir) do # we create a directory first so backups of :path that are stored in # the same directory will also be removed after the tests parent = tmpdir('file_spec') File.join(parent, 'dir_testing') end if Puppet.features.posix? def set_mode(mode, file) File.chmod(mode, file) end def get_mode(file) Puppet::FileSystem.lstat(file).mode end def get_owner(file) Puppet::FileSystem.lstat(file).uid end def get_group(file) Puppet::FileSystem.lstat(file).gid end else class SecurityHelper extend Puppet::Util::Windows::Security end def set_mode(mode, file) SecurityHelper.set_mode(mode, file) end def get_mode(file) SecurityHelper.get_mode(file) end def get_owner(file) SecurityHelper.get_owner(file) end def get_group(file) SecurityHelper.get_group(file) end def get_aces_for_path_by_sid(path, sid) SecurityHelper.get_aces_for_path_by_sid(path, sid) end end before do # stub this to not try to create state.yaml Puppet::Util::Storage.stubs(:store) end 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 status = catalog.apply.report.resource_statuses["File[#{source}]"] status.should_not be_failed status.should_not be_changed Puppet::FileSystem.exist?(source).should be_false end describe "when ensure is absent" do it "should remove the file if present" do FileUtils.touch(path) catalog.add_resource(described_class.new(:path => path, :ensure => :absent, :backup => :false)) report = catalog.apply.report report.resource_statuses["File[#{path}]"].should_not be_failed Puppet::FileSystem.exist?(path).should be_false end it "should do nothing if file is not present" do catalog.add_resource(described_class.new(:path => path, :ensure => :absent, :backup => :false)) report = catalog.apply.report report.resource_statuses["File[#{path}]"].should_not be_failed Puppet::FileSystem.exist?(path).should be_false end # issue #14599 it "should not fail if parts of path aren't directories" do FileUtils.touch(path) catalog.add_resource(described_class.new(:path => File.join(path,'no_such_file'), :ensure => :absent, :backup => :false)) report = catalog.apply.report report.resource_statuses["File[#{File.join(path,'no_such_file')}]"].should_not be_failed end end describe "when setting permissions" do it "should set the owner" do target = tmpfile_with_contents('target', '') owner = get_owner(target) catalog.add_resource described_class.new( :name => target, :owner => owner ) catalog.apply get_owner(target).should == owner end it "should set the group" do target = tmpfile_with_contents('target', '') group = get_group(target) catalog.add_resource described_class.new( :name => target, :group => group ) catalog.apply get_group(target).should == group end describe "when setting mode" do describe "for directories" do let(:target) { tmpdir('dir_mode') } it "should set executable bits for newly created directories" do catalog.add_resource described_class.new(:path => target, :ensure => :directory, :mode => 0600) catalog.apply (get_mode(target) & 07777).should == 0700 end 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.apply (get_mode(target) & 07777).should == 0755 end it "should not set executable bits for unreadable directories" do begin catalog.add_resource described_class.new(:path => target, :ensure => :directory, :mode => 0300) catalog.apply (get_mode(target) & 07777).should == 0300 ensure # so we can cleanup set_mode(0700, target) end end it "should set user, group, and other executable bits" do catalog.add_resource described_class.new(:path => target, :ensure => :directory, :mode => 0664) catalog.apply (get_mode(target) & 07777).should == 0775 end it "should set executable bits when overwriting a non-executable file" do target_path = tmpfile_with_contents('executable', '') set_mode(0444, target_path) catalog.add_resource described_class.new(:path => target_path, :ensure => :directory, :mode => 0666, :backup => false) catalog.apply (get_mode(target_path) & 07777).should == 0777 File.should be_directory(target_path) end end describe "for files" do it "should not set executable bits" do catalog.add_resource described_class.new(:path => path, :ensure => :file, :mode => 0666) catalog.apply (get_mode(path) & 07777).should == 0666 end it "should not set executable bits when replacing an executable directory (#10365)" do pending("bug #10365") FileUtils.mkdir(path) set_mode(0777, path) catalog.add_resource described_class.new(:path => path, :ensure => :file, :mode => 0666, :backup => false, :force => true) catalog.apply (get_mode(path) & 07777).should == 0666 end end describe "for links", :if => described_class.defaultprovider.feature?(:manages_symlinks) do let(:link) { tmpfile('link_mode') } describe "when managing links" do let(:link_target) { tmpfile('target') } before :each do FileUtils.touch(link_target) File.chmod(0444, link_target) Puppet::FileSystem.symlink(link_target, link) end it "should not set the executable bit on the link nor the target" do catalog.add_resource described_class.new(:path => link, :ensure => :link, :mode => 0666, :target => link_target, :links => :manage) catalog.apply (Puppet::FileSystem.stat(link).mode & 07777) == 0666 (Puppet::FileSystem.lstat(link_target).mode & 07777) == 0444 end it "should ignore dangling symlinks (#6856)" do File.delete(link_target) catalog.add_resource described_class.new(:path => link, :ensure => :link, :mode => 0666, :target => link_target, :links => :manage) catalog.apply Puppet::FileSystem.exist?(link).should be_false end it "should create a link to the target if ensure is omitted" do FileUtils.touch(link_target) catalog.add_resource described_class.new(:path => link, :target => link_target) catalog.apply Puppet::FileSystem.exist?(link).should be_true Puppet::FileSystem.lstat(link).ftype.should == 'link' Puppet::FileSystem.readlink(link).should == link_target end end describe "when following links" do it "should ignore dangling symlinks (#6856)" do target = tmpfile('dangling') FileUtils.touch(target) Puppet::FileSystem.symlink(target, link) File.delete(target) catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0600, :links => :follow) catalog.apply end describe "to a directory" do let(:link_target) { tmpdir('dir_target') } before :each do File.chmod(0600, link_target) Puppet::FileSystem.symlink(link_target, link) end after :each do File.chmod(0750, link_target) end describe "that is readable" do it "should set the executable bits when creating the destination (#10315)" do catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0666, :links => :follow) catalog.apply File.should be_directory(path) (get_mode(path) & 07777).should == 0777 end it "should set the executable bits when overwriting the destination (#10315)" do FileUtils.touch(path) catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0666, :links => :follow, :backup => false) catalog.apply File.should be_directory(path) (get_mode(path) & 07777).should == 0777 end end describe "that is not readable" do before :each do set_mode(0300, link_target) end # so we can cleanup after :each do set_mode(0700, link_target) end it "should set executable bits when creating the destination (#10315)" do catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0666, :links => :follow) catalog.apply File.should be_directory(path) (get_mode(path) & 07777).should == 0777 end it "should set executable bits when overwriting the destination" do FileUtils.touch(path) catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0666, :links => :follow, :backup => false) catalog.apply File.should be_directory(path) (get_mode(path) & 07777).should == 0777 end end end describe "to a file" do let(:link_target) { tmpfile('file_target') } before :each do FileUtils.touch(link_target) Puppet::FileSystem.symlink(link_target, link) end it "should create the file, not a symlink (#2817, #10315)" do catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0600, :links => :follow) catalog.apply File.should be_file(path) (get_mode(path) & 07777).should == 0600 end it "should overwrite the file" do FileUtils.touch(path) catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0600, :links => :follow) catalog.apply File.should be_file(path) (get_mode(path) & 07777).should == 0600 end end describe "to a link to a directory" do let(:real_target) { tmpdir('real_target') } let(:target) { tmpfile('target') } before :each do File.chmod(0666, real_target) # link -> target -> real_target Puppet::FileSystem.symlink(real_target, target) Puppet::FileSystem.symlink(target, link) end after :each do File.chmod(0750, real_target) end describe "when following all links" do it "should create the destination and apply executable bits (#10315)" do catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0600, :links => :follow) catalog.apply File.should be_directory(path) (get_mode(path) & 07777).should == 0700 end it "should overwrite the destination and apply executable bits" do FileUtils.mkdir(path) catalog.add_resource described_class.new(:path => path, :source => link, :mode => 0600, :links => :follow) catalog.apply File.should be_directory(path) (get_mode(path) & 0111).should == 0100 end end end end end end end describe "when writing files" do with_digest_algorithms do it "should backup files to a filebucket when one is configured" do filebucket = Puppet::Type.type(:filebucket).new :path => tmpfile("filebucket"), :name => "mybucket" file = described_class.new :path => path, :backup => "mybucket", :content => "foo" catalog.add_resource file catalog.add_resource filebucket File.open(file[:path], "w") { |f| f.write("bar") } d = digest(IO.binread(file[:path])) catalog.apply filebucket.bucket.getfile(d).should == "bar" end it "should backup files in the local directory when a backup string is provided" do file = described_class.new :path => path, :backup => ".bak", :content => "foo" catalog.add_resource file File.open(file[:path], "w") { |f| f.puts "bar" } catalog.apply backup = file[:path] + ".bak" Puppet::FileSystem.exist?(backup).should be_true File.read(backup).should == "bar\n" end it "should fail if no backup can be performed" do dir = tmpdir("backups") file = described_class.new :path => File.join(dir, "testfile"), :backup => ".bak", :content => "foo" catalog.add_resource file File.open(file[:path], 'w') { |f| f.puts "bar" } # Create a directory where the backup should be so that writing to it fails Dir.mkdir(File.join(dir, "testfile.bak")) Puppet::Util::Log.stubs(:newmessage) catalog.apply File.read(file[:path]).should == "bar\n" end it "should not backup symlinks", :if => described_class.defaultprovider.feature?(:manages_symlinks) do link = tmpfile("link") dest1 = tmpfile("dest1") dest2 = tmpfile("dest2") bucket = Puppet::Type.type(:filebucket).new :path => tmpfile("filebucket"), :name => "mybucket" file = described_class.new :path => link, :target => dest2, :ensure => :link, :backup => "mybucket" catalog.add_resource file catalog.add_resource bucket File.open(dest1, "w") { |f| f.puts "whatever" } Puppet::FileSystem.symlink(dest1, link) d = digest(File.read(file[:path])) catalog.apply Puppet::FileSystem.readlink(link).should == dest2 Puppet::FileSystem.exist?(bucket[:path]).should be_false end it "should backup directories to the local filesystem by copying the whole directory" do file = described_class.new :path => path, :backup => ".bak", :content => "foo", :force => true catalog.add_resource file Dir.mkdir(path) otherfile = File.join(path, "foo") File.open(otherfile, "w") { |f| f.print "yay" } catalog.apply backup = "#{path}.bak" FileTest.should be_directory(backup) File.read(File.join(backup, "foo")).should == "yay" end it "should backup directories to filebuckets by backing up each file separately" do bucket = Puppet::Type.type(:filebucket).new :path => tmpfile("filebucket"), :name => "mybucket" file = described_class.new :path => tmpfile("bucket_backs"), :backup => "mybucket", :content => "foo", :force => true catalog.add_resource file catalog.add_resource bucket Dir.mkdir(file[:path]) foofile = File.join(file[:path], "foo") barfile = File.join(file[:path], "bar") File.open(foofile, "w") { |f| f.print "fooyay" } File.open(barfile, "w") { |f| f.print "baryay" } food = digest(File.read(foofile)) bard = digest(File.read(barfile)) catalog.apply bucket.bucket.getfile(food).should == "fooyay" bucket.bucket.getfile(bard).should == "baryay" end end end describe "when recursing" do def build_path(dir) Dir.mkdir(dir) File.chmod(0750, dir) @dirs = [dir] @files = [] %w{one two}.each do |subdir| fdir = File.join(dir, subdir) Dir.mkdir(fdir) File.chmod(0750, fdir) @dirs << fdir %w{three}.each do |file| ffile = File.join(fdir, file) @files << ffile File.open(ffile, "w") { |f| f.puts "test #{file}" } File.chmod(0640, ffile) end end end it "should be able to recurse over a nonexistent file" do @file = described_class.new( :name => path, :mode => 0644, :recurse => true, :backup => false ) catalog.add_resource @file lambda { @file.eval_generate }.should_not raise_error end it "should be able to recursively set properties on existing files" do path = tmpfile("file_integration_tests") build_path(path) file = described_class.new( :name => path, :mode => 0644, :recurse => true, :backup => false ) catalog.add_resource file catalog.apply @dirs.should_not be_empty @dirs.each do |path| (get_mode(path) & 007777).should == 0755 end @files.should_not be_empty @files.each do |path| (get_mode(path) & 007777).should == 0644 end end it "should be able to recursively make links to other files", :if => described_class.defaultprovider.feature?(:manages_symlinks) do source = tmpfile("file_link_integration_source") build_path(source) dest = tmpfile("file_link_integration_dest") @file = described_class.new(:name => dest, :target => source, :recurse => true, :ensure => :link, :backup => false) catalog.add_resource @file catalog.apply @dirs.each do |path| link_path = path.sub(source, dest) Puppet::FileSystem.lstat(link_path).should be_directory end @files.each do |path| link_path = path.sub(source, dest) Puppet::FileSystem.lstat(link_path).ftype.should == "link" end end it "should be able to recursively copy files" do source = tmpfile("file_source_integration_source") build_path(source) dest = tmpfile("file_source_integration_dest") @file = described_class.new(:name => dest, :source => source, :recurse => true, :backup => false) catalog.add_resource @file catalog.apply @dirs.each do |path| newpath = path.sub(source, dest) Puppet::FileSystem.lstat(newpath).should be_directory end @files.each do |path| newpath = path.sub(source, dest) Puppet::FileSystem.lstat(newpath).ftype.should == "file" end end it "should not recursively manage files managed by a more specific explicit file" do dir = tmpfile("recursion_vs_explicit_1") subdir = File.join(dir, "subdir") file = File.join(subdir, "file") FileUtils.mkdir_p(subdir) File.open(file, "w") { |f| f.puts "" } base = described_class.new(:name => dir, :recurse => true, :backup => false, :mode => "755") sub = described_class.new(:name => subdir, :recurse => true, :backup => false, :mode => "644") catalog.add_resource base catalog.add_resource sub catalog.apply (get_mode(file) & 007777).should == 0644 end it "should recursively manage files even if there is an explicit file whose name is a prefix of the managed file" do managed = File.join(path, "file") generated = File.join(path, "file_with_a_name_starting_with_the_word_file") managed_mode = 0700 FileUtils.mkdir_p(path) FileUtils.touch(managed) FileUtils.touch(generated) catalog.add_resource described_class.new(:name => path, :recurse => true, :backup => false, :mode => managed_mode) catalog.add_resource described_class.new(:name => managed, :recurse => true, :backup => false, :mode => "644") catalog.apply (get_mode(generated) & 007777).should == managed_mode end describe "when recursing remote directories" do describe "when sourceselect first" do describe "for a directory" do it "should recursively copy the first directory that exists" do one = File.expand_path('thisdoesnotexist') two = tmpdir('two') FileUtils.mkdir_p(File.join(two, 'three')) FileUtils.touch(File.join(two, 'three', 'four')) catalog.add_resource Puppet::Type.newfile( :path => path, :ensure => :directory, :backup => false, :recurse => true, :sourceselect => :first, :source => [one, two] ) catalog.apply File.should be_directory(path) Puppet::FileSystem.exist?(File.join(path, 'one')).should be_false Puppet::FileSystem.exist?(File.join(path, 'three', 'four')).should be_true end it "should recursively copy an empty directory" do one = File.expand_path('thisdoesnotexist') two = tmpdir('two') three = tmpdir('three') file_in_dir_with_contents(three, 'a', '') catalog.add_resource Puppet::Type.newfile( :path => path, :ensure => :directory, :backup => false, :recurse => true, :sourceselect => :first, :source => [one, two, three] ) catalog.apply File.should be_directory(path) Puppet::FileSystem.exist?(File.join(path, 'a')).should be_false end it "should only recurse one level" do one = tmpdir('one') FileUtils.mkdir_p(File.join(one, 'a', 'b')) FileUtils.touch(File.join(one, 'a', 'b', 'c')) two = tmpdir('two') FileUtils.mkdir_p(File.join(two, 'z')) FileUtils.touch(File.join(two, 'z', 'y')) catalog.add_resource Puppet::Type.newfile( :path => path, :ensure => :directory, :backup => false, :recurse => true, :recurselimit => 1, :sourceselect => :first, :source => [one, two] ) catalog.apply Puppet::FileSystem.exist?(File.join(path, 'a')).should be_true Puppet::FileSystem.exist?(File.join(path, 'a', 'b')).should be_false Puppet::FileSystem.exist?(File.join(path, 'z')).should be_false end end describe "for a file" do it "should copy the first file that exists" do one = File.expand_path('thisdoesnotexist') two = tmpfile_with_contents('two', 'yay') three = tmpfile_with_contents('three', 'no') catalog.add_resource Puppet::Type.newfile( :path => path, :ensure => :file, :backup => false, :sourceselect => :first, :source => [one, two, three] ) catalog.apply File.read(path).should == 'yay' end it "should copy an empty file" do one = File.expand_path('thisdoesnotexist') two = tmpfile_with_contents('two', '') three = tmpfile_with_contents('three', 'no') catalog.add_resource Puppet::Type.newfile( :path => path, :ensure => :file, :backup => false, :sourceselect => :first, :source => [one, two, three] ) catalog.apply File.read(path).should == '' end end end describe "when sourceselect all" do describe "for a directory" do it "should recursively copy all sources from the first valid source" do dest = tmpdir('dest') one = tmpdir('one') two = tmpdir('two') three = tmpdir('three') four = tmpdir('four') file_in_dir_with_contents(one, 'a', one) file_in_dir_with_contents(two, 'a', two) file_in_dir_with_contents(two, 'b', two) file_in_dir_with_contents(three, 'a', three) file_in_dir_with_contents(three, 'c', three) obj = Puppet::Type.newfile( :path => dest, :ensure => :directory, :backup => false, :recurse => true, :sourceselect => :all, :source => [one, two, three, four] ) catalog.add_resource obj catalog.apply File.read(File.join(dest, 'a')).should == one File.read(File.join(dest, 'b')).should == two File.read(File.join(dest, 'c')).should == three end it "should only recurse one level from each valid source" do one = tmpdir('one') FileUtils.mkdir_p(File.join(one, 'a', 'b')) FileUtils.touch(File.join(one, 'a', 'b', 'c')) two = tmpdir('two') FileUtils.mkdir_p(File.join(two, 'z')) FileUtils.touch(File.join(two, 'z', 'y')) obj = Puppet::Type.newfile( :path => path, :ensure => :directory, :backup => false, :recurse => true, :recurselimit => 1, :sourceselect => :all, :source => [one, two] ) catalog.add_resource obj catalog.apply Puppet::FileSystem.exist?(File.join(path, 'a')).should be_true Puppet::FileSystem.exist?(File.join(path, 'a', 'b')).should be_false Puppet::FileSystem.exist?(File.join(path, 'z')).should be_true Puppet::FileSystem.exist?(File.join(path, 'z', 'y')).should be_false end end end end end describe "when generating resources" do before do source = tmpdir("generating_in_catalog_source") s1 = file_in_dir_with_contents(source, "one", "uno") s2 = file_in_dir_with_contents(source, "two", "dos") @file = described_class.new( :name => path, :source => source, :recurse => true, :backup => false ) catalog.add_resource @file end it "should add each generated resource to the catalog" do catalog.apply do |trans| catalog.resource(:file, File.join(path, "one")).must be_a(described_class) catalog.resource(:file, File.join(path, "two")).must be_a(described_class) end end it "should have an edge to each resource in the relationship graph" do catalog.apply do |trans| one = catalog.resource(:file, File.join(path, "one")) catalog.relationship_graph.should be_edge(@file, one) two = catalog.resource(:file, File.join(path, "two")) catalog.relationship_graph.should be_edge(@file, two) end end end describe "when copying files" do it "should be able to copy files with pound signs in their names (#285)" do source = tmpfile_with_contents("filewith#signs", "foo") dest = tmpfile("destwith#signs") catalog.add_resource described_class.new(:name => dest, :source => source) catalog.apply File.read(dest).should == "foo" end it "should be able to copy files with spaces in their names" do dest = tmpfile("destwith spaces") source = tmpfile_with_contents("filewith spaces", "foo") expected_mode = 0755 Puppet::FileSystem.chmod(expected_mode, source) catalog.add_resource described_class.new(:path => dest, :source => source) catalog.apply File.read(dest).should == "foo" (Puppet::FileSystem.stat(dest).mode & 007777).should == expected_mode end it "should be able to copy individual files even if recurse has been specified" do source = tmpfile_with_contents("source", "foo") dest = tmpfile("dest") catalog.add_resource described_class.new(:name => dest, :source => source, :recurse => true) catalog.apply File.read(dest).should == "foo" end end it "should create a file with content if ensure is omitted" do catalog.add_resource described_class.new( :path => path, :content => "this is some content, yo" ) catalog.apply File.read(path).should == "this is some content, yo" end it "should create files with content if both content and ensure are set" do file = described_class.new( :path => path, :ensure => "file", :content => "this is some content, yo" ) catalog.add_resource file catalog.apply File.read(path).should == "this is some content, yo" end it "should delete files with sources but that are set for deletion" do source = tmpfile_with_contents("source_source_with_ensure", "yay") dest = tmpfile_with_contents("source_source_with_ensure", "boo") file = described_class.new( :path => dest, :ensure => :absent, :source => source, :backup => false ) catalog.add_resource file catalog.apply Puppet::FileSystem.exist?(dest).should be_false end describe "when sourcing" do let(:source) { tmpfile_with_contents("source_default_values", "yay") } it "should apply the source metadata values" do set_mode(0770, source) file = described_class.new( :path => path, :ensure => :file, :source => source, :backup => false ) catalog.add_resource file catalog.apply get_owner(path).should == get_owner(source) get_group(path).should == get_group(source) (get_mode(path) & 07777).should == 0770 end it "should override the default metadata values" do set_mode(0770, source) file = described_class.new( :path => path, :ensure => :file, :source => source, :backup => false, :mode => 0440 ) catalog.add_resource file catalog.apply (get_mode(path) & 07777).should == 0440 end 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 def expects_system_granted_full_access_explicitly(path) expects_sid_granted_full_access_explicitly(path, @sids[:system]) 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 def expects_at_least_one_inherited_system_ace_grants_full_access(path) expects_at_least_one_inherited_ace_grants_full_access(path, @sids[:system]) end it "should provide valid default values when ACLs are not supported" do Puppet::Util::Windows::Security.stubs(:supports_acl?).returns(false) Puppet::Util::Windows::Security.stubs(:supports_acl?).with(source).returns false file = described_class.new( :path => path, :ensure => :file, :source => source, :backup => false ) catalog.add_resource file catalog.apply get_owner(path).should =~ /^S\-1\-5\-.*$/ get_group(path).should =~ /^S\-1\-0\-0.*$/ get_mode(path).should == 0644 end describe "when processing SYSTEM ACEs" do before do @sids = { :current_user => Puppet::Util::Windows::SID.name_to_sid(Sys::Admin.get_login), :system => Win32::Security::SID::LocalSystem, :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 } end describe "on files" do before :each do @file = described_class.new( :path => path, :ensure => :file, :source => source, :backup => false ) catalog.add_resource @file end describe "when source permissions are ignored" do before :each do @file[:source_permissions] = :ignore end it "preserves the inherited SYSTEM ACE" do catalog.apply expects_at_least_one_inherited_system_ace_grants_full_access(path) end end describe "when permissions are insync?" do it "preserves the explicit SYSTEM ACE" do FileUtils.touch(path) sd = Puppet::Util::Windows::Security.get_security_descriptor(path) sd.protect = true sd.owner = @sids[:none] sd.group = @sids[:none] Puppet::Util::Windows::Security.set_security_descriptor(source, sd) Puppet::Util::Windows::Security.set_security_descriptor(path, sd) catalog.apply expects_system_granted_full_access_explicitly(path) end end describe "when permissions are not insync?" do before :each do @file[:owner] = 'None' @file[:group] = 'None' end it "replaces inherited SYSTEM ACEs with an uninherited one for an existing file" do FileUtils.touch(path) expects_at_least_one_inherited_system_ace_grants_full_access(path) catalog.apply expects_system_granted_full_access_explicitly(path) end it "replaces inherited SYSTEM ACEs for a new file with an uninherited one" do catalog.apply expects_system_granted_full_access_explicitly(path) end end describe "created with SYSTEM as the group" do before :each do @file[:owner] = @sids[:users] @file[:group] = @sids[:system] @file[:mode] = 0644 catalog.apply end it "should allow the user to explicitly set the mode to 4" do system_aces = get_aces_for_path_by_sid(path, @sids[:system]) 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 it "prepends SYSTEM ace when changing group from system to power users" do @file[:group] = @sids[:power_users] catalog.apply system_aces = get_aces_for_path_by_sid(path, @sids[:system]) system_aces.size.should == 1 end end describe "with :links set to :follow" do it "should not fail to apply" do # at minimal, we need an owner and/or group @file[:owner] = @sids[:users] @file[:links] = :follow catalog.apply do |transaction| if transaction.any_failed? pretty_transaction_error(transaction) end end end end end describe "on directories" do before :each do @directory = described_class.new( :path => dir, :ensure => :directory ) catalog.add_resource @directory end def grant_everyone_full_access(path) 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 after :each do grant_everyone_full_access(dir) end describe "when source permissions are ignored" do before :each do @directory[:source_permissions] = :ignore end it "preserves the inherited SYSTEM ACE" do catalog.apply expects_at_least_one_inherited_system_ace_grants_full_access(dir) end end describe "when permissions are insync?" do it "preserves the explicit SYSTEM ACE" do Dir.mkdir(dir) source_dir = tmpdir('source_dir') @directory[:source] = source_dir sd = Puppet::Util::Windows::Security.get_security_descriptor(source_dir) sd.protect = true sd.owner = @sids[:none] sd.group = @sids[:none] Puppet::Util::Windows::Security.set_security_descriptor(source_dir, sd) Puppet::Util::Windows::Security.set_security_descriptor(dir, sd) catalog.apply expects_system_granted_full_access_explicitly(dir) end end describe "when permissions are not insync?" do before :each do @directory[:owner] = 'None' @directory[:group] = 'None' @directory[:mode] = 0444 end it "replaces inherited SYSTEM ACEs with an uninherited one for an existing directory" do FileUtils.mkdir(dir) expects_at_least_one_inherited_system_ace_grants_full_access(dir) catalog.apply expects_system_granted_full_access_explicitly(dir) end it "replaces inherited SYSTEM ACEs with an uninherited one for an existing directory" do catalog.apply expects_system_granted_full_access_explicitly(dir) end describe "created with SYSTEM as the group" do before :each do @directory[:owner] = @sids[:users] @directory[:group] = @sids[:system] @directory[:mode] = 0644 catalog.apply end it "should allow the user to explicitly set the mode to 4" do system_aces = get_aces_for_path_by_sid(dir, @sids[:system]) system_aces.should_not be_empty 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 it "prepends SYSTEM ace when changing group from system to power users" do @directory[:group] = @sids[:power_users] catalog.apply system_aces = get_aces_for_path_by_sid(dir, @sids[:system]) system_aces.size.should == 1 end end describe "with :links set to :follow" do it "should not fail to apply" do # at minimal, we need an owner and/or group @directory[:owner] = @sids[:users] @directory[:links] = :follow catalog.apply do |transaction| if transaction.any_failed? pretty_transaction_error(transaction) end end end end end end end end end describe "when purging files" do before do sourcedir = tmpdir("purge_source") destdir = tmpdir("purge_dest") sourcefile = File.join(sourcedir, "sourcefile") @copiedfile = File.join(destdir, "sourcefile") @localfile = File.join(destdir, "localfile") @purgee = File.join(destdir, "to_be_purged") File.open(@localfile, "w") { |f| f.print "oldtest" } File.open(sourcefile, "w") { |f| f.print "funtest" } # this file should get removed File.open(@purgee, "w") { |f| f.print "footest" } lfobj = Puppet::Type.newfile( :title => "localfile", :path => @localfile, :content => "rahtest", :ensure => :file, :backup => false ) destobj = Puppet::Type.newfile( :title => "destdir", :path => destdir, :source => sourcedir, :backup => false, :purge => true, :recurse => true ) catalog.add_resource lfobj, destobj catalog.apply end it "should still copy remote files" do File.read(@copiedfile).should == 'funtest' end it "should not purge managed, local files" do File.read(@localfile).should == 'rahtest' end it "should purge files that are neither remote nor otherwise managed" do Puppet::FileSystem.exist?(@purgee).should be_false end end describe "when using validate_cmd" do it "should fail the file resource if command fails" do catalog.add_resource(described_class.new(:path => path, :content => "foo", :validate_cmd => "/usr/bin/env false")) Puppet::Util::Execution.expects(:execute).with("/usr/bin/env false", {:combine => true, :failonfail => true}).raises(Puppet::ExecutionFailure, "Failed") report = catalog.apply.report report.resource_statuses["File[#{path}]"].should be_failed Puppet::FileSystem.exist?(path).should be_false end it "should succeed the file resource if command succeeds" do catalog.add_resource(described_class.new(:path => path, :content => "foo", :validate_cmd => "/usr/bin/env true")) Puppet::Util::Execution.expects(:execute).with("/usr/bin/env true", {:combine => true, :failonfail => true}).returns '' report = catalog.apply.report report.resource_statuses["File[#{path}]"].should_not be_failed Puppet::FileSystem.exist?(path).should be_true end end def tmpfile_with_contents(name, contents) file = tmpfile(name) File.open(file, "w") { |f| f.write contents } file end def file_in_dir_with_contents(dir, name, contents) full_name = File.join(dir, name) File.open(full_name, "w") { |f| f.write contents } full_name end def pretty_transaction_error(transaction) report = transaction.report status_failures = report.resource_statuses.values.select { |r| r.failed? } status_fail_msg = status_failures. collect(&:events). flatten. select { |event| event.status == 'failure' }. collect { |event| "#{event.resource}: #{event.message}" }.join("; ") raise "Got #{status_failures.length} failure(s) while applying: #{status_fail_msg}" 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 @@ -1,111 +1,111 @@ #!/usr/bin/env ruby require 'spec_helper' describe Puppet::Util do include PuppetSpec::Files describe "#execute" do it "should properly allow stdout and stderr to share a file" do command = "ruby -e '(1..10).each {|i| (i%2==0) ? $stdout.puts(i) : $stderr.puts(i)}'" Puppet::Util::Execution.execute(command, :combine => true).split.should =~ [*'1'..'10'] end it "should return output and set $CHILD_STATUS" do command = "ruby -e 'puts \"foo\"; exit 42'" output = Puppet::Util::Execution.execute(command, {:failonfail => false}) output.should == "foo\n" $CHILD_STATUS.exitstatus.should == 42 end it "should raise an error if non-zero exit status is returned" do command = "ruby -e 'exit 43'" expect { Puppet::Util::Execution.execute(command) }.to raise_error(Puppet::ExecutionFailure, /Execution of '#{command}' returned 43: /) $CHILD_STATUS.exitstatus.should == 43 end it "replace_file should preserve original ACEs from existing replaced file on Windows", :if => Puppet.features.microsoft_windows? do file = tmpfile("somefile") FileUtils.touch(file) 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) ignored_mode = 0644 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 replaced_sd = Puppet::Util::Windows::Security.get_security_descriptor(file) replaced_sd.dacl.should == expected_sd.dacl end it "replace_file should use reasonable default ACEs on a new file on Windows", :if => Puppet.features.microsoft_windows? do dir = tmpdir('DACL_playground') protected_sd = Puppet::Util::Windows::Security.get_security_descriptor(dir) protected_sd.protect = true Puppet::Util::Windows::Security.set_security_descriptor(dir, protected_sd) sibling_path = File.join(dir, 'sibling_file') FileUtils.touch(sibling_path) expected_sd = Puppet::Util::Windows::Security.get_security_descriptor(sibling_path) new_file_path = File.join(dir, 'new_file') ignored_mode = nil Puppet::Util.replace_file(new_file_path, ignored_mode) { |tmp_file| } new_sd = Puppet::Util::Windows::Security.get_security_descriptor(new_file_path) new_sd.dacl.should == expected_sd.dacl end end it "replace_file should work with filenames that include - and . (PUP-1389)", :if => Puppet.features.microsoft_windows? do expected_content = 'some content' dir = tmpdir('ReplaceFile_playground') destination_file = File.join(dir, 'some-file.xml') Puppet::Util.replace_file(destination_file, nil) do |temp_file| temp_file.open temp_file.write(expected_content) end actual_content = File.read(destination_file) actual_content.should == expected_content end it "replace_file should work with filenames that include special characters (PUP-1389)", :if => Puppet.features.microsoft_windows? do expected_content = 'some content' dir = tmpdir('ReplaceFile_playground') # http://www.fileformat.info/info/unicode/char/00e8/index.htm # dest_name = "somèfile.xml" dest_name = "som\u00E8file.xml" destination_file = File.join(dir, dest_name) Puppet::Util.replace_file(destination_file, nil) do |temp_file| temp_file.open temp_file.write(expected_content) end actual_content = File.read(destination_file) actual_content.should == expected_content end end diff --git a/spec/unit/provider/package/windows/package_spec.rb b/spec/unit/provider/package/windows/package_spec.rb index db0343a75..ce9d53fdd 100755 --- a/spec/unit/provider/package/windows/package_spec.rb +++ b/spec/unit/provider/package/windows/package_spec.rb @@ -1,126 +1,126 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/provider/package/windows/package' describe Puppet::Provider::Package::Windows::Package do subject { described_class } let(:hklm) { 'HKEY_LOCAL_MACHINE' } let(:hkcu) { 'HKEY_CURRENT_USER' } let(:path) { 'Software\Microsoft\Windows\CurrentVersion\Uninstall' } let(:key) { mock('key', :name => "#{hklm}\\#{path}\\Google") } let(:package) { mock('package') } context '::each' do it 'should generate an empty enumeration' do subject.expects(:with_key) subject.to_a.should be_empty end it 'should yield each package it finds' do subject.expects(:with_key).yields(key, {}) Puppet::Provider::Package::Windows::MsiPackage.expects(:from_registry).with('Google', {}).returns(package) yielded = nil subject.each do |pkg| yielded = pkg end yielded.should == package end end context '::with_key', :if => Puppet.features.microsoft_windows? do it 'should search HKLM (64 & 32) and HKCU (64 & 32)' do seq = sequence('reg') subject.expects(:open).with(hklm, path, subject::KEY64 | subject::KEY_READ).in_sequence(seq) subject.expects(:open).with(hklm, path, subject::KEY32 | subject::KEY_READ).in_sequence(seq) subject.expects(:open).with(hkcu, path, subject::KEY64 | subject::KEY_READ).in_sequence(seq) subject.expects(:open).with(hkcu, path, subject::KEY32 | subject::KEY_READ).in_sequence(seq) subject.with_key { |key, values| } end it 'should ignore file not found exceptions' do - ex = Puppet::Util::Windows::Error.new('Failed to open registry key', Windows::Error::ERROR_FILE_NOT_FOUND) + ex = Puppet::Util::Windows::Error.new('Failed to open registry key', Puppet::Util::Windows::Error::ERROR_FILE_NOT_FOUND) # make sure we don't stop after the first exception subject.expects(:open).times(4).raises(ex) keys = [] subject.with_key { |key, values| keys << key } keys.should be_empty end it 'should raise other types of exceptions' do - ex = Puppet::Util::Windows::Error.new('Failed to open registry key', Windows::Error::ERROR_ACCESS_DENIED) + ex = Puppet::Util::Windows::Error.new('Failed to open registry key', Puppet::Util::Windows::Error::ERROR_ACCESS_DENIED) subject.expects(:open).raises(ex) expect { subject.with_key{ |key, values| } }.to raise_error(Puppet::Error, /Access is denied/) end end context '::installer_class' do it 'should require the source parameter' do expect { subject.installer_class({}) }.to raise_error(Puppet::Error, /The source parameter is required when using the Windows provider./) end context 'MSI' do let (:klass) { Puppet::Provider::Package::Windows::MsiPackage } it 'should accept source ending in .msi' do subject.installer_class({:source => 'foo.msi'}).should == klass end it 'should accept quoted source ending in .msi' do subject.installer_class({:source => '"foo.msi"'}).should == klass end it 'should accept source case insensitively' do subject.installer_class({:source => '"foo.MSI"'}).should == klass end it 'should reject source containing msi in the name' do expect { subject.installer_class({:source => 'mymsi.txt'}) }.to raise_error(Puppet::Error, /Don't know how to install 'mymsi.txt'/) end end context 'Unknown' do it 'should reject packages it does not know about' do expect { subject.installer_class({:source => 'basram'}) }.to raise_error(Puppet::Error, /Don't know how to install 'basram'/) end end end context '::quote' do it 'should shell quote strings with spaces' do subject.quote('foo bar').should == '"foo bar"' end it 'should shell quote strings with spaces and quotes' do subject.quote('"foo bar" baz').should == '"\"foo bar\" baz"' end it 'should not shell quote strings without spaces' do subject.quote('"foobar"').should == '"foobar"' end end it 'should implement instance methods' do pkg = subject.new('orca', '5.0') pkg.name.should == 'orca' pkg.version.should == '5.0' end end diff --git a/spec/unit/util/windows/access_control_entry_spec.rb b/spec/unit/util/windows/access_control_entry_spec.rb index b139b0d42..8d3f51c8a 100644 --- a/spec/unit/util/windows/access_control_entry_spec.rb +++ b/spec/unit/util/windows/access_control_entry_spec.rb @@ -1,67 +1,67 @@ #!/usr/bin/env ruby require 'spec_helper' require 'puppet/util/windows' describe "Puppet::Util::Windows::AccessControlEntry", :if => Puppet.features.microsoft_windows? do let(:klass) { Puppet::Util::Windows::AccessControlEntry } let(:sid) { 'S-1-5-18' } - let(:mask) { Windows::File::FILE_ALL_ACCESS } + let(:mask) { Puppet::Util::Windows::File::FILE_ALL_ACCESS } it "creates an access allowed ace" do ace = klass.new(sid, mask) ace.type.should == klass::ACCESS_ALLOWED_ACE_TYPE end it "creates an access denied ace" do ace = klass.new(sid, mask, 0, klass::ACCESS_DENIED_ACE_TYPE) ace.type.should == klass::ACCESS_DENIED_ACE_TYPE end it "creates a non-inherited ace by default" do ace = klass.new(sid, mask) ace.should_not be_inherited end it "creates an inherited ace" do ace = klass.new(sid, mask, klass::INHERITED_ACE) ace.should be_inherited end it "creates a non-inherit-only ace by default" do ace = klass.new(sid, mask) ace.should_not be_inherit_only end it "creates an inherit-only ace" do ace = klass.new(sid, mask, klass::INHERIT_ONLY_ACE) ace.should be_inherit_only end context "when comparing aces" do let(:ace1) { klass.new(sid, mask, klass::INHERIT_ONLY_ACE, klass::ACCESS_DENIED_ACE_TYPE) } let(:ace2) { klass.new(sid, mask, klass::INHERIT_ONLY_ACE, klass::ACCESS_DENIED_ACE_TYPE) } it "returns true if different objects have the same set of values" do ace1.should == ace2 end it "returns false if different objects have different sets of values" do ace = klass.new(sid, mask) ace.should_not == ace1 end it "returns true when testing if two objects are eql?" do ace1.eql?(ace2) end it "returns false when comparing object identity" do ace1.should_not be_equal(ace2) end end end diff --git a/spec/unit/util/windows/sid_spec.rb b/spec/unit/util/windows/sid_spec.rb index 50f333a8d..2748f13c6 100755 --- a/spec/unit/util/windows/sid_spec.rb +++ b/spec/unit/util/windows/sid_spec.rb @@ -1,166 +1,166 @@ #!/usr/bin/env ruby require 'spec_helper' describe "Puppet::Util::Windows::SID", :if => Puppet.features.microsoft_windows? do if Puppet.features.microsoft_windows? require 'puppet/util/windows' end let(:subject) { Puppet::Util::Windows::SID } let(:sid) { Win32::Security::SID::LocalSystem } let(:invalid_sid) { 'bogus' } let(:unknown_sid) { 'S-0-0-0' } let(:unknown_name) { 'chewbacca' } context "#octet_string_to_sid_object" do it "should properly convert an array of bytes for the local Administrator SID" do host = '.' username = 'Administrator' admin = WIN32OLE.connect("WinNT://#{host}/#{username},user") converted = subject.octet_string_to_sid_object(admin.objectSID) converted.should == Win32::Security::SID.new(username, host) converted.should be_an_instance_of Win32::Security::SID end it "should properly convert an array of bytes for a well-known SID" do bytes = [1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0] converted = subject.octet_string_to_sid_object(bytes) converted.should == Win32::Security::SID.new('SYSTEM') converted.should be_an_instance_of Win32::Security::SID end it "should raise an error for non-array input" do expect { subject.octet_string_to_sid_object(invalid_sid) }.to raise_error(Puppet::Error, /Octet string must be an array of bytes/) end it "should raise an error for an empty byte array" do expect { subject.octet_string_to_sid_object([]) }.to raise_error(Puppet::Error, /Octet string must be an array of bytes/) end it "should raise an error for a malformed byte array" do expect { invalid_octet = [1] subject.octet_string_to_sid_object(invalid_octet) }.to raise_error(SystemCallError, /No mapping between account names and security IDs was done./) end end context "#name_to_sid" do it "should return nil if the account does not exist" do subject.name_to_sid(unknown_name).should be_nil end it "should accept unqualified account name" do subject.name_to_sid('SYSTEM').should == sid end it "should be case-insensitive" do subject.name_to_sid('SYSTEM').should == subject.name_to_sid('system') end it "should be leading and trailing whitespace-insensitive" do subject.name_to_sid('SYSTEM').should == subject.name_to_sid(' SYSTEM ') end it "should accept domain qualified account names" do subject.name_to_sid('NT AUTHORITY\SYSTEM').should == sid end it "should be the identity function for any sid" do subject.name_to_sid(sid).should == sid end end context "#name_to_sid_object" do it "should return nil if the account does not exist" do subject.name_to_sid_object(unknown_name).should be_nil end it "should return a Win32::Security::SID instance for any valid sid" do subject.name_to_sid_object(sid).should be_an_instance_of(Win32::Security::SID) end it "should accept unqualified account name" do subject.name_to_sid_object('SYSTEM').to_s.should == sid end it "should be case-insensitive" do subject.name_to_sid_object('SYSTEM').should == subject.name_to_sid_object('system') end it "should be leading and trailing whitespace-insensitive" do subject.name_to_sid_object('SYSTEM').should == subject.name_to_sid_object(' SYSTEM ') end it "should accept domain qualified account names" do subject.name_to_sid_object('NT AUTHORITY\SYSTEM').to_s.should == sid end end context "#sid_to_name" do it "should return nil if given a sid for an account that doesn't exist" do subject.sid_to_name(unknown_sid).should be_nil end it "should accept a sid" do subject.sid_to_name(sid).should == "NT AUTHORITY\\SYSTEM" end end context "#sid_ptr_to_string" do it "should raise if given an invalid sid" do expect { subject.sid_ptr_to_string(nil) }.to raise_error(Puppet::Error, /Invalid SID/) end it "should yield a valid sid pointer" do string = nil subject.string_to_sid_ptr(sid) do |ptr| string = subject.sid_ptr_to_string(ptr) end string.should == sid end end context "#string_to_sid_ptr" do it "should yield sid_ptr" do ptr = nil subject.string_to_sid_ptr(sid) do |p| ptr = p end ptr.should_not be_nil end it "should raise on an invalid sid" do expect { subject.string_to_sid_ptr(invalid_sid) }.to raise_error(Puppet::Error, /Failed to convert string SID/) end end context "#valid_sid?" do it "should return true for a valid SID" do subject.valid_sid?(sid).should be_true end it "should return false for an invalid SID" do subject.valid_sid?(invalid_sid).should be_false end it "should raise if the conversion fails" do subject.expects(:string_to_sid_ptr).with(sid). - raises(Puppet::Util::Windows::Error.new("Failed to convert string SID: #{sid}", Windows::Error::ERROR_ACCESS_DENIED)) + raises(Puppet::Util::Windows::Error.new("Failed to convert string SID: #{sid}", Puppet::Util::Windows::Error::ERROR_ACCESS_DENIED)) expect { subject.string_to_sid_ptr(sid) {|ptr| } }.to raise_error(Puppet::Util::Windows::Error, /Failed to convert string SID: #{sid}/) end end end