summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorluke <luke@980ebf18-57e1-0310-9a29-db15c13687c0>2005-09-08 20:03:52 +0000
committerluke <luke@980ebf18-57e1-0310-9a29-db15c13687c0>2005-09-08 20:03:52 +0000
commitda6590d072cbff40525311b6d5debae0885e1054 (patch)
tree9e1dde9f6251dee70bbe67f6822f8043d85eaed0
parent3726cee8a648268054b240ffae8d7f5f6272c75a (diff)
downloadpuppet-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-xlib/puppet/type/group.rb321
-rwxr-xr-xtest/types/tc_group.rb166
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."