From b77d29568e8adaa0104d8ee5b4c168ef6e3c4bdd Mon Sep 17 00:00:00 2001 From: luke Date: Wed, 7 Sep 2005 02:53:19 +0000 Subject: adding user and group classes (although user class is not yet functional), and added "is(state)" and "should(state)" methods for retrieving the respective values on a specified state git-svn-id: https://reductivelabs.com/svn/puppet/trunk@630 980ebf18-57e1-0310-9a29-db15c13687c0 --- lib/puppet/type.rb | 24 ++++++ lib/puppet/type/group.rb | 156 ++++++++++++++++++++++++++++++++++++ lib/puppet/type/user.rb | 205 +++++++++++++++++++++++++++++++++++++++++++++++ test/types/tc_group.rb | 99 +++++++++++++++++++++++ test/types/tc_user.rb | 98 ++++++++++++++++++++++ 5 files changed, 582 insertions(+) create mode 100755 lib/puppet/type/group.rb create mode 100755 lib/puppet/type/user.rb create mode 100755 test/types/tc_group.rb create mode 100755 test/types/tc_user.rb diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 509a10333..4a1afd91d 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -575,6 +575,28 @@ class Type < Puppet::Element end #--------------------------------------------------------------- + #--------------------------------------------------------------- + # retrieve the 'is' value for a specified state + def is(state) + if @states.include?(state) + return @states[state].is + else + return nil + end + end + #--------------------------------------------------------------- + + #--------------------------------------------------------------- + # retrieve the 'should' value for a specified state + def should(state) + if @states.include?(state) + return @states[state].should + else + return nil + end + end + #--------------------------------------------------------------- + #--------------------------------------------------------------- # is the instance a managed instance? A 'yes' here means that # the instance was created from the language, vs. being created @@ -1219,11 +1241,13 @@ end require 'puppet/statechange' require 'puppet/type/component' require 'puppet/type/exec' +require 'puppet/type/group' require 'puppet/type/package' require 'puppet/type/pfile' require 'puppet/type/pfilebucket' require 'puppet/type/service' require 'puppet/type/symlink' +require 'puppet/type/user' require 'puppet/type/tidy' #require 'puppet/type/typegen' #require 'puppet/type/typegen/filetype' diff --git a/lib/puppet/type/group.rb b/lib/puppet/type/group.rb new file mode 100755 index 000000000..38a56dc31 --- /dev/null +++ b/lib/puppet/type/group.rb @@ -0,0 +1,156 @@ +# $Id$ + +require 'etc' +require 'facter' +require 'puppet/type/state' + +module Puppet + class State + class GroupState < Puppet::State + class << self + def infomethod + if defined? @infomethod and @infomethod + return @infomethod + else + return @name + end + end + end + + def retrieve + obj = @parent.getinfo(true) + + method = self.class.infomethod + + @is = obj.send(method) + end + + def sync + obj = @parent.getinfo + + cmd = nil + event = nil + if @should == :notfound + # we need to remove the object... + if obj.nil? + # the group already doesn't exist + return nil + end + + cmd = ["groupdel", @parent.name] + type = "delete" + else + unless obj.nil? + 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 = ["groupadd"] + if gid = @parent.should(:gid) + cmd << "-g" << gid + end + 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 "group_#{type}d".intern + 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 + + 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 + end + + class Type + class Group < Type + @states = [ + Puppet::State::GroupGID + ] + + @parameters = [ + :name + ] + + @doc = " " + @name = :group + @namevar = :name + + def getinfo(refresh = false) + if @groupinfo.nil? or refresh == true + begin + @groupinfo = Etc.getgrnam(self[:name]) + rescue ArgumentError => detail + # leave groupinfo as nil + end + end + + @groupinfo + end + + def initialize(hash) + @groupinfo = nil + super + end + + def retrieve + obj = self.getinfo(true) + + if obj.nil? + # 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 @states.include?(:gid) or self.noop + highest = 0 + Etc.group { |group| + if group.gid > highest + unless group.gid > 65000 + highest = group.gid + end + end + } + + self[:gid] = highest + 1 + end + + @states.each { |name, state| + state.is = :notfound + } + + return + else + super + end + end + end + end +end diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb new file mode 100755 index 000000000..cc7bbadaa --- /dev/null +++ b/lib/puppet/type/user.rb @@ -0,0 +1,205 @@ +# $Id$ + +require 'etc' +require 'facter' +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 + end + + @@userinfo + end + end + + def retrieve + info = self.class.getinfo(true) + + method = self.class.infomethod || self.class.name + + unless method + raise Puppet::DevError, + "Could not retrieve info method for state %s" % self.class.name + end + + unless info.respond_to?(method) + raise Puppet::DevError, "UserInfo object does not respond to %s" % + method + end + + @is = info.send(method) + end + + def sync + end + end + + class UserUID < UserState + @doc = "The user ID. Must be specified numerically. For new users being + created, if no user ID is specified then one will be chosen + automatically, which will likely result in the same user having + different IDs on different systems, which is not recommended." + @name = :uid + end + + class UserGID < UserState + @doc = "The user's primary group. Can be specified numerically or by name." + @name = :gid + + def should=(gid) + method = :getgrgid + if gid.is_a?(String) + if gid =~ /^[0-9]+$/ + gid = Integer(gid) + else + method = :getgrnam + end + end + + # FIXME this should really check to see if we already have a group + # ready to be managed; if so, then we should just mark it as a prereq + begin + ginfo = Etc.send(method, gid) + rescue ArgumentError => detail + raise Puppet::Error, "Could not find group %s: %s" % [gid, detail] + end + + @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." + @name = :comment + + def retrieve + end + + def sync + return :executed_command + end + end + + class UserHome < UserState + @doc = "The expected return code. An error will be returned if the + executed command returns something else." + @name = :home + + def retrieve + end + + def sync + return :executed_command + end + end + + class UserShell < UserState + @doc = "The expected return code. An error will be returned if the + executed command returns something else." + @name = :shell + + def retrieve + end + + def sync + return :executed_command + end + end + + 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 + + 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 + end + + 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 + end + + end + + class Type + class User < Type + @states = [ + Puppet::State::UserUID, + Puppet::State::UserGID, + Puppet::State::UserComment, + Puppet::State::UserHome, + Puppet::State::UserShell, + Puppet::State::UserLocked, + Puppet::State::UserExpire, + Puppet::State::UserInactive + ] + + @parameters = [ + :name + ] + + @doc = " + " + @name = :user + @namevar = :name + + def retrieve + info = Puppet::State::UserState.getinfo + + if info == :notfound + # the user does not exist + @states.each { |name, state| + state.is = :notfound + } + return + else + super + end + end + end + end +end diff --git a/test/types/tc_group.rb b/test/types/tc_group.rb new file mode 100755 index 000000000..7f3c1d01f --- /dev/null +++ b/test/types/tc_group.rb @@ -0,0 +1,99 @@ +if __FILE__ == $0 + $:.unshift '..' + $:.unshift '../../lib' + $puppetbase = "../../../../language/trunk" +end + +# $Id$ + +require 'etc' +require 'puppet/type' +require 'puppettest' +require 'test/unit' + +class TestGroup < TestPuppet + def setup + Puppet[:loglevel] = :debug if __FILE__ == $0 + super + end + + def groupnames + %x{groups}.chomp.split(/ /) + end + + def groupids + Process.groups + end + + def test_eachmethod + obj = Etc.getgrnam(groupnames()[0]) + + assert(obj, "Could not retrieve test group object") + + Puppet::Type::Group.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 + + def test_owngroups + groupnames().each { |group| + gobj = nil + comp = nil + assert_nothing_raised { + gobj = Puppet::Type::Group.new( + :name => group, + :check => [:gid] + ) + + comp = newcomp("grouptest %s" % group, gobj) + } + + trans = nil + assert_nothing_raised { + trans = comp.evaluate + } + + assert(gobj.is(:gid), "Failed to retrieve gid") + } + end + + if Process.uid == 0 + def test_mkgroup + gobj = nil + comp = nil + name = "pptestgr" + assert_nothing_raised { + gobj = Puppet::Type::Group.new( + :name => name + ) + + comp = newcomp("groupmaker %s" % name, gobj) + } + + trans = nil + assert_nothing_raised { + trans = comp.evaluate + } + + events = nil + assert_nothing_raised { + events = trans.evaluate.reject { |e| e.nil? }.collect { |e| e.event } + } + + assert_equal([:group_created], events, "Incorrect group events") + + assert_nothing_raised { + events = trans.rollback.reject { |e| e.nil? }.collect { |e| e.event } + } + + assert_equal([:group_deleted], events, "Incorrect deletion group events") + end + else + $stderr.puts "Not running as root; skipping group creation tests." + end +end diff --git a/test/types/tc_user.rb b/test/types/tc_user.rb new file mode 100755 index 000000000..3522539d5 --- /dev/null +++ b/test/types/tc_user.rb @@ -0,0 +1,98 @@ +if __FILE__ == $0 + $:.unshift '..' + $:.unshift '../../lib' + $puppetbase = "../../../../language/trunk" +end + +# $Id$ + +require 'puppet/type' +require 'test/unit' + +class TestType < Test::Unit::TestCase + def test_typemethods + assert_nothing_raised() { + Puppet::Type.buildstatehash + } + + Puppet::Type.eachtype { |type| + name = nil + assert_nothing_raised() { + name = type.name + } + + assert( + name + ) + + assert_equal( + type, + Puppet::Type.type(name) + ) + + assert( + type.namevar + ) + + assert_not_nil( + type.states + ) + + assert_not_nil( + type.validstates + ) + + assert( + type.validparameter?(type.namevar) + ) + } + 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" + ) + } + assert_nothing_raised() { + file.retrieve + } + assert_nothing_raised() { + file.sync + } + 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_nothing_raised() { + file.retrieve + } + assert_nothing_raised() { + file[:path] + } + assert_nothing_raised() { + file["path"] + } + assert_nothing_raised() { + file[:recurse] + } + assert_nothing_raised() { + file["recurse"] + } + assert_nothing_raised() { + file.sync + } + end +end -- cgit