diff options
27 files changed, 1112 insertions, 1088 deletions
diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index e0c31777b..a460e6024 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -13,8 +13,18 @@ require 'puppet/type/state' module Puppet class Type < Puppet::Element - attr_accessor :children, :parameters, :parent, :implicit + attr_accessor :children, :parameters, :parent attr_accessor :file, :line + + attr_writer :implicit + def implicit? + if defined? @implicit and @implicit + return true + else + return false + end + end + include Enumerable # this is currently unused, but I expect to use it for metrics eventually @@ -513,9 +523,6 @@ class Type < Puppet::Element if stateklass = self.class.validstate?(name) if @states.include?(name) hash.each { |var,value| - if value.is_a?(Puppet::State) - raise "huh?" - end @states[name].send(var.to_s + "=", value) } else @@ -669,6 +676,12 @@ class Type < Puppet::Element end return nil end + + if implicit + obj.implicit = true + end + + return obj end def self.implicitcreate(hash) @@ -787,11 +800,28 @@ class Type < Puppet::Element end next if param == :name or param == self.class.namevar - # FIXME we should really allow equal values, but for now, don't allow - # any values - if oldval = self.should(param) - raise Puppet::Error, "Cannot override %s on %s(%s)" % - [param, self.class.name, self.name] + unless value.is_a?(Array) + value = [value] + end + + # This needs some way to retrieve the original values specified to + # 'should'. + if oldvals = self.should(param) + if oldvals.is_a?(Array) + # take the intersection + newvals = oldvals & value + if newvals.empty? + raise Puppet::Error, "No common values for %s on %s(%s)" % + [param, self.class.name, self.name] + else + Puppet.debug "Reduced old values %s and new values %s to %s" % + [oldvals.inspect, value.inspect, newvals.inspect] + self.should = newvals + end + else + raise Puppet::Error, "Cannot override %s on %s(%s)" % + [param, self.class.name, self.name] + end else self[param] = value end diff --git a/lib/puppet/type/cron.rb b/lib/puppet/type/cron.rb index 274c82ef2..1c9f57caa 100755 --- a/lib/puppet/type/cron.rb +++ b/lib/puppet/type/cron.rb @@ -122,12 +122,11 @@ module Puppet if @is == :notfound #@is = @should event = :cron_created - elsif @should == :notfound - # FIXME I need to actually delete the cronjob... - @parent.destroy + elsif self.should == :notfound + @parent.remove(true) event = :cron_deleted - elsif @should == @is - Puppet.err "Uh, they're both %s" % @should + elsif self.should == @is + Puppet.err "Uh, they're both %s" % self.should return nil else #@is = @should diff --git a/lib/puppet/type/exec.rb b/lib/puppet/type/exec.rb index a86e0207c..634613cc1 100755 --- a/lib/puppet/type/exec.rb +++ b/lib/puppet/type/exec.rb @@ -23,7 +23,7 @@ module Puppet if file = @parent[:creates] if FileTest.exists?(file) @is = true - @should = true + @should = nil return end end diff --git a/lib/puppet/type/group.rb b/lib/puppet/type/group.rb index 4da1b94b1..be838d70e 100755 --- a/lib/puppet/type/group.rb +++ b/lib/puppet/type/group.rb @@ -38,7 +38,7 @@ module Puppet return highest + 1 end - def should=(gid) + def shouldprocess(gid) case gid when String if gid =~ /^[-0-9]+$/ @@ -59,7 +59,7 @@ module Puppet Puppet.info "Setting gid to %s" % gid - @should = gid + return gid end end end diff --git a/lib/puppet/type/nameservice/netinfo.rb b/lib/puppet/type/nameservice/netinfo.rb index 7fde579e7..16148ffbf 100644 --- a/lib/puppet/type/nameservice/netinfo.rb +++ b/lib/puppet/type/nameservice/netinfo.rb @@ -131,7 +131,7 @@ module Puppet [@parent.class.netinfodir, @parent.name] if key = self.class.netinfokey - cmd << key << "'%s'" % @should + cmd << key << "'%s'" % self.should else raise Puppet::DevError, "Could not find netinfokey for state %s" % diff --git a/lib/puppet/type/nameservice/objectadd.rb b/lib/puppet/type/nameservice/objectadd.rb index 0a6842955..f8fba4419 100644 --- a/lib/puppet/type/nameservice/objectadd.rb +++ b/lib/puppet/type/nameservice/objectadd.rb @@ -58,7 +58,7 @@ module Puppet [ "groupmod", self.class.objectaddflag, - "'%s'" % @should, + "'%s'" % self.should, @parent.name ].join(" ") end @@ -100,7 +100,7 @@ module Puppet cmd = [ "usermod", self.class.objectaddflag, - "'%s'" % @should, + "'%s'" % self.should, @parent.name ].join(" ") end diff --git a/lib/puppet/type/nameservice/posix.rb b/lib/puppet/type/nameservice/posix.rb index af62b72ce..8b764ba71 100644 --- a/lib/puppet/type/nameservice/posix.rb +++ b/lib/puppet/type/nameservice/posix.rb @@ -98,18 +98,18 @@ module Puppet def sync event = nil # they're in sync some other way - if @is == @should + if self.insync? return nil end if @is == :notfound self.retrieve - if @is == @should + if self.insync? return nil end end # if the object needs to be created or deleted, # depend on another method to do it all at once - if @is == :notfound or @should == :notfound + if @is == :notfound or self.should == :notfound event = syncname() return event @@ -152,7 +152,7 @@ module Puppet def syncname cmd = nil event = nil - if @should == :notfound + if self.should == :notfound # we need to remove the object... unless @parent.exists? # the group already doesn't exist diff --git a/lib/puppet/type/package.rb b/lib/puppet/type/package.rb index f08410912..6c8057576 100644 --- a/lib/puppet/type/package.rb +++ b/lib/puppet/type/package.rb @@ -24,14 +24,14 @@ module Puppet end end - def should=(value) + def shouldprocess(value) # possible values are: true, false, and a version number case value #when true, /^[0-9]/: when true: - @should = value + return value when false: - @should = :notinstalled + return :notinstalled else raise Puppet::Error.new( "Invalid install value %s" % value @@ -42,7 +42,7 @@ module Puppet def sync method = nil event = nil - case @should + case @should[0] when true: method = :install event = :package_installed @@ -50,7 +50,7 @@ module Puppet method = :remove event = :package_removed else - raise Puppet::Error, "Invalid should value %s" % @should + raise Puppet::Error, "Invalid should value %s" % @should[0] end if @parent.respond_to?(method) diff --git a/lib/puppet/type/pfile.rb b/lib/puppet/type/pfile.rb index 858b1995d..c09ed5de9 100644 --- a/lib/puppet/type/pfile.rb +++ b/lib/puppet/type/pfile.rb @@ -1,7 +1,3 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - require 'digest/md5' require 'cgi' require 'etc' @@ -10,893 +6,19 @@ require 'fileutils' require 'puppet/type/state' require 'puppet/server/fileserver' -module Puppet - # we first define all of the state that our file will use - # because the objects must be defined for us to use them in our - # definition of the file object - class State - class PFileType < Puppet::State - require 'etc' - @doc = "A read-only state to check the file type." - @name = :type - - def should=(value) - raise Puppet::Error, ":type is read-only" - end - - def retrieve - if stat = @parent.stat(true) - @is = stat.ftype - else - @is = :notfound - end - - # so this state is never marked out of sync - @should = @is - end - - - def sync - raise Puppet::Error, ":type is read-only" - end - end - - class PFileCreate < Puppet::State - require 'etc' - @doc = "Whether to create files that don't currently exist. - **false**/*true*/*file*/*directory*" - @name = :create - @event = :file_created - - def should=(value) - # default to just about anything meaning 'true' - case value - when "false", false, nil: - @should = false - when "true", true, "file", "plain", /^f/: - @should = "file" - when "directory", /^d/: - @should = "directory" - when :notfound: - # this is where a creation is being rolled back - @should = :notfound - else - error = Puppet::Error.new "Cannot create files of type %s" % - value - raise error - end - end - - def retrieve - if stat = @parent.stat(true) - @is = stat.ftype - else - @is = :notfound - end - - #Puppet.debug "'exists' state is %s" % self.is - end - - - def sync - event = nil - mode = nil - if mstate = @parent.state(:mode) - mode = mstate.should - end - begin - case @should - when "file": - # just create an empty file - if mode - File.open(@parent[:path],"w", mode) { - } - @parent.delete(:mode) - else - File.open(@parent[:path],"w") { - } - end - event = :file_created - when "directory": - if mode - Dir.mkdir(@parent.name,mode) - @parent.delete(:mode) - else - Dir.mkdir(@parent.name) - end - event = :directory_created - when :notfound: - # this is where the file should be deleted... - unless FileTest.size(@parent.name) == 0 - raise Puppet::Error.new( - "Created file %s has since been modified; cannot roll back." - ) - end - - File.unlink(@parent.name) - else - error = Puppet::Error.new( - "Somehow got told to create a %s file" % @should) - raise error - end - rescue => detail - raise Puppet::Error.new("Could not create %s: %s" % - [@should, detail] - ) - end - return event - end - end - - class PFileChecksum < Puppet::State - attr_accessor :checktype - @doc = "How to check whether a file has changed. **md5**/*lite-md5*/ - *time*/*mtime*" - @name = :checksum - @event = :file_modified - - @unmanaged = true - - def should=(value) - @checktype = value - state = Puppet::Storage.state(self) - if hash = state[@parent[:path]] - if hash.include?(@checktype) - @should = hash[@checktype] - #Puppet.debug "Found checksum %s for %s" % - # [@should,@parent[:path]] - else - #Puppet.debug "Found checksum for %s but not of type %s" % - # [@parent[:path],@checktype] - @should = -2 - end - else - # We can't use :notfound here, because then it'll match on - # non-existent files - @should = -2 - end - end - - def retrieve - unless defined? @checktype - @checktype = "md5" - end - - unless FileTest.exists?(@parent.name) - Puppet.err "File %s does not exist" % @parent.name - self.is = :notfound - return - end - - sum = "" - case @checktype - when "md5", "md5lite": - if FileTest.directory?(@parent[:path]) - #Puppet.info "Cannot MD5 sum directory %s" % - # @parent[:path] - - # because we cannot sum directories, just delete ourselves - # from the file - # is/should so we won't sync - @parent.delete(self.name) - return - else - begin - File.open(@parent[:path]) { |file| - text = nil - if @checktype == "md5" - text = file.read - else - text = file.read(512) - end - if text.nil? - Puppet.info "Not checksumming empty file %s" % - @parent.name - sum = 0 - else - sum = Digest::MD5.hexdigest(text) - end - } - rescue Errno::EACCES => detail - Puppet.notice "Cannot checksum %s: permission denied" % - @parent.name - @parent.delete(self.class.name) - rescue => detail - Puppet.notice "Cannot checksum %s: %s" % - detail - @parent.delete(self.class.name) - end - end - when "timestamp","mtime": - sum = File.stat(@parent[:path]).mtime.to_s - when "time": - sum = File.stat(@parent[:path]).ctime.to_s - else - raise Puppet::Error, "Invalid sum time %s" % @checktype - end - - self.is = sum - - # if we don't have a 'should' value, then go ahead and mark it - if ! defined? @should or @should == -2 - @should = sum - # FIXME we should support an updatechecksums-like mechanism - self.updatesum - end - - #Puppet.debug "checksum state is %s" % self.is - end - - - # at this point, we don't actually modify the system, we modify - # the stored state to reflect the current state, and then kick - # off an event to mark any changes - def sync - if @is.nil? - error = Puppet::Error.new "Checksum state for %s is somehow nil" % - @parent.name - raise error - end - - if @is == :notfound - self.retrieve - - if @is == @should - Puppet.debug "Checksum is already in sync" - return nil - end - #Puppet.debug "%s(%s): after refresh, is '%s'" % - # [self.class.name,@parent.name,@is] - - # if we still can't retrieve a checksum, it means that - # the file still doesn't exist - if @is == :notfound - # if they're copying, then we won't worry about the file - # not existing yet - unless @parent.state(:source) - Puppet.warning "File %s does not exist -- cannot checksum" % - @parent.name - end - return nil - end - end - - if self.updatesum - # set the @should value to the new @is value - # most important for testing - #@should = @is - return :file_modified - else - # set the @should value, because it starts out as nil - #@should = @is - return nil - end - end - - def updatesum - result = false - state = Puppet::Storage.state(self) - unless state.include?(@parent.name) - Puppet.debug "Initializing state hash for %s" % - @parent.name - - state[@parent.name] = Hash.new - end - - if @is == :notfound - error = Puppet::Error.new("%s has invalid checksum" % - @parent.name) - raise error - #elsif @should == :notfound - # error = Puppet::Error.new("%s has invalid 'should' checksum" % - # @parent.name) - # raise error - end - - # if we're replacing, vs. updating - if state[@parent.name].include?(@checktype) - unless defined? @should - raise Puppet::Error.new( - ("@should is not initialized for %s, even though we " + - "found a checksum") % @parent[:path] - ) - end - Puppet.debug "Replacing %s checksum %s with %s" % - [@parent.name, state[@parent.name][@checktype],@is] - #Puppet.debug "@is: %s; @should: %s" % [@is,@should] - result = true - else - Puppet.debug "Creating checksum %s for %s of type %s" % - [self.is,@parent.name,@checktype] - result = false - end - state[@parent.name][@checktype] = @is - return result - end - end - - class PFileUID < Puppet::State - require 'etc' - @doc = "To whom the file should belong. Argument can be user name or - user ID." - @name = :owner - @event = :inode_changed - - def retrieve - # if we're not root, then we can't chown anyway -# unless Process.uid == 0 -# @parent.delete(self.name) -# @should = nil -# @is = nil -# unless defined? @@notifieduid -# Puppet.notice "Cannot manage ownership unless running as root" -# @@notifieduid = true -# return -# end -# end - - unless stat = @parent.stat(true) - @is = :notfound - return - end - - self.is = stat.uid - end - - # If we're not root, we can check the values but we cannot change them - def should=(value) - #unless Process.uid == 0 - # @should = nil - # @is = nil - # unless defined? @@notifieduid - # Puppet.notice "Cannot manage ownership unless running as root" - # #@parent.delete(self.name) - # @@notifieduid = true - # end - # return - #if @parent.state(:owner) - # @parent.delete(:owner) - #end - #raise Puppet::Error.new( - # "Cannot manage ownership unless running as root" - #) - #end - if value.is_a?(Integer) - # verify the user is a valid user - begin - user = Etc.getpwuid(value) - if user.uid == "" - error = Puppet::Error.new( - "Could not retrieve uid for '%s'" % - @parent.name) - raise error - end - rescue ArgumentError => detail - raise Puppet::Error.new("User ID %s does not exist" % - value - ) - rescue => detail - raise Puppet::Error.new( - "Could not find user '%s': %s" % [value, detail]) - raise error - end - else - begin - user = Etc.getpwnam(value) - if user.uid == "" - error = Puppet::Error.new( - "Could not retrieve uid for '%s'" % - @parent.name) - raise error - end - value = user.uid - rescue ArgumentError => detail - raise Puppet::Error.new("User %s does not exist" % - value - ) - rescue => detail - error = Puppet::Error.new( - "Could not find user '%s': %s" % [value, detail]) - raise error - end - end - - @should = value - end - - def sync - unless Process.uid == 0 - unless defined? @@notifieduid - Puppet.notice "Cannot manage ownership unless running as root" - #@parent.delete(self.name) - @@notifieduid = true - end - # there's a possibility that we never got retrieve() called - # e.g., if the file didn't exist - # thus, just delete ourselves now and don't do any work - #@parent.delete(self.name) - return nil - end - - if @is == :notfound - @parent.stat(true) - self.retrieve - #Puppet.debug "%s: after refresh, is '%s'" % [self.class.name,@is] - end - - unless @parent.stat - Puppet.err "File '%s' does not exist; cannot chown" % - @parent[:path] - return nil - end - - begin - File.chown(self.should,nil,@parent[:path]) - rescue => detail - raise Puppet::Error, "Failed to set owner of '%s' to '%s': %s" % - [@parent[:path],self.should,detail] - end - - return :inode_changed - end - end - - # this state should actually somehow turn into many states, - # one for each bit in the mode - # I think MetaStates are the answer, but I'm not quite sure - class PFileMode < Puppet::State - require 'etc' - @doc = "Mode the file should be. Currently relatively limited: - you must specify the exact mode the file should be." - @name = :mode - @event = :inode_changed - - # our modes are octal, so print them in decimal instead - def is_to_s - "%o" % @is - end - - def should_to_s - "%o" % @should - end - - def should=(should) - # this is pretty hackish, but i need to make sure the number is in - # octal, yet the number can only be specified as a string right now - unless should.is_a?(Integer) # i've already converted it correctly - unless should =~ /^0/ - should = "0" + should - end - should = Integer(should) - end - @should = should - if FileTest.exists?(@parent.name) - self.dirfix - end - end - - def dirfix - # if we're a directory, we need to be executable for all cases - # that are readable - if FileTest.directory?(@parent.name) - if @should & 0400 != 0 - @should |= 0100 - end - if @should & 040 != 0 - @should |= 010 - end - if @should & 04 != 0 - @should |= 01 - end - end - - @fixed = true - end - - def retrieve - if stat = @parent.stat(true) - self.is = stat.mode & 007777 - unless defined? @fixed - if defined? @should and @should - self.dirfix - end - end - else - self.is = :notfound - end - - #Puppet.debug "chmod state is %o" % self.is - end - - def sync - if @is == :notfound - @parent.stat(true) - self.retrieve - #Puppet.debug "%s: after refresh, is '%s'" % [self.class.name,@is] - if @is == :notfound - @parent.log "%s does not exist; cannot set mode" % - @parent.name - return nil - end - end - - unless defined? @fixed - self.dirfix - end - - begin - File.chmod(@should,@parent[:path]) - rescue => detail - error = Puppet::Error.new("failed to chmod %s: %s" % - [@parent.name, detail.message]) - raise error - end - return :inode_changed - end - end - - class PFileGroup < Puppet::State - require 'etc' - @doc = "Which group should own the file. Argument can be either group - name or group ID." - @name = :group - @event = :inode_changed - - def retrieve - stat = @parent.stat(true) - - self.is = stat.gid - - # we probably shouldn't actually modify the 'should' value - # but i don't see a good way around it right now - # mmmm, should - #if defined? @should - #else - # @parent.delete(self.name) - #end - end - - def should=(value) - method = nil - gid = nil - gname = nil - - #unless Process.uid == 0 - # unless defined? @@notifiedgroup - # Puppet.notice( - # "Cannot manage group unless running as root" - # ) - # @@notifiedgroup = true - # end - # return - #end -# - if value.is_a?(Integer) - method = :getgrgid - else - method = :getgrnam - end - - begin - group = Etc.send(method,value) - - # at one time, os x was putting the gid into the passwd - # field of the group struct, but that appears to not - # be the case any more - #os = Puppet::Fact["Operatingsystem"] - #case os - #when "Darwin": - # #gid = group.passwd - # gid = group.gid - #else - #end - - gid = group.gid - gname = group.name - - rescue ArgumentError => detail - raise Puppet::Error.new( - "Could not find group %s" % value) - rescue => detail - raise Puppet::Error.new( - "Could not find group %s: %s" % [self.should,detail]) - end - if gid.nil? - raise Puppet::Error.new( - "Could not retrieve gid for %s" % @parent.name) - end - - # now make sure the user is allowed to change to that group - unless Process.uid == 0 - groups = %x{groups}.chomp.split(/\s/) - unless groups.include?(gname) - Puppet.notice "Cannot chgrp: not in group %s" % gname - raise Puppet::Error.new( - "Cannot chgrp: not in group %s" % gname) - end - end - - if gid.nil? - raise Puppet::Error.new( - "Nil gid for %s" % @parent.name) - else - @should = gid - end - 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 - #unless Process.uid == 0 - # unless defined? @@notifiedgroup - # Puppet.notice( - # "Cannot manage group ownership unless running as root" - # ) - # @@notifiedgroup = true - # end - # return nil - #end - - Puppet.debug "setting chgrp state to %s" % self.should - if @is == :notfound - @parent.stat(true) - self.retrieve - #Puppet.debug "%s: after refresh, is '%s'" % [self.class.name,@is] - end - - unless @parent.stat - Puppet.err "File '%s' does not exist; cannot chgrp" % - @parent[:path] - return nil - end - - begin - # set owner to nil so it's ignored - File.chown(nil,self.should,@parent[:path]) - rescue => detail - error = Puppet::Error.new( "failed to chgrp %s to %s: %s" % - [@parent[:path], self.should, detail.message]) - raise error - end - return :inode_changed - end - end - - # Copy files from a local or remote source. - class PFileSource < Puppet::State - attr_accessor :source, :local - @doc = "Copy a file over the current file. Uses `checksum` to - determine when a file should be copied. Valid values are either - fully qualified paths to files, or URIs. Currently supported URI - types are *puppet* and *file*." - @name = :source - - # Ask the file server to describe our file. - def describe - source = @source - - sourceobj, path = @parent.uri2obj(source) - server = sourceobj.server - - begin - desc = server.describe(path) - rescue NetworkClientError => detail - Puppet.err "Could not describe %s: %s" % - [path, detail] - return nil - end - - args = {} - Puppet::Type::PFile::PINPARAMS.zip( - desc.split("\t") - ).each { |param, value| - if value =~ /^[0-9]+$/ - value = value.to_i - end - args[param] = value - } - - # we can't manage ownership as root, so don't even try - unless Process.uid == 0 - args.delete(:owner) - end - - return args - end - - # This basically calls describe() on our file, and then sets all - # of the local states appropriately. If the remote file is a normal - # file then we set it to copy; if it's a directory, then we just mark - # that the local directory should be created. - def retrieve - sum = nil - - # Describe the remote file. - @stats = self.describe - if @stats.nil? or @stats[:type].nil? - @is = :notdescribed - return nil - end - - # Take each of the stats and set them as states on the local file - # if a value has not already been provided. - @stats.each { |stat, value| - next if stat == :checksum - next if stat == :type - - # was the stat already specified, or should the value - # be inherited from the source? - unless @parent.argument?(stat) - if state = @parent.state(stat) - state.should = value - else - @parent[stat] = value - end - end - } - - # If we're a normal file, then set things up to copy the file down. - case @stats[:type] - when "file": - if sum = @parent.state(:checksum) - if sum.is - if sum.is == :notfound - sum.retrieve - end - @is = sum.is - else - @is = :notfound - end - else - @is = :notfound - end - - @should = @stats[:checksum] - - if state = @parent.state(:create) - unless state.should == "file" - Puppet.notice( - "File %s had both create and source enabled" % - @parent.name - ) - @parent.delete(:create) - end - end - # If we're a directory, then do not copy anything, and instead just - # create the directory using the 'create' state. - when "directory": - if state = @parent.state(:create) - unless state.should == "directory" - state.should = "directory" - end - else - @parent[:create] = "directory" - @parent.state(:create).retrieve - end - # we'll let the :create state do our work - @should = true - @is = true - # FIXME We should at least support symlinks, I would think... - else - Puppet.err "Cannot use files of type %s as sources" % - @stats[:type] - @should = true - @is = true - end - end - - # The special thing here is that we need to make sure that 'should' - # is only set for files, not directories. - def should=(source) - if ! defined? @stats or @stats.nil? - @source = source - - # stupid hack for now; it'll get overriden - @should = source - else - if @stats[:type] == "directory" - @should = true - @is = true - else - @source = source - @should = source - end - end - end - - def sync - if @is == :notdescribed - self.retrieve # try again - if @is == :notdescribed - @parent.log "Could not retreive information on %s" % @parent.name - return nil - end - if @is == @should - return nil - end - end - - unless @stats[:type] == "file" - if @stats[:type] == "directory" - [@parent.name, @is.inspect, @should.inspect] - end - raise Puppet::DevError, "Got told to copy non-file %s" % - @parent.name - end - - sourceobj, path = @parent.uri2obj(@source) - - begin - contents = sourceobj.server.retrieve(path) - rescue NetworkClientError => detail - Puppet.err "Could not retrieve %s: %s" % - [path, detail] - return nil - end - - unless sourceobj.server.local - contents = CGI.unescape(contents) - end - - if contents == "" - Puppet.notice "Could not retrieve contents for %s" % - @source - end - - if FileTest.exists?(@parent.name) - # this makes sure we have a copy for posterity - @backed = @parent.handlebackup - end - - # create the file in a tmp location - args = [@parent.name + ".puppettmp", - File::CREAT | File::WRONLY | File::TRUNC] - - # try to create it with the correct modes to start - # we should also be changing our effective uid/gid, but... - if @parent.should(:mode) and @parent.should(:mode) != :notfound - args.push @parent.should(:mode) - end - - # FIXME we should also change our effective user and group id - - begin - File.open(*args) { |f| - f.print contents - } - rescue => detail - # since they said they want a backup, let's error out - # if we couldn't make one - raise Puppet::Error, "Could not create %s to %s: %s" % - [@source, @parent.name, detail.message] - end - - if FileTest.exists?(@parent.name) - begin - File.unlink(@parent.name) - rescue => detail - Puppet.err "Could not remove %s for replacing: %s" % - [@parent.name, detail] - end - end - - begin - File.rename(@parent.name + ".puppettmp", @parent.name) - rescue => detail - Puppet.err "Could not rename tmp %s for replacing: %s" % - [@parent.name, detail] - end - - return :file_changed - end - end - end +# We put all of the states in separate files, because there are so many +# of them. +require 'puppet/type/pfile/pfiletype' +require 'puppet/type/pfile/pfilecreate' +require 'puppet/type/pfile/pfilechecksum' +require 'puppet/type/pfile/pfileuid' +require 'puppet/type/pfile/pfilemode' +require 'puppet/type/pfile/pfilegroup' +require 'puppet/type/pfile/pfilesource' +module Puppet class Type class PFile < Type - # FIXME i don't think these are used - attr_reader :params, :source, :srcbase @doc = "Manages local files, including setting ownership and permissions, and allowing creation of both files and directories." @@ -1044,7 +166,6 @@ module Puppet # default to true self[:backup] = true - @srcbase = nil super end @@ -1098,8 +219,7 @@ module Puppet end def newchild(path, hash = {}) - - #make local copy of arguments + # make local copy of arguments args = @arghash.dup if path =~ %r{^#{File::SEPARATOR}} @@ -1112,25 +232,6 @@ module Puppet args[:path] = path -=begin - # FIXME I think this is obviated now - unless hash.include?(:source) # it's being manually overridden - if args.include?(:source) - Puppet.err "Rewriting source for %s" % path - name = File.basename(path) - dirname = args[:source] - #Puppet.notice "Old: %s" % args[:source] - #Puppet.notice "New: %s" % File.join(dirname,name) - if FileTest.exists?(dirname) and ! FileTest.directory?(dirname) - Puppet.err "Cannot make a child of %s" % dirname - exit - end - args[:source] = File.join(dirname,name) - end - - end -=end - unless hash.include?(:recurse) if args.include?(:recurse) if args[:recurse].is_a?(Integer) @@ -1145,10 +246,6 @@ module Puppet args[key] = value } - #if @states.include?(:checksum) - # args[:checksum] = @states[:checksum].checktype - #end - child = nil klass = nil if @parameters[:linkmaker] and args.include?(:source) and @@ -1166,28 +263,24 @@ module Puppet klass = self.class end - # We know we're creating an implicit child. If the child we're - # trying to create already exists, then we know it should still - # be there, since if it shouldn't be, it would have already been - # removed. Either it's entirely explicit, in which case it - # should win, or it's implicit but resulting from a more explicit - # child. + # The child might already exist because 'localrecurse' runs + # before 'sourcerecurse'. I could push the override stuff into + # a separate method or something, but the work is the same other + # than this last bit, so it doesn't really make sense. if child = klass[path] - Puppet.warning "Not managing more explicit %s" % child - return nil - #unless @children.include?(child) - # raise Puppet::Error, - # "Planned child file %s of %s already exists with parent %s" % - # [path, self.name, child.parent] - #end - #args.each { |var,value| - # next if var == :path - # next if var == :name - # # behave idempotently - # unless child.should(var) == value - # child[var] = value - # end - #} + unless @children.include?(child) + Puppet.notice "Not managing more explicit file %s" % + path + return nil + end + args.each { |var,value| + next if var == :path + next if var == :name + # behave idempotently + unless child.should(var) == value + child[var] = value + end + } else # create it anew #notice "Creating new file with args %s" % args.inspect begin @@ -1218,6 +311,8 @@ module Puppet return child end + # Recurse into the directory. This basically just calls 'localrecurse' + # and maybe 'sourcerecurse'. def recurse recurse = @parameters[:recurse] # we might have a string, rather than a number @@ -1417,8 +512,8 @@ module Puppet end else raise Puppet::Error, - "Got other recursive file proto %s" % uri.scheme - return + "Got other recursive file proto %s from %s" % + [uri.scheme, source] end return [sourceobj, path.sub(/\/\//, '/')] @@ -1432,3 +527,5 @@ module Puppet attr_accessor :mount, :root, :server, :local end end + +# $Id$ diff --git a/lib/puppet/type/pfile/pfilechecksum.rb b/lib/puppet/type/pfile/pfilechecksum.rb new file mode 100755 index 000000000..8747cfeb5 --- /dev/null +++ b/lib/puppet/type/pfile/pfilechecksum.rb @@ -0,0 +1,217 @@ +# Keep a copy of the file checksums, and notify when they change. + +# This state never actually modifies the system, it only notices when the system +# changes on its own. +module Puppet + class State + class PFileChecksum < Puppet::State + @doc = "How to check whether a file has changed. **md5**/*lite-md5*/ + *time*/*mtime*" + @name = :checksum + @event = :file_modified + + @unmanaged = true + + @validtypes = %w{md5 md5lite timestamp mtime time} + + def self.validtype?(type) + @validtypes.include?(type) + end + + def getsum(checktype) + sum = "" + case checktype + when "md5", "md5lite": + unless FileTest.file?(@parent[:path]) + #Puppet.info "Cannot MD5 sum directory %s" % + # @parent[:path] + + # because we cannot sum directories, just delete ourselves + # from the file so we won't sync + @parent.delete(self.name) + return + else + begin + File.open(@parent[:path]) { |file| + text = nil + if checktype == "md5" + text = file.read + else + text = file.read(512) + end + if text.nil? + Puppet.info "Not checksumming empty file %s" % + @parent.name + sum = 0 + else + sum = Digest::MD5.hexdigest(text) + end + } + rescue Errno::EACCES => detail + Puppet.notice "Cannot checksum %s: permission denied" % + @parent.name + @parent.delete(self.class.name) + rescue => detail + Puppet.notice "Cannot checksum %s: %s" % + detail + @parent.delete(self.class.name) + end + end + when "timestamp","mtime": + sum = File.stat(@parent[:path]).mtime.to_s + when "time": + sum = File.stat(@parent[:path]).ctime.to_s + else + raise Puppet::Error, "Invalid sum type %s" % checktype + end + + return sum + end + + # Convert from the sum type to the stored checksum. + def shouldprocess(value) + unless self.class.validtype?(value) + raise Puppet::Error, "Invalid checksum type '%s'" % value + end + state = Puppet::Storage.state(self) + if hash = state[@parent[:path]] + if hash.include?(value) + return hash[value] + #Puppet.debug "Found checksum %s for %s" % + # [self.should,@parent[:path]] + else + #Puppet.debug "Found checksum for %s but not of type %s" % + # [@parent[:path],@checktype] + return :nosum + end + else + # We can't use :notfound here, because then it'll match on + # non-existent files + return :nosum + end + end + + # Even though they can specify multiple checksums, the insync? + # mechanism can really only test against one, so we'll just retrieve + # the first specified sum type. + def retrieve + checktypes = nil + if defined? @shouldorig + checktypes = @shouldorig + else + checktypes = ["md5"] + end + + unless FileTest.exists?(@parent.name) + Puppet.err "File %s does not exist" % @parent.name + self.is = :notfound + return + end + + # Just use the first allowed check type + @checktype = checktypes[0] + + @is = getsum(@checktype) + + # If there is no should defined, then store the current value + # into the 'should' value, so that we're not marked as being + # out of sync. We don't want to generate an event the first + # time we get a sum. + if ! defined? @should or @should == [:nosum] + @should = [@is] + # FIXME we should support an updatechecksums-like mechanism + self.updatesum + end + + #Puppet.debug "checksum state is %s" % self.is + end + + + # At this point, we don't actually modify the system, we modify + # the stored state to reflect the current state, and then kick + # off an event to mark any changes. + def sync + if @is.nil? + raise Puppet::Error, "Checksum state for %s is somehow nil" % + @parent.name + end + + if @is == :notfound + self.retrieve + + if self.insync? + Puppet.debug "Checksum is already in sync" + return nil + end + #Puppet.debug "%s(%s): after refresh, is '%s'" % + # [self.class.name,@parent.name,@is] + + # If we still can't retrieve a checksum, it means that + # the file still doesn't exist + if @is == :notfound + # if they're copying, then we won't worry about the file + # not existing yet + unless @parent.state(:source) + Puppet.warning( + "File %s does not exist -- cannot checksum" % + @parent.name + ) + end + return nil + end + end + + # If the sums are different, then return an event. + if self.updatesum + return :file_modified + else + return nil + end + end + + # Store the new sum to the state db. + def updatesum + result = false + state = Puppet::Storage.state(self) + unless state.include?(@parent.name) + Puppet.debug "Initializing state hash for %s" % + @parent.name + + state[@parent.name] = Hash.new + end + + if @is.is_a?(Symbol) + error = Puppet::Error.new("%s has invalid checksum" % + @parent.name) + raise error + #elsif @should == :notfound + # error = Puppet::Error.new("%s has invalid 'should' checksum" % + # @parent.name) + # raise error + end + + # if we're replacing, vs. updating + if state[@parent.name].include?(@checktype) + unless defined? @should + raise Puppet::Error.new( + ("@should is not initialized for %s, even though we " + + "found a checksum") % @parent[:path] + ) + end + Puppet.debug "Replacing %s checksum %s with %s" % + [@parent.name, state[@parent.name][@checktype],@is] + #Puppet.debug "@is: %s; @should: %s" % [@is,@should] + result = true + else + Puppet.debug "Creating checksum %s for %s of type %s" % + [self.is,@parent.name,@checktype] + result = false + end + state[@parent.name][@checktype] = @is + return result + end + end + end +end + +# $Id$ diff --git a/lib/puppet/type/pfile/pfilecreate.rb b/lib/puppet/type/pfile/pfilecreate.rb new file mode 100755 index 000000000..f8b2f3818 --- /dev/null +++ b/lib/puppet/type/pfile/pfilecreate.rb @@ -0,0 +1,87 @@ +module Puppet + class State + class PFileCreate < Puppet::State + require 'etc' + @doc = "Whether to create files that don't currently exist. + **false**/*true*/*file*/*directory*" + @name = :create + @event = :file_created + + def shouldprocess(value) + # default to just about anything meaning 'true' + case value + when "false", false, nil: + return false + when "true", true, "file", "plain", /^f/: + return "file" + when "directory", /^d/: + return "directory" + when :notfound: + # this is where a creation is being rolled back + return :notfound + else + raise Puppet::Error, "Cannot create files of type %s" % value + end + end + + def retrieve + if stat = @parent.stat(true) + @is = stat.ftype + else + @is = :notfound + end + + #Puppet.debug "'exists' state is %s" % self.is + end + + + def sync + event = nil + mode = @parent.should(:mode) + begin + case self.should + when "file": + # just create an empty file + if mode + File.open(@parent[:path],"w", mode) { + } + @parent.delete(:mode) + else + File.open(@parent[:path],"w") { + } + end + event = :file_created + when "directory": + if mode + Dir.mkdir(@parent.name,mode) + @parent.delete(:mode) + else + Dir.mkdir(@parent.name) + end + event = :directory_created + when :notfound: + # this is where the file should be deleted... + unless FileTest.size(@parent.name) == 0 + raise Puppet::Error.new( + "Created file %s has since been modified; cannot roll back." + ) + end + + File.unlink(@parent.name) + else + error = Puppet::Error.new( + "Somehow got told to create a %s file" % self.should) + raise error + end + rescue => detail + raise Puppet::Error.new("Could not create %s: %s" % + [self.should, detail] + ) + end + return event + end + end + end +end + +# $Id$ diff --git a/lib/puppet/type/pfile/pfilegroup.rb b/lib/puppet/type/pfile/pfilegroup.rb new file mode 100755 index 000000000..f38860259 --- /dev/null +++ b/lib/puppet/type/pfile/pfilegroup.rb @@ -0,0 +1,115 @@ +# Manage file group ownership. +module Puppet + class State + class PFileGroup < Puppet::State + require 'etc' + @doc = "Which group should own the file. Argument can be either group + name or group ID." + @name = :group + @event = :inode_changed + + def retrieve + stat = @parent.stat(true) + + self.is = stat.gid + end + + def shouldprocess(value) + method = nil + gid = nil + gname = nil + + if value.is_a?(Integer) + method = :getgrgid + else + method = :getgrnam + end + + begin + group = Etc.send(method,value) + + # at one time, os x was putting the gid into the passwd + # field of the group struct, but that appears to not + # be the case any more + #os = Puppet::Fact["Operatingsystem"] + #case os + #when "Darwin": + # #gid = group.passwd + # gid = group.gid + #else + #end + + gid = group.gid + gname = group.name + + rescue ArgumentError => detail + raise Puppet::Error.new( + "Could not find group %s" % value) + rescue => detail + raise Puppet::Error.new( + "Could not find group %s: %s" % [self.should,detail]) + end + if gid.nil? + raise Puppet::Error.new( + "Could not retrieve gid for %s" % @parent.name) + end + + # now make sure the user is allowed to change to that group + unless Process.uid == 0 + groups = %x{groups}.chomp.split(/\s/) + unless groups.include?(gname) + Puppet.notice "Cannot chgrp: not in group %s" % gname + raise Puppet::Error.new( + "Cannot chgrp: not in group %s" % gname) + end + end + + if gid.nil? + raise Puppet::Error.new( + "Nil gid for %s" % @parent.name) + else + return gid + end + 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 + #unless Process.uid == 0 + # unless defined? @@notifiedgroup + # Puppet.notice( + # "Cannot manage group ownership unless running as root" + # ) + # @@notifiedgroup = true + # end + # return nil + #end + + if @is == :notfound + @parent.stat(true) + self.retrieve + #Puppet.debug "%s: after refresh, is '%s'" % [self.class.name,@is] + end + + unless @parent.stat + Puppet.err "File '%s' does not exist; cannot chgrp" % + @parent[:path] + return nil + end + + begin + # set owner to nil so it's ignored + File.chown(nil,self.should,@parent[:path]) + rescue => detail + error = Puppet::Error.new( "failed to chgrp %s to %s: %s" % + [@parent[:path], self.should, detail.message]) + raise error + end + return :inode_changed + end + end + end +end + +# $Id$ diff --git a/lib/puppet/type/pfile/pfilemode.rb b/lib/puppet/type/pfile/pfilemode.rb new file mode 100755 index 000000000..b432dc639 --- /dev/null +++ b/lib/puppet/type/pfile/pfilemode.rb @@ -0,0 +1,108 @@ +# Manage file modes. This state should support different formats +# for specification (e.g., u+rwx, or -0011), but for now only supports +# specifying the full mode. +module Puppet + class State + class PFileMode < Puppet::State + require 'etc' + @doc = "Mode the file should be. Currently relatively limited: + you must specify the exact mode the file should be." + @name = :mode + @event = :inode_changed + + # Our modes are octal, so make sure they print correctly. + def is_to_s + "%o" % @is + end + + def should_to_s + "%o" % self.should + end + + def shouldprocess(should) + # this is pretty hackish, but i need to make sure the number is in + # octal, yet the number can only be specified as a string right now + value = should + if value.is_a?(String) + unless value =~ /^0/ + value = "0" + value + end + value = Integer(value) + end + + #Puppet.warning "Should is %o from %s" % [value, should] + + return value + end + + # If we're a directory, we need to be executable for all cases + # that are readable. This should probably be selectable, but eh. + def dirmask(value) + if FileTest.directory?(@parent.name) + if value & 0400 != 0 + value |= 0100 + end + if value & 040 != 0 + value |= 010 + end + if value & 04 != 0 + value |= 01 + end + end + + return value + end + + def retrieve + if stat = @parent.stat(true) + self.is = stat.mode & 007777 + unless defined? @fixed + if defined? @should and @should + @should = @should.collect { |s| self.dirmask(s) } + end + end + else + self.is = :notfound + end + + #Puppet.debug "chmod state is %o" % self.is + end + + def sync + if @is == :notfound + @parent.stat(true) + self.retrieve + #Puppet.debug "%s: after refresh, is '%s'" % [self.class.name,@is] + if @is == :notfound + @parent.log "%s does not exist; cannot set mode" % + @parent.name + return nil + end + + if self.insync? + # we're already in sync + return nil + end + end + + mode = self.should + + if mode == :notfound + # This is really only valid for create states... + return nil + end + + begin + File.chmod(mode,@parent[:path]) + rescue => detail + error = Puppet::Error.new("failed to chmod %s: %s" % + [@parent.name, detail.message]) + raise error + end + return :inode_changed + end + end + end +end + +# $Id$ diff --git a/lib/puppet/type/pfile/pfilesource.rb b/lib/puppet/type/pfile/pfilesource.rb new file mode 100755 index 000000000..ec4f7107b --- /dev/null +++ b/lib/puppet/type/pfile/pfilesource.rb @@ -0,0 +1,257 @@ +module Puppet + class State + # Copy files from a local or remote source. + class PFileSource < Puppet::State + attr_accessor :source, :local + @doc = "Copy a file over the current file. Uses `checksum` to + determine when a file should be copied. Valid values are either + fully qualified paths to files, or URIs. Currently supported URI + types are *puppet* and *file*." + @name = :source + + # Ask the file server to describe our file. + def describe(source) + sourceobj, path = @parent.uri2obj(source) + server = sourceobj.server + + begin + desc = server.describe(path) + rescue NetworkClientError => detail + Puppet.err "Could not describe %s: %s" % + [path, detail] + return nil + end + + args = {} + Puppet::Type::PFile::PINPARAMS.zip( + desc.split("\t") + ).each { |param, value| + if value =~ /^[0-9]+$/ + value = value.to_i + end + args[param] = value + } + + # we can't manage ownership as root, so don't even try + unless Process.uid == 0 + args.delete(:owner) + end + + return args + end + + # This basically calls describe() on our file, and then sets all + # of the local states appropriately. If the remote file is a normal + # file then we set it to copy; if it's a directory, then we just mark + # that the local directory should be created. + def retrieve + sum = nil + + unless defined? @shouldorig + raise Puppet::DevError, "No sources defined for %s" % + @parent.name + end + + @source = nil + + # Find the first source that exists. @shouldorig contains + # the sources as specified by the user. + @shouldorig.each { |source| + if @stats = self.describe(source) + @source = source + break + end + } + + if @stats.nil? or @stats[:type].nil? + @is = :notdescribed + @source = nil + return nil + end + + # Take each of the stats and set them as states on the local file + # if a value has not already been provided. + @stats.each { |stat, value| + next if stat == :checksum + next if stat == :type + + # was the stat already specified, or should the value + # be inherited from the source? + unless @parent.argument?(stat) + if state = @parent.state(stat) + state.should = value + else + @parent[stat] = value + end + end + } + + # If we're a normal file, then set things up to copy the file down. + case @stats[:type] + when "file": + if sum = @parent.state(:checksum) + if sum.is + if sum.is == :notfound + sum.retrieve + end + @is = sum.is + else + @is = :notfound + end + else + @is = :notfound + end + + @should = [@stats[:checksum]] + + if state = @parent.state(:create) + unless state.should == "file" + Puppet.notice( + "File %s had both create and source enabled" % + @parent.name + ) + @parent.delete(:create) + end + end + # If we're a directory, then do not copy anything, and instead just + # create the directory using the 'create' state. + when "directory": + if state = @parent.state(:create) + unless state.should == "directory" + state.should = "directory" + end + else + @parent[:create] = "directory" + @parent.state(:create).retrieve + end + # we'll let the :create state do our work + @should = nil + @is = true + # FIXME We should at least support symlinks, I would think... + else + Puppet.err "Cannot use files of type %s as sources" % + @stats[:type] + @should = nil + @is = true + end + end + + # The special thing here is that we need to make sure that 'should' + # is only set for files, not directories. The processing we're doing + # here doesn't really matter, because the @should values will be + # overridden when we 'retrieve'. + def shouldprocess(source) + unless @parent.uri2obj(source) + raise Puppet::Error, "Invalid source %s" % source + end + + if ! defined? @stats or @stats.nil? + # stupid hack for now; it'll get overriden + return source + else + if @stats[:type] == "directory" + @is = true + return nil + else + return source + end + end + end + + def sync + if @is == :notdescribed + self.retrieve # try again + if @is == :notdescribed + @parent.log "Could not retreive information on %s" % + @parent.name + return nil + end + if @is == @should + return nil + end + end + + unless @stats[:type] == "file" + if @stats[:type] == "directory" + [@parent.name, @is.inspect, @should.inspect] + end + raise Puppet::DevError, "Got told to copy non-file %s" % + @parent.name + end + + unless defined? @source + raise Puppet::DevError, "Somehow source is still undefined" + end + + sourceobj, path = @parent.uri2obj(@source) + + begin + contents = sourceobj.server.retrieve(path) + rescue NetworkClientError => detail + Puppet.err "Could not retrieve %s: %s" % + [path, detail] + return nil + end + + # FIXME It's stupid that this isn't taken care of in the + # protocol. + unless sourceobj.server.local + contents = CGI.unescape(contents) + end + + if contents == "" + Puppet.notice "Could not retrieve contents for %s" % + @source + end + + if FileTest.exists?(@parent.name) + # this makes sure we have a copy for posterity + @backed = @parent.handlebackup + end + + # create the file in a tmp location + args = [@parent.name + ".puppettmp", + File::CREAT | File::WRONLY | File::TRUNC] + + # try to create it with the correct modes to start + # we should also be changing our effective uid/gid, but... + if @parent.should(:mode) and @parent.should(:mode) != :notfound + args.push @parent.should(:mode) + end + + # FIXME we should also change our effective user and group id + + begin + File.open(*args) { |f| + f.print contents + } + rescue => detail + # since they said they want a backup, let's error out + # if we couldn't make one + raise Puppet::Error, "Could not create %s to %s: %s" % + [@source, @parent.name, detail.message] + end + + if FileTest.exists?(@parent.name) + begin + File.unlink(@parent.name) + rescue => detail + Puppet.err "Could not remove %s for replacing: %s" % + [@parent.name, detail] + end + end + + begin + File.rename(@parent.name + ".puppettmp", @parent.name) + rescue => detail + Puppet.err "Could not rename tmp %s for replacing: %s" % + [@parent.name, detail] + end + + return :file_changed + end + end + end +end + +# $Id$ diff --git a/lib/puppet/type/pfile/pfiletype.rb b/lib/puppet/type/pfile/pfiletype.rb new file mode 100755 index 000000000..1d03634e7 --- /dev/null +++ b/lib/puppet/type/pfile/pfiletype.rb @@ -0,0 +1,31 @@ +module Puppet + class State + class PFileType < Puppet::State + require 'etc' + @doc = "A read-only state to check the file type." + @name = :type + + def shouldprocess(value) + raise Puppet::Error, ":type is read-only" + end + + def retrieve + if stat = @parent.stat(true) + @is = stat.ftype + else + @is = :notfound + end + + # so this state is never marked out of sync + @should = [@is] + end + + + def sync + raise Puppet::Error, ":type is read-only" + end + end + end +end + +# $Id$ diff --git a/lib/puppet/type/pfile/pfileuid.rb b/lib/puppet/type/pfile/pfileuid.rb new file mode 100755 index 000000000..176988eb1 --- /dev/null +++ b/lib/puppet/type/pfile/pfileuid.rb @@ -0,0 +1,103 @@ +module Puppet + class State + class PFileUID < Puppet::State + require 'etc' + @doc = "To whom the file should belong. Argument can be user name or + user ID." + @name = :owner + @event = :inode_changed + + def retrieve + unless stat = @parent.stat(true) + @is = :notfound + return + end + + self.is = stat.uid + end + + # If we're not root, we can check the values but we cannot change them + def shouldprocess(value) + if value.is_a?(Integer) + # verify the user is a valid user + begin + user = Etc.getpwuid(value) + if user.uid == "" + error = Puppet::Error.new( + "Could not retrieve uid for '%s'" % + @parent.name) + raise error + end + rescue ArgumentError => detail + raise Puppet::Error.new("User ID %s does not exist" % + value + ) + rescue => detail + raise Puppet::Error.new( + "Could not find user '%s': %s" % [value, detail]) + raise error + end + else + begin + user = Etc.getpwnam(value) + if user.uid == "" + error = Puppet::Error.new( + "Could not retrieve uid for '%s'" % + @parent.name) + raise error + end + value = user.uid + rescue ArgumentError => detail + raise Puppet::Error.new("User %s does not exist" % + value + ) + rescue => detail + error = Puppet::Error.new( + "Could not find user '%s': %s" % [value, detail]) + raise error + end + end + + return value + end + + def sync + unless Process.uid == 0 + unless defined? @@notifieduid + Puppet.notice "Cannot manage ownership unless running as root" + #@parent.delete(self.name) + @@notifieduid = true + end + # there's a possibility that we never got retrieve() called + # e.g., if the file didn't exist + # thus, just delete ourselves now and don't do any work + #@parent.delete(self.name) + return nil + end + + if @is == :notfound + @parent.stat(true) + self.retrieve + #Puppet.debug "%s: after refresh, is '%s'" % [self.class.name,@is] + end + + unless @parent.stat + Puppet.err "File '%s' does not exist; cannot chown" % + @parent[:path] + return nil + end + + begin + File.chown(self.should,nil,@parent[:path]) + rescue => detail + raise Puppet::Error, "Failed to set owner of '%s' to '%s': %s" % + [@parent[:path],self.should,detail] + end + + return :inode_changed + end + end + end +end + +# $Id$ diff --git a/lib/puppet/type/service.rb b/lib/puppet/type/service.rb index 4308291fa..b98764f48 100644 --- a/lib/puppet/type/service.rb +++ b/lib/puppet/type/service.rb @@ -6,6 +6,7 @@ module Puppet class State + # Handle whether the service is started at boot time. class ServiceEnabled < State @doc = "Whether a service should be enabled to start at boot. **true**/*false*/*runlevel*" @@ -15,17 +16,17 @@ module Puppet @is = @parent.enabled? end - def should=(should) + def shouldprocess(should) case should - when true: @should = :enabled - when false: @should = :disabled + when true: return :enabled + when false: return :disabled else raise Puppet::Error, "Invalid 'enabled' value %s" % should end end def sync - case @should + case self.should when :enabled unless @parent.respond_to?(:enable) raise Puppet::Error, "Service %s does not support enabling" @@ -43,13 +44,12 @@ module Puppet end end + # Handle whether the service should actually be running right now. class ServiceRunning < State @doc = "Whether a service should be running. **true**/*false*" @name = :running - # this whole thing is annoying - # i should probably just be using booleans, but for now, i'm not... - def should=(should) + def shouldprocess(should) case should when false,0,"0", "stopped", :stopped: should = :stopped @@ -61,7 +61,7 @@ module Puppet should = 0 end Puppet.debug "Service should is %s" % should - @should = should + return should end def retrieve @@ -72,21 +72,21 @@ module Puppet def sync event = nil - if @should == :running + case self.should + when :running @parent.start event = :service_started - elsif @should == :stopped + when :stopped @parent.stop event = :service_stopped else Puppet.debug "Not running '%s' and shouldn't be running" % self end - - return event end end end + class Type class Service < Type attr_reader :stat diff --git a/lib/puppet/type/state.rb b/lib/puppet/type/state.rb index 0d5d84b6f..7f74f7344 100644 --- a/lib/puppet/type/state.rb +++ b/lib/puppet/type/state.rb @@ -1,63 +1,26 @@ -#!/usr/local/bin/ruby -w - -# $Id$ +# The virtual base class for states, which are the self-contained building +# blocks for actually doing work on the system. require 'puppet' require 'puppet/element' require 'puppet/statechange' -#--------------------------------------------------------------- -# this is a virtual base class for states -# states are self-contained building blocks for objects - -# States can currently only be used for comparing a virtual "should" value -# against the real state of the system. For instance, you could verify that -# a file's owner is what you want, but you could not create two file objects -# and use these methods to verify that they have the same owner module Puppet class State < Puppet::Element - attr_accessor :is, :should, :parent + attr_accessor :is, :parent + + # Because 'should' uses an array, we have a special method for handling + # it. We also want to keep copies of the original values, so that + # they can be retrieved and compared later when merging. + attr_reader :should, :shouldorig @virtual = true class << self attr_accessor :unmanaged + attr_reader :name end - #--------------------------------------------------------------- - # which event gets generated if this state change happens; not currently - # called - def self.generates - return @event - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # every state class must tell us what its name will be (as a symbol) - # this determines how we will refer to the state during usage - # e.g., the Owner state for Files might say its name is :owner; - # this means that we can say "file[:owner] = 'yayness'" - def self.name - return @name - end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- - # if we're not in sync, return a statechange capable of putting us - # in sync - #def evaluate - # #debug "evaluating %s" % self - # self.retrieve - # if self.insync? - # #debug "%s is in sync" % self - # return nil - # else - # return Puppet::StateChange.new(self) - # end - #end - #--------------------------------------------------------------- - - #--------------------------------------------------------------- # initialize our state def initialize(hash) @is = nil @@ -75,23 +38,35 @@ class State < Puppet::Element self.is = hash[:is] end end - #--------------------------------------------------------------- # Determine whether the state is in-sync or not. If @should is # not defined or is set to a non-true value, then we do not have # a valid value for it and thus consider the state to be in-sync - # since we cannot fix it. + # since we cannot fix it. Otherwise, we expect our should value + # to be an array, and if @is matches any of those values, then + # we consider it to be in-sync. def insync? #debug "%s value is '%s', should be '%s'" % # [self,self.is.inspect,self.should.inspect] unless defined? @should and @should return true end - self.is == self.should + + unless @should.is_a?(Array) + raise Puppet::DevError, "%s's should is not array" % self.class.name + end + + # Look for a matching value + @should.each { |val| + if @is == val + return true + end + } + + # otherwise, return false + return false end - #--------------------------------------------------------------- - #--------------------------------------------------------------- # each state class must define the name() method, and state instances # do not change that name # this implicitly means that a given object can only have one state @@ -99,9 +74,7 @@ class State < Puppet::Element def name return self.class.name end - #--------------------------------------------------------------- - #--------------------------------------------------------------- # for testing whether we should actually do anything def noop unless defined? @noop @@ -111,18 +84,37 @@ class State < Puppet::Element #debug "noop is %s" % tmp return tmp end - #--------------------------------------------------------------- - #--------------------------------------------------------------- # return the full path to us, for logging and rollback; not currently # used def path return [@parent.path, self.name].flatten end - #--------------------------------------------------------------- + # Only return the first value + def should + return @should[0] + end + + # Set the should value. + def should=(values) + unless values.is_a?(Array) + values = [values] + end + + @shouldorig = values - #--------------------------------------------------------------- + if self.respond_to?(:shouldprocess) + @should = values.collect { |val| + self.shouldprocess(val) + } + else + @should = values + end + end + + + # How should a state change be printed as a string? def change_to_s if @is == :notfound return "%s: defined '%s' as '%s'" % @@ -135,8 +127,7 @@ class State < Puppet::Element [self.parent, self.name, self.is_to_s, self.should_to_s] end end - #--------------------------------------------------------------- - #--------------------------------------------------------------- + # because the @should and @is vars might be in weird formats, # we need to set up a mechanism for pretty printing of the values # default to just the values, but this way individual states can @@ -148,12 +139,11 @@ class State < Puppet::Element def should_to_s @should end - #--------------------------------------------------------------- - #--------------------------------------------------------------- def to_s return "%s(%s)" % [@parent.name,self.name] end - #--------------------------------------------------------------- end end + +# $Id$ diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb index b88bc5c5a..9db7173a7 100755 --- a/lib/puppet/type/user.rb +++ b/lib/puppet/type/user.rb @@ -33,7 +33,7 @@ module Puppet return highest + 1 end - def should=(value) + def shouldprocess(value) case value when String if value =~ /^[-0-9]+$/ @@ -49,7 +49,7 @@ module Puppet end end - @should = value + return value end end @@ -63,7 +63,7 @@ module Puppet :gid end - def should=(gid) + def shouldprocess(gid) method = :getgrgid case gid when String @@ -77,8 +77,7 @@ module Puppet raise Puppet::DevError, "Invalid GID %s" % gid end # these are treated specially by sync() - @should = gid - return + return gid end # FIXME this should really check to see if we already have a @@ -92,7 +91,7 @@ module Puppet end Puppet.notice "setting gid to %s" % ginfo.gid.inspect - @should = ginfo.gid + return ginfo.gid end end diff --git a/test/certmgr/tc_certmgr.rb b/test/certmgr/tc_certmgr.rb index b67ff920d..ec1cb62c4 100755 --- a/test/certmgr/tc_certmgr.rb +++ b/test/certmgr/tc_certmgr.rb @@ -221,7 +221,7 @@ class TestCertMgr < Test::Unit::TestCase def test_interactiveca ca = nil Puppet[:ssldir] = "/tmp/puppetinteractivecatest" - @@tmpfiles.push Puppet[:ssldir] + @@tmpfiles << Puppet[:ssldir] assert_nothing_raised { ca = Puppet::SSLCertificates::CA.new diff --git a/test/puppettest.rb b/test/puppettest.rb index f18675549..6b91f1049 100644 --- a/test/puppettest.rb +++ b/test/puppettest.rb @@ -1,4 +1,3 @@ -# $Id$ require 'test/unit' libdir = File.join(File.dirname(__FILE__), '../lib') @@ -360,7 +359,12 @@ class FileTesting < TestPuppet } tolist = file_list(todir).sort - assert_equal(fromlist,tolist) + fromlist.sort.zip(tolist.sort).each { |a,b| + assert_equal(a, b, + "Fromfile %s with length %s does not match tofile %s with length %s" % + [a, fromlist.length, b, tolist.length]) + } + #assert_equal(fromlist,tolist) # and then do some verification that the files are actually set up # the same @@ -404,12 +408,14 @@ class FileTesting < TestPuppet end def delete_random_files(dir) + deleted = [] random_files(dir) { |file| stat = File.stat(file) begin if stat.ftype == "directory" false else + deleted << file File.unlink(file) true end @@ -419,6 +425,8 @@ class FileTesting < TestPuppet false end } + + return deleted end def add_random_files(dir) @@ -549,3 +557,5 @@ def failers yield file } end + +# $Id$ diff --git a/test/server/tc_bucket.rb b/test/server/tc_bucket.rb index 8a7e71511..49f01287a 100644 --- a/test/server/tc_bucket.rb +++ b/test/server/tc_bucket.rb @@ -196,6 +196,7 @@ class TestBucket < ServerTest files = filelist() tmpdir = File.join(tmpdir(),"tmpfiledir") + @@tmpfiles << tmpdir FileUtils.mkdir_p(tmpdir) Puppet[:autosign] = true diff --git a/test/server/tc_master.rb b/test/server/tc_master.rb index 4fb8df8b2..f4d5d967b 100644 --- a/test/server/tc_master.rb +++ b/test/server/tc_master.rb @@ -105,6 +105,7 @@ class TestMaster < ServerTest manifest = mktestmanifest() file2 = @createdfile + "2" + @@tmpfiles << file2 client = master = nil assert_nothing_raised() { diff --git a/test/types/tc_basic.rb b/test/types/tc_basic.rb index f0257fef0..6ba91f076 100644 --- a/test/types/tc_basic.rb +++ b/test/types/tc_basic.rb @@ -5,15 +5,15 @@ if __FILE__ == $0 end require 'puppet' +require 'puppettest' require 'test/unit' -# $Id$ - -class TestBasic < Test::Unit::TestCase +class TestBasic < TestPuppet # hmmm # this is complicated, because we store references to the created # objects in a central store def setup + super @component = nil @configfile = nil @sleeper = nil @@ -29,7 +29,7 @@ class TestBasic < Test::Unit::TestCase assert_nothing_raised() { @filepath = "/tmp/testfile" - system("rm -f %s" % @filepath) + @@tmpfiles << @filepath @configfile = Puppet::Type::PFile.create( :path => @filepath, :create => true, @@ -55,11 +55,6 @@ class TestBasic < Test::Unit::TestCase #puts "ConfigFile is %s, id %s" % [@configfile, @configfile.object_id] end - def teardown - Puppet::Type.allclear - system("rm -f %s" % @filepath) - end - def test_name_calls [@sleeper,@configfile].each { |obj| Puppet.debug "obj is %s" % obj @@ -118,3 +113,5 @@ class TestBasic < Test::Unit::TestCase } end end + +# $Id$ diff --git a/test/types/tc_file.rb b/test/types/tc_file.rb index 8b19dc670..ec357dcc0 100644 --- a/test/types/tc_file.rb +++ b/test/types/tc_file.rb @@ -1,8 +1,7 @@ if __FILE__ == $0 $:.unshift '..' $:.unshift '../../lib' - $:.unshift "../../../../language/trunk/lib" - $puppetbase = "../../../../language/trunk" + $puppetbase = "../.." end require 'puppet' @@ -10,8 +9,6 @@ require 'test/unit' require 'fileutils' require 'puppettest' -# $Id$ - class TestFile < FileTesting # hmmm # this is complicated, because we store references to the created @@ -500,3 +497,5 @@ class TestFile < FileTesting } end end + +# $Id$ diff --git a/test/types/tc_filebucket.rb b/test/types/tc_filebucket.rb index 6c57c8dab..a8ba7a8c3 100755 --- a/test/types/tc_filebucket.rb +++ b/test/types/tc_filebucket.rb @@ -125,6 +125,7 @@ class TestFileBucket < FileTesting } opath = "/tmp/anotherbuckettest" + @@tmpfiles << opath system("cp /etc/passwd %s" % opath) origmd5 = File.open(file.name) { |f| newmd5 = Digest::MD5.hexdigest(f.read) } diff --git a/test/types/tc_filesources.rb b/test/types/tc_filesources.rb index 0de4157bc..031e1b76e 100755 --- a/test/types/tc_filesources.rb +++ b/test/types/tc_filesources.rb @@ -11,28 +11,7 @@ require 'test/unit' require 'fileutils' require 'puppettest' -# $Id$ - class TestFileSources < FileTesting - -=begin - def mkfile(hash) - file = nil - assert_nothing_raised { - file = Puppet::Type::PFile.create(hash) - } - return file - end - - def mktestfile - # because luke's home directory is on nfs, it can't be used for testing - # as root - tmpfile = tempfile() - File.open(tmpfile, "w") { |f| f.puts rand(100) } - @@tmpfiles.push tmpfile - mkfile(:name => tmpfile) - end -=end def setup begin initstorage @@ -83,10 +62,6 @@ class TestFileSources < FileTesting child = file.newchild("childtest") } assert(child) - assert_nothing_raised { - child = file.newchild("childtest") - } - assert(child) assert_raise(Puppet::DevError) { file.newchild(File.join(path,"childtest")) } @@ -133,6 +108,7 @@ class TestFileSources < FileTesting end def recursive_source_test(fromdir, todir) + Puppet::Type.allclear initstorage tofile = nil trans = nil @@ -196,11 +172,15 @@ class TestFileSources < FileTesting fromdir, todir = run_complex_sources # then delete some files assert(FileTest.exists?(todir)) - delete_random_files(todir) + missing_files = delete_random_files(todir) # and run recursive_source_test(fromdir, todir) + missing_files.each { |file| + assert(FileTest.exists?(file), "Deleted file %s is still missing" % file) + } + # and make sure they're still equal assert_trees_equal(fromdir,todir) end @@ -547,3 +527,5 @@ class TestFileSources < FileTesting assert(!FileTest.exists?(name), "File with no source exists anyway") end end + +# $Id$ |