diff --git a/lib/puppet/provider/file/posix.rb b/lib/puppet/provider/file/posix.rb index 3b6ab6eb1..23aabed3b 100644 --- a/lib/puppet/provider/file/posix.rb +++ b/lib/puppet/provider/file/posix.rb @@ -1,115 +1,83 @@ Puppet::Type.type(:file).provide :posix do desc "Uses POSIX functionality to manage file's users and rights." confine :feature => :posix include Puppet::Util::POSIX include Puppet::Util::Warnings require 'etc' - def id2name(id) - return id.to_s if id.is_a?(Symbol) + def uid2name(id) + return id.to_s if id.is_a?(Symbol) or id.is_a?(String) return nil if id > Puppet[:maximum_uid].to_i begin user = Etc.getpwuid(id) - rescue TypeError - return nil - rescue ArgumentError + rescue TypeError, ArgumentError return nil end if user.uid == "" return nil else return user.name end end - def is_owner_insync?(current, should) - should.each do |value| - if value =~ /^\d+$/ - uid = Integer(value) - elsif value.is_a?(String) - fail "Could not find user #{value}" unless uid = uid(value) - else - uid = value - end - - return true if uid == current - end - - unless Puppet.features.root? - warnonce "Cannot manage ownership unless running as root" - return true - end - - false - end - # Determine if the user is valid, and if so, return the UID - def validuser?(value) + def name2uid(value) Integer(value) rescue uid(value) || false end - def retrieve(resource) + def owner unless stat = resource.stat return :absent end currentvalue = stat.uid # On OS X, files that are owned by -2 get returned as really # large UIDs instead of negative ones. This isn't a Ruby bug, # it's an OS X bug, since it shows up in perl, too. if currentvalue > Puppet[:maximum_uid].to_i self.warning "Apparently using negative UID (#{currentvalue}) on a platform that does not consistently handle them" currentvalue = :silly end currentvalue end - def sync(path, links, should) + def owner=(should) # Set our method appropriately, depending on links. - if links == :manage + if resource[:links] == :manage method = :lchown else method = :chown end - uid = nil - should.each do |user| - break if uid = validuser?(user) - end - - raise Puppet::Error, "Could not find user(s) #{should.join(",")}" unless uid - begin - File.send(method, uid, nil, path) + File.send(method, should, nil, resource[:path]) rescue => detail - raise Puppet::Error, "Failed to set owner to '#{uid}': #{detail}" + raise Puppet::Error, "Failed to set owner to '#{should}': #{detail}" end - - :file_changed end def mode if stat = resource.stat return (stat.mode & 007777).to_s(8) else return :absent end end def mode=(value) begin File.chmod(value.to_i(8), resource[:path]) rescue => detail error = Puppet::Error.new("failed to set mode #{mode} on #{resource[:path]}: #{detail.message}") error.set_backtrace detail.backtrace raise error end end end diff --git a/lib/puppet/provider/file/windows.rb b/lib/puppet/provider/file/windows.rb index d212d8d60..dbd4c8818 100644 --- a/lib/puppet/provider/file/windows.rb +++ b/lib/puppet/provider/file/windows.rb @@ -1,94 +1,79 @@ Puppet::Type.type(:file).provide :windows do desc "Uses Microsoft Windows functionality to manage file's users and rights." confine :feature => :microsoft_windows include Puppet::Util::Warnings + if Puppet.features.microsoft_windows? require 'puppet/util/windows' + require 'puppet/util/adsi' include Puppet::Util::Windows::Security end + ERROR_INVALID_SID_STRUCTURE = 1337 - def id2name(id) - return id.to_s if id.is_a?(Symbol) - return nil if id > Puppet[:maximum_uid].to_i - # should translate ID numbers to usernames - id - end - - def is_owner_insync?(current, should) - should.each do |value| - if value =~ /^\d+$/ - uid = Integer(value) - elsif value.is_a?(String) - fail "Could not find user #{value}" unless uid = uid(value) - else - uid = value + def uid2name(id) + # If it's a valid sid, get the name. Otherwise, it's already a name, so + # just return it. + begin + if string_to_sid_ptr(id) + name = nil + Puppet::Util::ADSI.execquery( + "SELECT Name FROM Win32_Account WHERE SID = '#{id}' + AND LocalAccount = true" + ).each { |u| name ||= u.name } + return name end - - return true if uid == current - end - - unless Puppet.features.root? - warnonce "Cannot manage ownership unless running as root" - return true + rescue Puppet::Util::Windows::Error => e + raise unless e.code == ERROR_INVALID_SID_STRUCTURE end - false + id end # Determine if the user is valid, and if so, return the UID - def validuser?(value) - info "Is '#{value}' a valid user?" - return 0 + def name2uid(value) + # If it's a valid sid, then return it. Else, it's name we need to convert + # to sid. begin - number = Integer(value) - return number - rescue ArgumentError - number = nil - end - (number = uid(value)) && number - end - - def retrieve(resource) - unless stat = resource.stat - return :absent + return value if string_to_sid_ptr(value) + rescue Puppet::Util::Windows::Error => e + raise unless e.code == ERROR_INVALID_SID_STRUCTURE end - currentvalue = stat.uid - - # On OS X, files that are owned by -2 get returned as really - # large UIDs instead of negative ones. This isn't a Ruby bug, - # it's an OS X bug, since it shows up in perl, too. - if currentvalue > Puppet[:maximum_uid].to_i - self.warning "Apparently using negative UID (#{currentvalue}) on a platform that does not consistently handle them" - currentvalue = :silly - end + Puppet::Util::ADSI::User.new(value).sid rescue nil + end - currentvalue + def owner + return :absent unless resource.exist? + get_owner(resource[:path]) end - def sync(path, links, should) - info("should set '%s'%%owner to '%s'" % [path, should]) + def owner=(should) + begin + set_owner(should, resource[:path]) + rescue => detail + raise Puppet::Error, "Failed to set owner to '#{should}': #{detail}" + end end def mode if resource.exist? get_mode(resource[:path]).to_s(8) else :absent end end def mode=(value) begin set_mode(value.to_i(8), resource[:path]) rescue => detail error = Puppet::Error.new("failed to set mode #{mode} on #{resource[:path]}: #{detail.message}") error.set_backtrace detail.backtrace raise error end :file_changed end end diff --git a/lib/puppet/type/file/group.rb b/lib/puppet/type/file/group.rb index 4d1f2f4e6..29655313a 100755 --- a/lib/puppet/type/file/group.rb +++ b/lib/puppet/type/file/group.rb @@ -1,113 +1,116 @@ require 'puppet/util/posix' # Manage file group ownership. module Puppet Puppet::Type.type(:file).newproperty(:group) do include Puppet::Util::POSIX require 'etc' desc "Which group should own the file. Argument can be either group name or group ID." @event = :file_changed validate do |group| raise(Puppet::Error, "Invalid group name '#{group.inspect}'") unless group and group != "" end def id2name(id) return id.to_s if id.is_a?(Symbol) return nil if id > Puppet[:maximum_uid].to_i begin group = Etc.getgrgid(id) rescue ArgumentError return nil end if group.gid == "" return nil else return group.name end end # We want to print names, not numbers def is_to_s(currentvalue) if currentvalue.is_a? Integer id2name(currentvalue) || currentvalue else return currentvalue.to_s end end def should_to_s(newvalue = @should) if newvalue.is_a? Integer id2name(newvalue) || newvalue else return newvalue.to_s end end def insync?(current) + return true if Puppet.features.microsoft_windows? + @should.each do |value| if value =~ /^\d+$/ gid = Integer(value) elsif value.is_a?(String) fail "Could not find group #{value}" unless gid = gid(value) else gid = value end return true if gid == current end false end def retrieve + return 0 if Puppet.features.microsoft_windows? return :absent unless stat = resource.stat currentvalue = stat.gid # On OS X, files that are owned by -2 get returned as really # large GIDs instead of negative ones. This isn't a Ruby bug, # it's an OS X bug, since it shows up in perl, too. if currentvalue > Puppet[:maximum_uid].to_i self.warning "Apparently using negative GID (#{currentvalue}) on a platform that does not consistently handle them" currentvalue = :silly end currentvalue end # Determine if the group is valid, and if so, return the GID def validgroup?(value) Integer(value) rescue gid(value) || false end # Normal users will only be able to manage certain groups. Right now, # we'll just let it fail, but we should probably set things up so # that users get warned if they try to change to an unacceptable group. def sync # Set our method appropriately, depending on links. if resource[:links] == :manage method = :lchown else method = :chown end gid = nil @should.each do |group| break if gid = validgroup?(group) end raise Puppet::Error, "Could not find group(s) #{@should.join(",")}" unless gid begin # set owner to nil so it's ignored File.send(method, nil, gid, resource[:path]) rescue => detail error = Puppet::Error.new( "failed to chgrp #{resource[:path]} to #{gid}: #{detail.message}") raise error end :file_changed end end end diff --git a/lib/puppet/type/file/owner.rb b/lib/puppet/type/file/owner.rb index 483cc7fce..2eda3c406 100755 --- a/lib/puppet/type/file/owner.rb +++ b/lib/puppet/type/file/owner.rb @@ -1,52 +1,36 @@ module Puppet Puppet::Type.type(:file).newproperty(:owner) do + include Puppet::Util::Warnings desc "To whom the file should belong. Argument can be user name or user ID." - @event = :file_changed def insync?(current) - provider.is_owner_insync?(current, @should) - end + # We don't want to validate/munge users until we actually start to + # evaluate this property, because they might be added during the catalog + # apply. + @should.map! do |val| + provider.name2uid(val) or raise "Could not find user #{val}" + end - # We want to print names, not numbers - def is_to_s(currentvalue) - provider.id2name(currentvalue) || currentvalue - end + return true if @should.include?(current) - def should_to_s(newvalue = @should) - case newvalue - when Symbol - newvalue.to_s - when Integer - provider.id2name(newvalue) || newvalue - when String - newvalue - else - raise Puppet::DevError, "Invalid uid type #{newvalue.class}(#{newvalue})" + unless Puppet.features.root? + warnonce "Cannot manage ownership unless running as root" + return true end + + false end - def retrieve - if self.should - @should = @should.collect do |val| - unless val.is_a?(Integer) - if tmp = provider.validuser?(val) - val = tmp - else - raise "Could not find user #{val}" - end - else - val - end - end - end - provider.retrieve(@resource) + # We want to print names, not numbers + def is_to_s(currentvalue) + provider.uid2name(currentvalue) || currentvalue end - def sync - provider.sync(resource[:path], resource[:links], @should) + def should_to_s(newvalue) + provider.uid2name(newvalue) || newvalue end end end diff --git a/spec/integration/type/file_spec.rb b/spec/integration/type/file_spec.rb index f4d4eb11b..9e088183a 100755 --- a/spec/integration/type/file_spec.rb +++ b/spec/integration/type/file_spec.rb @@ -1,506 +1,506 @@ #!/usr/bin/env rspec 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) do include PuppetSpec::Files let(:catalog) { Puppet::Resource::Catalog.new } let(:path) { tmpfile('file_testing') } if Puppet.features.posix? def get_mode(file) File.lstat(file).mode end else class SecurityHelper extend Puppet::Util::Windows::Security end def get_mode(file) SecurityHelper.get_mode(file) 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 file = described_class.new :path => path, :mode => 0755 catalog.add_resource file file.parameter(:mode).expects(:retrieve).never report = catalog.apply.report report.resource_statuses["File[#{path}]"].should_not be_failed File.should_not be_exist(path) end describe "when writing files" do - it "should backup files to a filebucket when one is configured", :fails_on_windows => true 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.puts "bar" } md5 = Digest::MD5.hexdigest(File.read(file[:path])) catalog.apply filebucket.bucket.getfile(md5).should == "bar\n" 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" FileTest.should be_exist(backup) File.read(backup).should == "bar\n" end it "should fail if no backup can be performed" do file = described_class.new :path => path, :backup => ".bak", :content => "foo" catalog.add_resource file File.open(path, 'w') { |f| f.puts "bar" } # Create a directory where the backup should be so that writing to it fails Dir.mkdir("#{path}.bak") catalog.apply File.read(path).should == "bar\n" end it "should not backup symlinks", :unless => Puppet.features.microsoft_windows? 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" } File.symlink(dest1, link) md5 = Digest::MD5.hexdigest(File.read(file[:path])) catalog.apply File.readlink(link).should == dest2 Find.find(bucket[:path]) { |f| File.file?(f) }.should be_nil 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", :fails_on_windows => true do + 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" } foomd5 = Digest::MD5.hexdigest(File.read(foofile)) barmd5 = Digest::MD5.hexdigest(File.read(barfile)) catalog.apply bucket.bucket.getfile(foomd5).should == "fooyay" bucket.bucket.getfile(barmd5).should == "baryay" end it "should propagate failures encountered when renaming the temporary file" do file = described_class.new :path => path, :content => "foo" file.stubs(:perform_backup).returns(true) catalog.add_resource file File.open(path, "w") { |f| f.print "bar" } File.expects(:rename).raises ArgumentError expect { file.write(:content) }.to raise_error(Puppet::Error, /Could not rename temporary file/) File.read(path).should == "bar" 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", :fails_on_windows => true do + 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", :unless => Puppet.features.microsoft_windows? 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) File.lstat(link_path).should be_directory end @files.each do |path| link_path = path.sub(source, dest) File.lstat(link_path).ftype.should == "link" end end - it "should be able to recursively copy files", :fails_on_windows => true do + 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) File.lstat(newpath).should be_directory end @files.each do |path| newpath = path.sub(source, dest) File.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 end - describe "when generating resources", :fails_on_windows => true do + describe "when generating resources" do before do source = tmpfile("generating_in_catalog_source") Dir.mkdir(source) s1 = File.join(source, "one") s2 = File.join(source, "two") File.open(s1, "w") { |f| f.puts "uno" } File.open(s2, "w") { |f| f.puts "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")).should be_a(described_class) catalog.resource(:file, File.join(path, "two")).should 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 # Ticket #285. it "should be able to copy files with pound signs in their names" do source = tmpfile("filewith#signs") dest = tmpfile("destwith#signs") File.open(source, "w") { |f| f.print "foo" } file = described_class.new(:name => dest, :source => source) catalog.add_resource file catalog.apply File.read(dest).should == "foo" end it "should be able to copy files with spaces in their names" do source = tmpfile("filewith spaces") dest = tmpfile("destwith spaces") File.open(source, "w") { |f| f.print "foo" } File.chmod(0755, source) file = described_class.new(:path => dest, :source => source) catalog.add_resource file catalog.apply expected_mode = Puppet.features.microsoft_windows? ? 0644 : 0755 File.read(dest).should == "foo" (File.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("source") dest = tmpfile("dest") File.open(source, "w") { |f| f.print "foo" } file = described_class.new(:name => dest, :source => source, :recurse => true) catalog.add_resource file catalog.apply File.read(dest).should == "foo" end end it "should create a file with content if ensure is omitted" do file = described_class.new( :path => path, :content => "this is some content, yo" ) catalog.add_resource file 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("source_source_with_ensure") File.open(source, "w") { |f| f.puts "yay" } File.open(path, "w") { |f| f.puts "boo" } file = described_class.new( :path => path, :ensure => :absent, :source => source, :backup => false ) catalog.add_resource file catalog.apply File.should_not be_exist(path) end describe "when purging files" do before do sourcedir = tmpfile("purge_source") destdir = tmpfile("purge_dest") Dir.mkdir(sourcedir) Dir.mkdir(destdir) 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 FileTest.should_not be_exist(@purgee) end end end diff --git a/spec/unit/provider/file/posix_spec.rb b/spec/unit/provider/file/posix_spec.rb index 41937e4a9..b8106ca9f 100644 --- a/spec/unit/provider/file/posix_spec.rb +++ b/spec/unit/provider/file/posix_spec.rb @@ -1,41 +1,133 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Type.type(:file).provider(:posix), :if => Puppet.features.posix? do include PuppetSpec::Files let(:path) { tmpfile('posix_file_spec') } let(:resource) { Puppet::Type.type(:file).new :path => path, :mode => 0777, :provider => described_class.name } let(:provider) { resource.provider } describe "#mode" do it "should return a string with the higher-order bits stripped away" do FileUtils.touch(path) File.chmod(0644, path) provider.mode.should == '644' end it "should return absent if the file doesn't exist" do provider.mode.should == :absent end end describe "#mode=" do it "should chmod the file to the specified value" do FileUtils.touch(path) File.chmod(0644, path) provider.mode = '0755' provider.mode.should == '755' end it "should pass along any errors encountered" do expect do provider.mode = '644' end.to raise_error(Puppet::Error, /failed to set mode/) end end + + describe "#uid2name" do + it "should return the name of the user identified by the id" do + Etc.stubs(:getpwuid).with(501).returns(Struct::Passwd.new('jilluser', nil, 501)) + + provider.uid2name(501).should == 'jilluser' + end + + it "should return the argument if it's already a name" do + provider.uid2name('jilluser').should == 'jilluser' + end + + it "should return nil if the argument is above the maximum uid" do + provider.uid2name(Puppet[:maximum_uid] + 1).should == nil + end + + it "should return nil if the user doesn't exist" do + Etc.expects(:getpwuid).raises(ArgumentError, "can't find user for 999") + + provider.uid2name(999).should == nil + end + end + + describe "#name2uid" do + it "should return the id of the user if it exists" do + passwd = Struct::Passwd.new('bobbo', nil, 502) + + Etc.stubs(:getpwnam).with('bobbo').returns(passwd) + Etc.stubs(:getpwuid).with(502).returns(passwd) + + provider.name2uid('bobbo').should == 502 + end + + it "should return the argument if it's already an id" do + provider.name2uid('503').should == 503 + end + + it "should return false if the user doesn't exist" do + Etc.stubs(:getpwnam).with('chuck').raises(ArgumentError, "can't find user for chuck") + + provider.name2uid('chuck').should == false + end + end + + describe "#owner" do + it "should return the uid of the file owner" do + FileUtils.touch(path) + owner = File.stat(path).uid + + provider.owner.should == owner + end + + it "should return absent if the file can't be statted" do + provider.owner.should == :absent + end + + it "should warn and return :silly if the value is beyond the maximum uid" do + stat = stub('stat', :uid => Puppet[:maximum_uid] + 1) + resource.stubs(:stat).returns(stat) + + provider.owner.should == :silly + @logs.should be_any {|log| log.level == :warning and log.message =~ /Apparently using negative UID/} + end + end + + describe "#owner=" do + it "should set the owner but not the group of the file" do + File.expects(:lchown).with(15, nil, resource[:path]) + + provider.owner = 15 + end + + it "should chown a link if managing links" do + resource[:links] = :manage + File.expects(:lchown).with(20, nil, resource[:path]) + + provider.owner = 20 + end + + it "should chown a link target if following links" do + resource[:links] = :follow + File.expects(:chown).with(20, nil, resource[:path]) + + provider.owner = 20 + end + + it "should pass along any error encountered setting the owner" do + File.expects(:lchown).raises(ArgumentError) + + expect { provider.owner = 25 }.to raise_error(Puppet::Error, /Failed to set owner to '25'/) + end + end end diff --git a/spec/unit/provider/file/windows_spec.rb b/spec/unit/provider/file/windows_spec.rb index 3bec3c435..416a54687 100644 --- a/spec/unit/provider/file/windows_spec.rb +++ b/spec/unit/provider/file/windows_spec.rb @@ -1,47 +1,110 @@ #!/usr/bin/env rspec require 'spec_helper' if Puppet.features.microsoft_windows? require 'puppet/util/windows' class WindowsSecurity extend Puppet::Util::Windows::Security end end describe Puppet::Type.type(:file).provider(:windows), :if => Puppet.features.microsoft_windows? do include PuppetSpec::Files let(:path) { tmpfile('windows_file_spec') } let(:resource) { Puppet::Type.type(:file).new :path => path, :mode => 0777, :provider => described_class.name } let(:provider) { resource.provider } describe "#mode" do it "should return a string with the higher-order bits stripped away" do FileUtils.touch(path) WindowsSecurity.set_mode(0644, path) provider.mode.should == '644' end it "should return absent if the file doesn't exist" do provider.mode.should == :absent end end describe "#mode=" do it "should chmod the file to the specified value" do FileUtils.touch(path) WindowsSecurity.set_mode(0644, path) provider.mode = '0755' provider.mode.should == '755' end it "should pass along any errors encountered" do expect do provider.mode = '644' end.to raise_error(Puppet::Error, /failed to set mode/) end end + + describe "#uid2name" do + it "should return the name of the user identified by the sid" do + result = [stub('user', :name => 'quinn')] + Puppet::Util::ADSI.stubs(:execquery).returns(result) + + provider.uid2name('S-1-1-50').should == 'quinn' + end + + it "should return the argument if it's already a name" do + provider.uid2name('flannigan').should == 'flannigan' + end + + it "should return nil if the user doesn't exist" do + Puppet::Util::ADSI.stubs(:execquery).returns [] + + provider.uid2name('S-1-1-50').should == nil + end + end + + describe "#name2uid" do + it "should return the sid of the user" do + Puppet::Util::ADSI.stubs(:execquery).returns [stub('account', :Sid => 'S-1-1-50')] + + provider.name2uid('anybody').should == 'S-1-1-50' + end + + it "should return the argument if it's already a sid" do + provider.name2uid('S-1-1-50').should == 'S-1-1-50' + end + + it "should return nil if the user doesn't exist" do + Puppet::Util::ADSI.stubs(:execquery).returns [] + + provider.name2uid('someone').should == nil + end + end + + describe "#owner" do + it "should return the sid of the owner if the file does exist" do + FileUtils.touch(resource[:path]) + provider.stubs(:get_owner).with(resource[:path]).returns('S-1-1-50') + + provider.owner.should == 'S-1-1-50' + end + + it "should return absent if the file doesn't exist" do + provider.owner.should == :absent + end + end + + describe "#owner=" do + it "should set the owner to the specified value" do + provider.expects(:set_owner).with('S-1-1-50', resource[:path]) + provider.owner = 'S-1-1-50' + end + + it "should propagate any errors encountered when setting the owner" do + provider.stubs(:set_owner).raises(ArgumentError) + + expect { provider.owner = 'S-1-1-50' }.to raise_error(Puppet::Error, /Failed to set owner/) + end + end end diff --git a/spec/unit/type/file/owner_spec.rb b/spec/unit/type/file/owner_spec.rb index ed3bef1fe..a57e91091 100755 --- a/spec/unit/type/file/owner_spec.rb +++ b/spec/unit/type/file/owner_spec.rb @@ -1,149 +1,58 @@ #!/usr/bin/env rspec -require 'spec_helper' - -property = Puppet::Type.type(:file).attrclass(:owner) - -describe property do - before do - # FIXME: many of these tests exercise the provider rather than `owner` - # and should be moved into provider tests. ~JW - @provider = Puppet::Type.type(:file).provider(:posix).new - @provider.stubs(:uid).with("one").returns(1) - @resource = stub 'resource', :line => "foo", :file => "bar" - @resource.stubs(:[]).returns "foo" - @resource.stubs(:[]).with(:path).returns "/my/file" - @resource.stubs(:provider).returns @provider - - @owner = property.new :resource => @resource - end +require 'spec_helper' - it "should have a method for testing whether an owner is valid" do - @provider.must respond_to(:validuser?) - end +describe Puppet::Type.type(:file).attrclass(:owner) do + include PuppetSpec::Files - it "should return the found uid if an owner is valid" do - @provider.expects(:uid).with("foo").returns 500 - @provider.validuser?("foo").should == 500 - end + let(:path) { tmpfile('mode_spec') } + let(:resource) { Puppet::Type.type(:file).new :path => path, :owner => 'joeuser' } + let(:owner) { resource.property(:owner) } - it "should return false if an owner is not valid" do - @provider.expects(:uid).with("foo").returns nil - @provider.validuser?("foo").should be_false + before :each do + Puppet.features.stubs(:root?).returns(true) end - describe "when retrieving the current value" do - it "should return :absent if the file cannot stat" do - @resource.expects(:stat).returns nil - - @owner.retrieve.should == :absent - end - - it "should get the uid from the stat instance from the file" do - stat = stub 'stat', :ftype => "foo" - @resource.expects(:stat).returns stat - stat.expects(:uid).returns 500 - - @owner.retrieve.should == 500 - end - - it "should warn and return :silly if the found value is higher than the maximum uid value" do - Puppet.settings.expects(:value).with(:maximum_uid).returns 500 - - stat = stub 'stat', :ftype => "foo" - @resource.expects(:stat).returns stat - stat.expects(:uid).returns 1000 + describe "#insync?" do + before :each do + resource[:owner] = ['foo', 'bar'] - @provider.expects(:warning) - @owner.retrieve.should == :silly + resource.provider.stubs(:name2uid).with('foo').returns 1001 + resource.provider.stubs(:name2uid).with('bar').returns 1002 end - end - - describe "when determining if the file is in sync" do - describe "and not running as root" do - it "should warn once and return true" do - Puppet.features.expects(:root?).returns false - - @provider.expects(:warnonce) - @owner.should = [10] - @owner.must be_safe_insync(20) - end - end + it "should fail if an owner's id can't be found by name" do + resource.provider.stubs(:name2uid).returns nil - before do - Puppet.features.stubs(:root?).returns true + expect { owner.insync?(5) }.to raise_error(/Could not find user foo/) end - it "should be in sync if 'should' is not provided" do - @owner.must be_safe_insync(10) + it "should use the id for comparisons, not the name" do + owner.insync?('foo').should be_false end - it "should directly compare the owner values if the desired owner is an integer" do - @owner.should = [10] - @owner.must be_safe_insync(10) + it "should return true if the current owner is one of the desired owners" do + owner.insync?(1001).should be_true end - it "should treat numeric strings as integers" do - @owner.should = ["10"] - @owner.must be_safe_insync(10) - end - - it "should convert the owner name to an integer if the desired owner is a string" do - @provider.expects(:uid).with("foo").returns 10 - @owner.should = %w{foo} - - @owner.must be_safe_insync(10) - end - - it "should not validate that users exist when a user is specified as an integer" do - @provider.expects(:uid).never - @provider.validuser?(10) - end - - it "should fail if it cannot convert an owner name to an integer" do - @provider.expects(:uid).with("foo").returns nil - @owner.should = %w{foo} - - lambda { @owner.safe_insync?(10) }.should raise_error(Puppet::Error) - end - - it "should return false if the owners are not equal" do - @owner.should = [10] - @owner.should_not be_safe_insync(20) + it "should return false if the current owner is not one of the desired owners" do + owner.insync?(1003).should be_false end end - describe "when changing the owner" do - before do - @owner.should = %w{one} - @owner.stubs(:path).returns "path" - @owner.stubs(:uid).returns 500 - end - - it "should chown the file if :links is set to :follow" do - @resource.expects(:[]).with(:links).returns :follow - File.expects(:chown) - - @owner.sync - end - - it "should lchown the file if :links is set to :manage" do - @resource.expects(:[]).with(:links).returns :manage - File.expects(:lchown) + %w[is_to_s should_to_s].each do |prop_to_s| + describe "##{prop_to_s}" do + it "should use the name of the user if it can find it" do + resource.provider.stubs(:uid2name).with(1001).returns 'foo' - @owner.sync - end - - it "should use the first valid owner in its 'should' list" do - @owner.should = %w{one two three} - @provider.expects(:validuser?).with("one").returns nil - @provider.expects(:validuser?).with("two").returns 500 - @provider.expects(:validuser?).with("three").never + owner.send(prop_to_s, 1001).should == 'foo' + end - File.expects(:chown).with(500, nil, "/my/file") + it "should use the id of the user if it can't" do + resource.provider.stubs(:uid2name).with(1001).returns nil - @owner.sync + owner.send(prop_to_s, 1001).should == 1001 + end end end end