diff --git a/lib/puppet/autoload.rb b/lib/puppet/autoload.rb index 7e880b922..2604b3c91 100644 --- a/lib/puppet/autoload.rb +++ b/lib/puppet/autoload.rb @@ -1,94 +1,96 @@ # Autoload paths, either based on names or all at once. class Puppet::Autoload include Puppet::Util @autoloaders = {} attr_accessor :object, :path, :objwarn, :wrap class << self attr_reader :autoloaders private :autoloaders end Puppet::Util.classproxy self, :autoloaders, "[]", "[]=", :clear attr_reader :loaded private :loaded Puppet::Util.proxy self, :loaded, :clear def initialize(obj, path, options = {}) @path = path.to_s @object = obj self.class[obj] = self options.each do |opt, value| opt = opt.intern if opt.is_a? String begin self.send(opt.to_s + "=", value) rescue NoMethodError raise ArgumentError, "%s is not a valid option" % opt end end unless defined? @wrap @wrap = true end @loaded = {} end def load(name) name = symbolize(name) path = File.join(@path, name.to_s + ".rb") begin Kernel.load path, @wrap @loaded[name] = true return true rescue LoadError => detail # I have no idea what's going on here, but different versions # of ruby are raising different errors on missing files. unless detail.to_s =~ /^no such file/i warn "Could not autoload %s: %s" % [name, detail] end return false end end def loaded?(name) name = symbolize(name) @loaded[name] end def loadall # Load every instance of everything we can find. $:.each do |dir| fdir = File.join(dir, @path) if FileTest.exists?(fdir) and FileTest.directory?(fdir) Dir.glob("#{fdir}/*.rb").each do |file| # Load here, rather than require, so that facts # can be reloaded. This has some short-comings, I # believe, but it works as long as real classes # aren't used. name = File.basename(file).sub(".rb", '').intern next if @loaded.include? name + next if $".include?(File.join(@path, name.to_s + ".rb")) + filepath = File.join(@path, name.to_s + ".rb") begin - Kernel.load file, @wrap + Kernel.require filepath @loaded[name] = true rescue => detail if Puppet[:trace] puts detail.backtrace end warn "Could not autoload %s: %s" % [file.inspect, detail] end end end end end end # $Id$ diff --git a/lib/puppet/type/pfile/checksum.rb b/lib/puppet/type/pfile/checksum.rb index cd82a4439..a37ccf9dc 100755 --- a/lib/puppet/type/pfile/checksum.rb +++ b/lib/puppet/type/pfile/checksum.rb @@ -1,313 +1,320 @@ # 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 Puppet.type(:file).newstate(:checksum) do - desc "How to check whether a file has changed. **md5**/*lite-md5*/ - *time*/*mtime*" + desc "How to check whether a file has changed. This state is used internally + for file copying, but it can also be used to monitor files somewhat + like Tripwire without managing the file contents in any way. You can + specify that a file's checksum should be monitored and then subscribe to + the file from another object and receive events to signify + checksum changes, for instance." + @event = :file_changed @unmanaged = true @validtypes = %w{md5 md5lite timestamp mtime time} def self.validtype?(type) @validtypes.include?(type) end @validtypes.each do |ctype| newvalue(ctype) do handlesum() end end str = @validtypes.join("|") + # This is here because Puppet sets this internally, using + # {md5}...... newvalue(/^\{#{str}\}/) do handlesum() end newvalue(:nosum) do # nothing :nochange end # Convert from the sum type to the stored checksum. munge do |value| unless defined? @checktypes @checktypes = [] end if value =~ /^\{(\w+)\}(.+)$/ @checktypes << $1 #return $2 return value else if FileTest.directory?(@parent[:path]) value = "time" end value = super(value) @checktypes << value return getcachedsum() end end def checktype @checktypes[0] end # Because source and content and whomever else need to set the checksum # and do the updating, we provide a simple mechanism for doing so. def checksum=(value) @is = value @should = [value] self.updatesum end # Checksums need to invert how changes are printed. def change_to_s begin if @is == :absent return "defined '%s' as '%s'" % [self.name, self.should_to_s] elsif self.should == :absent return "undefined %s from '%s'" % [self.name, self.is_to_s] else return "%s changed '%s' to '%s'" % [self.name, self.should_to_s, self.is_to_s] end rescue Puppet::Error, Puppet::DevError raise rescue => detail raise Puppet::DevError, "Could not convert change %s to string: %s" % [self.name, detail] end end # Retrieve the cached sum def getcachedsum hash = nil unless hash = @parent.cached(:checksums) hash = {} @parent.cache(:checksums, hash) end sumtype = @checktypes[0] #unless state # self.devfail "Did not get state back from Storage" #end if hash.include?(sumtype) #self.notice "Found checksum %s for %s" % # [hash[sumtype] ,@parent[:path]] sum = hash[sumtype] unless sum =~ /^\{\w+\}/ sum = "{%s}%s" % [sumtype, sum] end return sum elsif hash.empty? #self.notice "Could not find sum of type %s" % sumtype return :nosum else #self.notice "Found checksum for %s but not of type %s" % # [@parent[:path],sumtype] return :nosum end end # Calculate the sum from disk. def getsum(checktype) sum = "" checktype = checktype.intern if checktype.is_a? String case checktype when :md5, :md5lite: unless FileTest.file?(@parent[:path]) @parent.info "Cannot MD5 sum directory %s" % @parent[:path] @should = [nil] @is = nil #@parent.delete(self[:path]) return else begin File.open(@parent[:path]) { |file| text = nil case checktype when :md5 text = file.read when :md5lite text = file.read(512) end if text.nil? self.debug "Not checksumming empty file %s" % @parent[:path] sum = 0 else sum = Digest::MD5.hexdigest(text) end } rescue Errno::EACCES => detail self.notice "Cannot checksum %s: permission denied" % @parent[:path] @parent.delete(self.class.name) rescue => detail self.notice "Cannot checksum %s: %s" % detail @parent.delete(self.class.name) end end when :timestamp, :mtime: sum = @parent.stat.mtime.to_s #sum = File.stat(@parent[:path]).mtime.to_s when :time: sum = @parent.stat.ctime.to_s #sum = File.stat(@parent[:path]).ctime.to_s else raise Puppet::Error, "Invalid sum type %s" % checktype end return "{#{checktype}}" + sum.to_s #return sum.to_s 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 handlesum if @is.nil? raise Puppet::Error, "Checksum state for %s is somehow nil" % @parent.title end if @is == :absent self.retrieve if self.insync? self.debug "Checksum is already in sync" return nil end #@parent.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 == :absent # if they're copying, then we won't worry about the file # not existing yet unless @parent.state(:source) self.warning( "File %s does not exist -- cannot checksum" % @parent[:path] ) end return nil end end # If the sums are different, then return an event. if self.updatesum return :file_changed else return nil 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(usecache = false) # When the 'source' is retrieving, it passes "true" here so # that we aren't reading the file twice in quick succession, yo. if usecache and @is return @is end unless defined? @checktypes @checktypes = ["md5"] end stat = nil unless stat = @parent.stat self.is = :absent return end if stat.ftype == "link" and @parent[:links] != :follow self.debug "Not checksumming symlink" #@parent.delete(:checksum) self.is = self.should return end if stat.ftype == "directory" and @checktypes[0] =~ /md5/ @checktypes = ["time"] end # Just use the first allowed check type @is = getsum(@checktypes[0]) # @should should always be set, so if it's not set at all, we # know we haven't looked in the cache yet. unless defined? @should and ! @should.nil? @should = [getcachedsum()] end # 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 @should == [:nosum] @should = [@is] # FIXME we should support an updatechecksums-like mechanism self.updatesum end #@parent.debug "checksum state is %s" % self.is end # Store the new sum to the state db. def updatesum result = false state = nil unless state = @parent.cached(:checksums) self.debug "Initializing checksum hash for %s" % @parent.title state = {} @parent.cache(:checksums, state) end if @is.is_a?(Symbol) error = Puppet::Error.new("%s has invalid checksum" % @parent.title) raise error end # if we're replacing, vs. updating if state.include?(@checktypes[0]) unless defined? @should raise Puppet::Error.new( ("@should is not initialized for %s, even though we " + "found a checksum") % @parent[:path] ) end self.debug "Replacing %s checksum %s with %s" % [@parent.title, state[@checktypes[0]],@is] #@parent.debug "@is: %s; @should: %s" % [@is,@should] result = true else @parent.debug "Creating checksum %s" % @is result = false end state[@checktypes[0]] = @is return result end end end # $Id$ diff --git a/test/other/autoload.rb b/test/other/autoload.rb index 7cbb11c1a..f561a81a9 100644 --- a/test/other/autoload.rb +++ b/test/other/autoload.rb @@ -1,104 +1,101 @@ #!/usr/bin/env ruby require 'puppet' require 'puppet/autoload' require 'puppettest' class TestAutoload < Test::Unit::TestCase include PuppetTest @things = [] def self.newthing(name) @things << name end def self.thing?(name) @things.include? name end def self.clear @things.clear end def mkfile(name, path) # Now create a file to load File.open(path, "w") do |f| f.puts %{ TestAutoload.newthing(:#{name.to_s}) } end end def teardown super self.class.clear end def test_load dir = tempfile() $: << dir cleanup do $:.delete(dir) end Dir.mkdir(dir) rbdir = File.join(dir, "yayness") Dir.mkdir(rbdir) # An object for specifying autoload klass = self.class loader = nil assert_nothing_raised { loader = Puppet::Autoload.new(klass, :yayness) } assert_equal(loader.object_id, Puppet::Autoload[klass].object_id, "Did not retrieve loader object by class") # Make sure we don't fail on missing files assert_nothing_raised { assert_equal(false, loader.load(:mything), "got incorrect return on failed load") } # Now create a couple of files for testing path = File.join(rbdir, "mything.rb") mkfile(:mything, path) opath = File.join(rbdir, "othing.rb") mkfile(:othing, opath) # Now try to actually load it. assert_nothing_raised { assert_equal(true, loader.load(:mything), "got incorrect return on failed load") } assert(loader.loaded?(:mything), "Not considered loaded") assert(klass.thing?(:mything), "Did not get loaded thing") # Now clear everything, and test loadall assert_nothing_raised { loader.clear } self.class.clear assert_nothing_raised { loader.loadall } [:mything, :othing].each do |thing| assert(loader.loaded?(thing), "#{thing.to_s} not considered loaded") assert(klass.thing?(thing), "Did not get loaded #{thing.to_s}") end end - - def test_loadall - end end