summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/puppet/statechange.rb6
-rw-r--r--lib/puppet/transaction.rb1
-rwxr-xr-xlib/puppet/type/group.rb9
-rwxr-xr-xlib/puppet/type/user.rb210
-rw-r--r--test/puppettest.rb34
-rwxr-xr-xtest/types/tc_group.rb35
-rwxr-xr-xtest/types/tc_user.rb335
7 files changed, 469 insertions, 161 deletions
diff --git a/lib/puppet/statechange.rb b/lib/puppet/statechange.rb
index ac10dc301..af066a773 100644
--- a/lib/puppet/statechange.rb
+++ b/lib/puppet/statechange.rb
@@ -48,7 +48,7 @@ module Puppet
if event.nil?
#event = @state.parent.class.name.id2name + "_changed"
# they didn't actually change anything
- return
+ return nil
elsif ! event.is_a?(Symbol)
Puppet.warning "State '%s' returned invalid event '%s'; resetting to default" %
[@state.class,event]
@@ -111,6 +111,10 @@ module Puppet
unless @state.insync?
Puppet.notice "Rolling %s backward" % self
return self.go
+ else
+ Puppet.debug "rollback is already in sync: %s vs. %s" %
+ [@state.is.inspect, @state.should.inspect]
+ return nil
end
#raise "Moving statechanges backward is currently unsupported"
diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb
index 2e726df0b..f0ef8469d 100644
--- a/lib/puppet/transaction.rb
+++ b/lib/puppet/transaction.rb
@@ -115,6 +115,7 @@ class Transaction
def rollback
events = @changes.collect { |change|
if change.is_a?(Puppet::StateChange)
+ # skip changes that were never actually run
next unless change.run
#change.transaction = self
begin
diff --git a/lib/puppet/type/group.rb b/lib/puppet/type/group.rb
index 38a56dc31..fc8b7555d 100755
--- a/lib/puppet/type/group.rb
+++ b/lib/puppet/type/group.rb
@@ -100,7 +100,12 @@ module Puppet
:name
]
- @doc = " "
+ @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
@@ -109,7 +114,7 @@ module Puppet
begin
@groupinfo = Etc.getgrnam(self[:name])
rescue ArgumentError => detail
- # leave groupinfo as nil
+ @groupinfo = nil
end
end
diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb
index cc7bbadaa..c4b31ed3e 100755
--- a/lib/puppet/type/user.rb
+++ b/lib/puppet/type/user.rb
@@ -7,43 +7,95 @@ require 'puppet/type/state'
module Puppet
class State
class UserState < Puppet::State
- @@userinfo = nil
-
class << self
- attr_accessor :infomethod
-
- def getinfo(refresh = false)
- if @@userinfo.nil? or refresh == true
- begin
- @@userinfo = Etc.getpwnam(@parent[:name])
- rescue ArgumentError => detail
- @@userinfo = :notfound
- end
+ attr_accessor :flag
+ def infomethod
+ if defined? @infomethod and @infomethod
+ return @infomethod
+ else
+ return @name
+ end
+ end
+ end
+
+ def create
+ obj = @parent.getinfo
+
+ cmd = nil
+ event = nil
+ if @should == :notfound
+ # we need to remove the object...
+ if obj.nil?
+ # the user already doesn't exist
+ return nil
end
- @@userinfo
+ cmd = ["userdel", @parent.name]
+ type = "delete"
+ else
+ unless obj.nil?
+ raise Puppet::DevError,
+ "Got told to create a user that already exists"
+ end
+ # we're creating the user
+
+ # 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 = ["useradd"]
+ @parent.eachstate { |state|
+ # the value needs to be quoted, mostly because -c might
+ # have spaces in it
+ cmd << state.class.flag << "'%s'" % state.should
+ }
+ cmd << @parent.name
+ type = "create"
+ end
+
+ output = %x{#{cmd.join(" ")} 2>&1}
+
+ unless $? == 0
+ raise Puppet::Error, "Could not %s group %s: %s" %
+ [type, @parent.name, output]
end
+
+ return "user_#{type}d".intern
end
def retrieve
- info = self.class.getinfo(true)
+ if info = @parent.getinfo(true)
+ @is = info.send(self.class.infomethod)
+ else
+ @is = :notfound
+ end
+ end
- method = self.class.infomethod || self.class.name
+ def sync
+ obj = @parent.getinfo
- unless method
- raise Puppet::DevError,
- "Could not retrieve info method for state %s" % self.class.name
+ # if the user either does not or should not exist...
+ # yes, it's a badly named method
+ if obj.nil? or @should == :notfound
+ return self.create
end
- unless info.respond_to?(method)
- raise Puppet::DevError, "UserInfo object does not respond to %s" %
- method
+ # 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 = [
+ "usermod", self.class.flag, "'%s'" % @should, @parent.name
+ ].join(" ")
- @is = info.send(method)
- end
+ output = %x{#{cmd} 2>&1}
- def sync
+ unless $? == 0
+ raise Puppet::Error, "Could not modify %s on user %s: %s" %
+ [self.class.name, @parent.name, output]
+ end
+
+ return :user_modified
end
end
@@ -53,11 +105,13 @@ module Puppet
automatically, which will likely result in the same user having
different IDs on different systems, which is not recommended."
@name = :uid
+ @flag = "-u"
end
class UserGID < UserState
@doc = "The user's primary group. Can be specified numerically or by name."
@name = :gid
+ @flag = "-g"
def should=(gid)
method = :getgrgid
@@ -79,88 +133,54 @@ module Puppet
@should = ginfo.gid
end
-
- def sync
- return :executed_command
- end
end
class UserComment < UserState
- @doc = "The expected return code. An error will be returned if the
- executed command returns something else."
+ @doc = "A description of the user. Generally is a user's full name."
@name = :comment
-
- def retrieve
- end
-
- def sync
- return :executed_command
- end
+ @infomethod = :gecos
+ @flag = "-c"
end
class UserHome < UserState
- @doc = "The expected return code. An error will be returned if the
- executed command returns something else."
+ @doc = "The home directory of the user. The directory must be created
+ separately and is not currently checked for existence."
@name = :home
-
- def retrieve
- end
-
- def sync
- return :executed_command
- end
+ @infomethod = :dir
+ @flag = "-d"
end
class UserShell < UserState
- @doc = "The expected return code. An error will be returned if the
- executed command returns something else."
+ @doc = "The user's login shell. The shell must exist and be
+ executable."
@name = :shell
-
- def retrieve
- end
-
- def sync
- return :executed_command
- end
+ @flag = "-s"
end
+ # these three states are all implemented differently on each platform, so i'm
+ # disabling them for now
+
+ # FIXME Puppet::State::UserLocked is currently non-functional
class UserLocked < UserState
@doc = "The expected return code. An error will be returned if the
executed command returns something else."
@name = :locked
-
- def retrieve
- end
-
- def sync
- return :executed_command
- end
end
+ # FIXME Puppet::State::UserExpire is currently non-functional
class UserExpire < UserState
@doc = "The expected return code. An error will be returned if the
executed command returns something else."
@name = :expire
-
- def retrieve
- end
-
- def sync
- return :executed_command
- end
+ @flag = "-e"
end
+ # FIXME Puppet::State::UserInactive is currently non-functional
class UserInactive < UserState
@doc = "The expected return code. An error will be returned if the
executed command returns something else."
@name = :inactive
-
- def retrieve
- end
-
- def sync
- return :executed_command
- end
+ @flag = "-f"
end
end
@@ -172,25 +192,49 @@ module Puppet
Puppet::State::UserGID,
Puppet::State::UserComment,
Puppet::State::UserHome,
- Puppet::State::UserShell,
- Puppet::State::UserLocked,
- Puppet::State::UserExpire,
- Puppet::State::UserInactive
+ Puppet::State::UserShell
]
@parameters = [
:name
]
- @doc = "
- "
+ @paramdoc[:name] = "User name. While limitations are determined for
+ each operating system, it is generally a good idea to keep to the
+ degenerate 8 characters, beginning with a letter."
+
+ @doc = "Manage users. Currently can create and modify users, but cannot
+ delete them. Theoretically all of the parameters are optional,
+ but if no parameters are specified the comment will be set to the
+ user name in order to make the internals work out correctly."
@name = :user
@namevar = :name
+ def getinfo(refresh = false)
+ if @userinfo.nil? or refresh == true
+ begin
+ @userinfo = Etc.getpwnam(self[:name])
+ rescue ArgumentError => detail
+ @userinfo = nil
+ end
+ end
+
+ @userinfo
+ end
+
+ def initialize(hash)
+ @userinfo = nil
+ super
+
+ if @states.empty?
+ self[:comment] = self[:name]
+ end
+ end
+
def retrieve
- info = Puppet::State::UserState.getinfo
+ info = self.getinfo(true)
- if info == :notfound
+ if info.nil?
# the user does not exist
@states.each { |name, state|
state.is = :notfound
diff --git a/test/puppettest.rb b/test/puppettest.rb
index 642c8275f..1543434e9 100644
--- a/test/puppettest.rb
+++ b/test/puppettest.rb
@@ -27,6 +27,40 @@ class TestPuppet < Test::Unit::TestCase
Puppet::Type.allclear
end
+ def assert_rollback_events(trans, events, msg)
+ run_events(:rollback, trans, events, msg)
+ end
+
+ def assert_events(comp, events, msg)
+ trans = nil
+ assert_nothing_raised("Component %s failed" % [msg]) {
+ trans = comp.evaluate
+ }
+
+ run_events(:evaluate, trans, events, msg)
+ end
+
+ def run_events(type, trans, events, msg)
+ case type
+ when :evaluate, :rollback: # things are hunky-dory
+ else
+ raise Puppet::DevError, "Incorrect run_events type"
+ end
+
+ method = type
+
+ newevents = nil
+ assert_nothing_raised("Transaction %s %s failed" % [type, msg]) {
+ newevents = trans.send(method).reject { |e| e.nil? }.collect { |e|
+ e.event
+ }
+ }
+
+ assert_equal(events, newevents, "Incorrect %s %s events" % [type, msg])
+
+ return trans
+ end
+
def test_nothing
end
end
diff --git a/test/types/tc_group.rb b/test/types/tc_group.rb
index 7f3c1d01f..2a23535d4 100755
--- a/test/types/tc_group.rb
+++ b/test/types/tc_group.rb
@@ -14,6 +14,19 @@ require 'test/unit'
class TestGroup < TestPuppet
def setup
Puppet[:loglevel] = :debug if __FILE__ == $0
+ @@tmpgroups = []
+ super
+ end
+
+ def teardown
+ @@tmpgroups.each { |group|
+ begin
+ obj = Etc.getgrnam(group)
+ system("groupdel %s" % group)
+ rescue ArgumentError => detail
+ # no such group, so we're fine
+ end
+ }
super
end
@@ -67,6 +80,10 @@ class TestGroup < TestPuppet
gobj = nil
comp = nil
name = "pptestgr"
+
+ assert_raise(ArgumentError) {
+ obj = Etc.getgrnam(name)
+ }
assert_nothing_raised {
gobj = Puppet::Type::Group.new(
:name => name
@@ -75,23 +92,19 @@ class TestGroup < TestPuppet
comp = newcomp("groupmaker %s" % name, gobj)
}
- trans = nil
- assert_nothing_raised {
- trans = comp.evaluate
- }
+ trans = assert_events(comp, [:group_created], "group")
+ @@tmpgroups << name
- events = nil
+ obj = nil
assert_nothing_raised {
- events = trans.evaluate.reject { |e| e.nil? }.collect { |e| e.event }
+ obj = Etc.getgrnam(name)
}
- assert_equal([:group_created], events, "Incorrect group events")
+ assert_rollback_events(trans, [:group_deleted], "group")
- assert_nothing_raised {
- events = trans.rollback.reject { |e| e.nil? }.collect { |e| e.event }
+ assert_raise(ArgumentError) {
+ obj = Etc.getgrnam(name)
}
-
- assert_equal([:group_deleted], events, "Incorrect deletion group events")
end
else
$stderr.puts "Not running as root; skipping group creation tests."
diff --git a/test/types/tc_user.rb b/test/types/tc_user.rb
index 3522539d5..a70e55ce6 100755
--- a/test/types/tc_user.rb
+++ b/test/types/tc_user.rb
@@ -6,93 +6,300 @@ end
# $Id$
+require 'etc'
require 'puppet/type'
+require 'puppettest'
require 'test/unit'
-class TestType < Test::Unit::TestCase
- def test_typemethods
- assert_nothing_raised() {
- Puppet::Type.buildstatehash
+class TestUser < TestPuppet
+ def setup
+ @@tmpusers = []
+ Puppet[:loglevel] = :debug if __FILE__ == $0
+ super
+ end
+
+ def teardown
+ @@tmpusers.each { |user|
+ begin
+ obj = Etc.getpwnam(user)
+ system("userdel %s" % user)
+ rescue ArgumentError => detail
+ # no such user, so we're fine
+ end
}
+ super
+ end
- Puppet::Type.eachtype { |type|
- name = nil
- assert_nothing_raised() {
- name = type.name
- }
+ def attrtest_comment(user)
+ old = user.is(:comment)
+ user[:comment] = "A different comment"
- assert(
- name
- )
+ comp = newcomp("commenttest", user)
- assert_equal(
- type,
- Puppet::Type.type(name)
- )
+ trans = assert_events(comp, [:user_modified], "user")
- assert(
- type.namevar
- )
+ obj = nil
+ assert_nothing_raised {
+ obj = Etc.getpwnam(user[:name])
+ }
- assert_not_nil(
- type.states
- )
+ assert_equal("A different comment", obj.gecos, "Comment was not changed")
- assert_not_nil(
- type.validstates
- )
+ assert_rollback_events(trans, [:user_modified], "user")
- assert(
- type.validparameter?(type.namevar)
- )
+ assert_nothing_raised {
+ obj = Etc.getpwnam(user[:name])
}
+
+ assert_equal(old, obj.gecos, "Comment was not reverted")
end
- def test_stringvssymbols
- file = nil
- path = "/tmp/testfile"
- assert_nothing_raised() {
- system("rm -f %s" % path)
- file = Puppet::Type::PFile.new(
- :path => path,
- :create => true,
- :recurse => true,
- :checksum => "md5"
- )
+ def attrtest_home(user)
+ obj = nil
+ assert_nothing_raised {
+ obj = Etc.getpwnam(user[:name])
}
- assert_nothing_raised() {
- file.retrieve
+ old = obj.dir
+ comp = newcomp("hometest", user)
+
+ user[:home] = old
+
+ trans = assert_events(comp, [], "user")
+
+ user[:home] = "/tmp"
+
+ trans = assert_events(comp, [:user_modified], "user")
+
+ obj = nil
+ assert_nothing_raised {
+ obj = Etc.getpwnam(user[:name])
}
- assert_nothing_raised() {
- file.sync
+
+ assert_equal("/tmp", obj.dir, "Home was not changed")
+
+ assert_rollback_events(trans, [:user_modified], "user")
+
+ assert_nothing_raised {
+ obj = Etc.getpwnam(user[:name])
}
- Puppet::Type::PFile.clear
- assert_nothing_raised() {
- system("rm -f %s" % path)
- file = Puppet::Type::PFile.new(
- "path" => path,
- "create" => true,
- "recurse" => true,
- "checksum" => "md5"
- )
+
+ assert_equal(old, obj.dir, "Home was not reverted")
+ end
+
+ def attrtest_shell(user)
+ obj = nil
+ assert_nothing_raised {
+ obj = Etc.getpwnam(user[:name])
}
- assert_nothing_raised() {
- file.retrieve
+ old = obj.shell
+ comp = newcomp("shelltest", user)
+
+ user[:shell] = old
+
+ trans = assert_events(comp, [], "user")
+
+ newshell = %w{/bin/sh /bin/bash /sbin/sh /bin/ksh /bin/zsh /bin/csh /bin/tcsh
+ /usr/bin/sh /usr/bin/bash /usr/bin/ksh /usr/bin/zsh /usr/bin/csh
+ /usr/bin/tcsh}.find { |shell|
+ FileTest.exists?(shell) and shell != old
}
- assert_nothing_raised() {
- file[:path]
+
+ unless newshell
+ $stderr.puts "Cannot find alternate shell; skipping shell test"
+ return
+ end
+
+ user[:shell] = newshell
+
+ trans = assert_events(comp, [:user_modified], "user")
+
+ obj = nil
+ assert_nothing_raised {
+ obj = Etc.getpwnam(user[:name])
+ }
+
+ assert_equal(newshell, obj.shell, "Shell was not changed")
+
+ assert_rollback_events(trans, [:user_modified], "user")
+
+ assert_nothing_raised {
+ obj = Etc.getpwnam(user[:name])
}
- assert_nothing_raised() {
- file["path"]
+
+ assert_equal(old, obj.shell, "Shell was not reverted")
+ end
+
+ def attrtest_gid(user)
+ obj = nil
+ assert_nothing_raised {
+ obj = Etc.getpwnam(user[:name])
+ }
+ old = obj.gid
+ comp = newcomp("gidtest", user)
+
+ user[:gid] = old
+
+ trans = assert_events(comp, [], "user")
+
+ newgid = %w{nogroup nobody staff users daemon}.find { |gid|
+ begin
+ group = Etc.getgrnam(gid)
+ rescue ArgumentError => detail
+ false
+ end
+ old != group.gid
}
- assert_nothing_raised() {
- file[:recurse]
+
+ unless newgid
+ $stderr.puts "Cannot find alternate group; skipping gid test"
+ return
+ end
+
+ # first test by name
+ assert_nothing_raised("Failed to specify group by name") {
+ user[:gid] = newgid
}
- assert_nothing_raised() {
- file["recurse"]
+
+ trans = assert_events(comp, [:user_modified], "user")
+
+ # then by id
+ newgid = Etc.getgrnam(newgid).gid
+
+ assert_nothing_raised("Failed to specify group by id") {
+ user[:gid] = newgid
}
- assert_nothing_raised() {
- file.sync
+
+ assert_events(comp, [], "user")
+
+ obj = nil
+ assert_nothing_raised {
+ obj = Etc.getpwnam(user[:name])
}
+
+ assert_equal(newgid, obj.gid, "GID was not changed")
+
+ assert_rollback_events(trans, [:user_modified], "user")
+
+ assert_nothing_raised {
+ obj = Etc.getpwnam(user[:name])
+ }
+
+ assert_equal(old, obj.gid, "GID was not reverted")
+ end
+
+ def attrtest_uid(user)
+ obj = nil
+ assert_nothing_raised {
+ obj = Etc.getpwnam(user[:name])
+ }
+ old = obj.uid
+ comp = newcomp("uidtest", user)
+
+ user[:uid] = old
+
+ trans = assert_events(comp, [], "user")
+
+ newuid = old
+ while true
+ newuid += 1
+
+ if newuid - old > 1000
+ $stderr.puts "Could not find extra test UID"
+ return
+ end
+ begin
+ newuser = Etc.getpwuid(newuid)
+ rescue ArgumentError => detail
+ break
+ end
+ end
+
+ assert_nothing_raised("Failed to change user id") {
+ user[:uid] = newuid
+ }
+
+ trans = assert_events(comp, [:user_modified], "user")
+
+ obj = nil
+ assert_nothing_raised {
+ obj = Etc.getpwnam(user[:name])
+ }
+
+ assert_equal(newuid, obj.uid, "UID was not changed")
+
+ assert_rollback_events(trans, [:user_modified], "user")
+
+ assert_nothing_raised {
+ obj = Etc.getpwnam(user[:name])
+ }
+
+ assert_equal(old, obj.uid, "UID was not reverted")
+ end
+
+ def test_eachmethod
+ obj = Etc.getpwuid(Process.uid)
+
+ assert(obj, "Could not retrieve test group object")
+
+ Puppet::Type::User.validstates.each { |name, state|
+ 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
+
+ if Process.uid == 0
+ def test_simpleuser
+ user = nil
+ name = "pptest"
+
+ assert_raise(ArgumentError) {
+ Etc.getpwnam(name)
+ }
+
+ assert_nothing_raised {
+ user = Puppet::Type::User.new(
+ :name => name,
+ :comment => "Puppet Testing User"
+ )
+ }
+
+ comp = newcomp("usercomp", user)
+
+ trans = assert_events(comp, [:user_created], "user")
+
+ @@tmpusers << name
+
+ obj = nil
+ assert_nothing_raised {
+ obj = Etc.getpwnam(name)
+ }
+
+ assert_equal("Puppet Testing User", obj.gecos, "Comment was not set")
+
+ tests = Puppet::Type::User.validstates.collect { |name, state|
+ state.name
+ }
+
+ user.retrieve
+ tests.each { |test|
+ if self.respond_to?("attrtest_%s" % test)
+ self.send("attrtest_%s" % test, user)
+ else
+ $stderr.puts "Not testing attr %s of user" % test
+ end
+ }
+
+ assert_rollback_events(trans, [:user_deleted], "user")
+
+ assert_raise(ArgumentError) {
+ Etc.getpwnam(user[:name])
+ }
+ end
+ else
+ $stderr.puts "Not root; skipping user creation/modification tests"
end
end