diff options
| author | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2005-09-08 20:03:52 +0000 |
|---|---|---|
| committer | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2005-09-08 20:03:52 +0000 |
| commit | da6590d072cbff40525311b6d5debae0885e1054 (patch) | |
| tree | 9e1dde9f6251dee70bbe67f6822f8043d85eaed0 | |
| parent | 3726cee8a648268054b240ffae8d7f5f6272c75a (diff) | |
| download | puppet-da6590d072cbff40525311b6d5debae0885e1054.tar.gz puppet-da6590d072cbff40525311b6d5debae0885e1054.tar.xz puppet-da6590d072cbff40525311b6d5debae0885e1054.zip | |
group management now works on os x, although it is six shades of really nasty. Netinfo is a nightmare.
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@640 980ebf18-57e1-0310-9a29-db15c13687c0
| -rwxr-xr-x | lib/puppet/type/group.rb | 321 | ||||
| -rwxr-xr-x | test/types/tc_group.rb | 166 |
2 files changed, 435 insertions, 52 deletions
diff --git a/lib/puppet/type/group.rb b/lib/puppet/type/group.rb index fc8b7555d..d75146a8b 100755 --- a/lib/puppet/type/group.rb +++ b/lib/puppet/type/group.rb @@ -7,16 +7,23 @@ require 'puppet/type/state' module Puppet class State class GroupState < Puppet::State + attr_accessor :xaddflag, :ninfoarg, :infomethod class << self - def infomethod - if defined? @infomethod and @infomethod - return @infomethod - else - return @name - end - end + [:infomethod, :ninfokey, :xaddflag].each { |method| + self.send(:define_method, method) { + # *shudder* + if eval("defined? @%s" % method) and eval("@%s" % method) + return eval("@%s" % method) + else + return @name + end + } + } end + # we use the POSIX interfaces to retrieve all information, + # so we don't have to worry about abstracting that across + # the system def retrieve obj = @parent.getinfo(true) @@ -24,9 +31,100 @@ module Puppet @is = obj.send(method) end + end + + class GroupGID < GroupState + @doc = "The group ID. Must be specified numerically. If not + specified, a number will be picked, which can result in ID + differences across systems and thus is not recommended. The + method for picking GIDs is basically to find the next GID above + the highest existing GID excluding those above 65000." + @name = :gid + @xaddflag = "-g" + + def should=(gid) + if gid.is_a?(String) + if gid =~ /^[0-9]+$/ + gid = Integer(gid) + else + raise Puppet::Error, "Invalid GID %s" % gid + end + end + + if gid.is_a?(Integer) and gid < 0 + raise Puppet::Error, "GIDs must be positive" + end + + Puppet.info "Setting gid to %s" % gid + + @should = gid + end + end + + class GroupName < GroupState + @doc = "The group name. While naming limitations vary by + system, it is advisable to keep the name to the degenerate + limitations, which is a maximum of 8 characters beginning with + a letter." + @name = :name + + def should=(gid) + if gid.is_a?(String) + if gid =~ /^[0-9]+$/ + gid = Integer(gid) + end + end + + Puppet.info "Setting gid to %s" % gid + + @should = gid + end + end + + module GroupXAdd + def self.test + system("which groupadd > /dev/null 2>&1") + + if $? == 0 + return true + else + Puppet.err "Could not find groupadd" + return false + end + end def sync obj = @parent.getinfo + + # if the user either does not or should not exist... + # yes, it's a badly named method + if obj.nil? or @should == :notfound + return create() + end + + # there's a possibility that we created the user in this session + # so verify that we're actually out of sync + if self.insync? + return nil + end + + cmd = [ + "groupmod", self.class.flag, "'%s'" % @should, @parent.name + ].join(" ") + + output = %x{#{cmd} 2>&1} + + unless $? == 0 + raise Puppet::Error, "Could not modify %s on group %s: %s" % + [self.class.name, @parent.name, output] + end + + return :user_modified + end + + private + def create + obj = @parent.getinfo cmd = nil event = nil @@ -68,24 +166,131 @@ module Puppet end end - class GroupGID < GroupState - @doc = "The group ID. Must be specified numerically. If not specified, - a number will be picked, which can result in ID differences across - systems and thus is not recommended. The method for picking GIDs - is basically to find the next GID above the highest existing GID - excluding those above 65000." - @name = :gid + module GroupNInfo + def self.test + system("which niutil > /dev/null 2>&1") - def should=(gid) - if gid.is_a?(String) - if gid =~ /^[0-9]+$/ - gid = Integer(gid) + if $? == 0 + return true + else + Puppet.err "Could not find niutil" + return false + end + end + + def self.flush + output = %x{lookupd -flushcache 2>&1} + + if $? != 0 + Puppet.err "Could not flush lookupd cache: %s" % output + end + end + + def retrieve + cmd = %w{nireport / /groups name} + + if key = self.class.ninfokey + cmd << key.to_s + else + raise Puppet::DevError, + "Could not find ninfokey for state %s" % + self.class.name + end + + output = %x{#{cmd.join(" ")} 2>&1}.split("\n").each { |line| + name, value = line.chomp.split(/\s+/) + + if name == @parent.name + if value =~ /^\d+$/ + @is = Integer(value) + else + @is = value + end end + } + + unless defined? @is + @is = :notfound end + end - Puppet.info "Setting gid to %s" % gid + def sync + events = [] + obj = @parent.getinfo - @should = gid + if self.name == :name + return syncname() + end + + if obj.nil? + raise Puppet::DevError, + "Group does not exist; cannot set gid" + end + + cmd = ["niutil"] + + cmd << "-createprop" << "/" << "/groups/%s" % @parent.name + + if key = self.class.ninfokey + cmd << key << "'%s'" % @should + else + raise Puppet::DevError, + "Could not find ninfokey for state %s" % + self.class.name + end + + output = %x{#{cmd.join(" ")} 2>&1} + + unless $? == 0 + raise Puppet::Error, "Could not set %s on group %s: %s" % + [self.class.name, @parent.name, output] + end + + GroupNInfo.flush() + + return :group_modified + end + + private + def syncname + + cmd = ["niutil"] + event = nil + if @should == :notfound + # we need to remove the object... + unless @parent.exists? + # the group already doesn't exist + return nil + end + + cmd << "-destroy" + type = "delete" + else + if @parent.exists? + raise Puppet::DevError, + "Got told to create a group that already exists" + end + # we're creating the group + + # i can just tell i'm going to regret this + # why doesn't POSIX include interfaces for adding users + # and groups? it's stupid + cmd << "-create" + type = "create" + end + + cmd << "/" << "/groups/%s" % @parent.name + + output = %x{#{cmd.join(" ")} 2>&1} + + unless $? == 0 + raise Puppet::Error, "Could not %s group %s: %s" % + [type, @parent.name, output] + end + + GroupNInfo.flush() + + return "group_#{type}d".intern end end end @@ -93,26 +298,70 @@ module Puppet class Type class Group < Type @states = [ - Puppet::State::GroupGID + Puppet::State::GroupName, + Puppet::State::GroupGID ] - @parameters = [ - :name - ] + @@extender = nil + case Facter["operatingsystem"].value + when "Darwin": + @@extender = "NInfo" + else + @@extender = "XAdd" + end + + @name = :group + @namevar = :name + + # all of the states are very similar, but syncing is different + # for each _type_ of state + @states.each { |state| + begin + klass = eval("Puppet::State::Group" + @@extender) + if klass.test + state.send(:include, klass) + else + Puppet.err "Cannot sync %s on %s" % + [state.name, @name] + end + rescue NameError + Puppet.notice "No %s extender for %s" % + [@@extender, state.name] + end + } + + @parameters = [] @doc = "Manage groups. This type can only create groups. Group membership must be managed on individual users." - @paramdoc[:name] = "The group name. While naming limitations vary by - system, it is advisable to keep the name to the degenerate limitations, - which is a maximum of 8 characters beginning with a letter." - @name = :group - @namevar = :name + + def exists? + case @@extender + when "NInfo": + cmd = "nidump -r /groups/%s /" % self.name + output = %x{#{cmd} 2>/dev/null} + if output == "" + return false + else + return true + end + else + if self.getinfo + return true + else + return false + end + end + end def getinfo(refresh = false) if @groupinfo.nil? or refresh == true begin - @groupinfo = Etc.getgrnam(self[:name]) + #GroupNInfo.flush() + system("lookupd -flushcache") + #sleep(4) + @groupinfo = Etc.getgrnam(self.name) rescue ArgumentError => detail @groupinfo = nil end @@ -127,13 +376,13 @@ module Puppet end def retrieve - obj = self.getinfo(true) - - if obj.nil? + if self.exists? + super + else # the group does not exist - # unless we're in noop mode, we need to auto-pick a gid if there - # hasn't been one specified + # unless we're in noop mode, we need to auto-pick a gid if + # there hasn't been one specified unless @states.include?(:gid) or self.noop highest = 0 Etc.group { |group| @@ -152,8 +401,6 @@ module Puppet } return - else - super end end end diff --git a/test/types/tc_group.rb b/test/types/tc_group.rb index 2a23535d4..d5c46ae60 100755 --- a/test/types/tc_group.rb +++ b/test/types/tc_group.rb @@ -13,23 +13,73 @@ require 'test/unit' class TestGroup < TestPuppet def setup - Puppet[:loglevel] = :debug if __FILE__ == $0 @@tmpgroups = [] super end def teardown + Puppet::Type::Group.clear + if Facter["operatingsystem"].value == "Darwin" + Puppet::State::GroupNInfo.flush() + end @@tmpgroups.each { |group| - begin - obj = Etc.getgrnam(group) - system("groupdel %s" % group) - rescue ArgumentError => detail - # no such group, so we're fine + unless missing?(group) + # if we've gotten this far, the group exists, so remove it + Puppet.info "Cleaning %s" % group + group = Puppet::Type::Group.new( + :name => group + ) + group[:name] = :notfound + group.state(:name).sync end } super end + case Facter["operatingsystem"].value + when "Darwin": + def missing?(group) + output = %x{nidump -r /groups/#{group} / 2>/dev/null}.chomp + + if output == "" + return true + else + return false + end + + assert_equal("", output, "Group %s is present:\n%s" % [group, output]) + end + + def gid(name) + %x{nireport / /groups name gid}.split("\n").each { |line| + group, id = line.chomp.split(/\s+/) + if group == name + return Integer(id) + end + } + + return nil + end + else + def assert_missing(group) + begin + obj = Etc.getgrnam(group) + raise false + rescue ArgumentError + raise true + end + end + + def gid(name) + assert_nothing_raised { + obj = Etc.getgrnam(group[:name]) + return obj.gid + } + + return nil + end + end + def groupnames %x{groups}.chomp.split(/ /) end @@ -38,6 +88,56 @@ class TestGroup < TestPuppet Process.groups end + def attrtest_gid(group) + obj = nil + #assert_nothing_raised { + # obj = Etc.getgrnam(group[:name]) + #} + group.retrieve + old = gid(group[:name]) + comp = newcomp("gidtest", group) + + group[:gid] = old + + trans = assert_events(comp, [], "group") + + newgid = old + while true + newgid += 1 + + if newgid - old > 1000 + $stderr.puts "Could not find extra test UID" + return + end + begin + Etc.getgrgid(newgid) + rescue ArgumentError => detail + break + end + end + + assert_nothing_raised("Failed to change group id") { + group[:gid] = newgid + } + + trans = assert_events(comp, [:group_modified], "group") + + curgid = nil + assert_nothing_raised { + curgid = gid(group[:name]) + } + + assert_equal(newgid, curgid, "GID was not changed") + + assert_rollback_events(trans, [:group_modified], "group") + + assert_nothing_raised { + curgid = gid(group[:name]) + } + + assert_equal(old, curgid, "UID was not reverted") + end + def test_eachmethod obj = Etc.getgrnam(groupnames()[0]) @@ -47,8 +147,19 @@ class TestGroup < TestPuppet assert_nothing_raised { method = state.infomethod assert(method, "State %s has no infomethod" % name) - assert(obj.respond_to?(method), "State %s has an invalid method %s" % - [name, method]) + assert(obj.respond_to?(method), + "State %s has an invalid method %s" % + [name, method] + ) + } + + assert_nothing_raised { + method = state.infomethod + assert(method, "State %s has no infomethod" % name) + assert(obj.respond_to?(method), + "State %s has an invalid method %s" % + [name, method] + ) } } end @@ -81,9 +192,21 @@ class TestGroup < TestPuppet comp = nil name = "pptestgr" - assert_raise(ArgumentError) { - obj = Etc.getgrnam(name) - } + os = Facter["operatingsystem"].value + + #if os == "Darwin" + # obj = nil + # assert_nothing_raised { + # obj = Etc.getgrnam(name) + # } + # assert_equal(-2, obj.gid, "Darwin GID is not -2") + #else + #assert_raise(ArgumentError) { + # obj = Etc.getgrnam(name) + #} + #end + assert(missing?(name), "Group %s is still present" % name) + assert_nothing_raised { gobj = Puppet::Type::Group.new( :name => name @@ -92,19 +215,32 @@ class TestGroup < TestPuppet comp = newcomp("groupmaker %s" % name, gobj) } - trans = assert_events(comp, [:group_created], "group") @@tmpgroups << name + trans = assert_events(comp, [:group_created, :group_modified], + "group") obj = nil assert_nothing_raised { obj = Etc.getgrnam(name) } + assert(!missing?(name), "Group %s is missing" % name) - assert_rollback_events(trans, [:group_deleted], "group") + tests = Puppet::Type::Group.validstates.collect { |name, state| + state.name + } - assert_raise(ArgumentError) { - obj = Etc.getgrnam(name) + gobj.retrieve + tests.each { |test| + if self.respond_to?("attrtest_%s" % test) + self.send("attrtest_%s" % test, gobj) + else + #$stderr.puts "Not testing attr %s of group" % test + end } + assert_rollback_events(trans, [:group_modified, :group_deleted], + "group") + + assert(missing?(name), "Group %s is still present" % name) end else $stderr.puts "Not running as root; skipping group creation tests." |
