diff options
Diffstat (limited to 'lib')
47 files changed, 3772 insertions, 3479 deletions
diff --git a/lib/puppet.rb b/lib/puppet.rb index 5d0a3c579..c4a14ef7b 100644 --- a/lib/puppet.rb +++ b/lib/puppet.rb @@ -1,5 +1,6 @@ require 'singleton' require 'puppet/log' +require 'puppet/util' # see the bottom of the file for further inclusions @@ -239,9 +240,28 @@ PUPPETVERSION = '0.9.4' return true end end + + # Create a new type + def self.newtype(name, parent = nil, &block) + parent ||= Puppet::Type + Puppet::Util.symbolize(name) + t = Class.new(parent) do + @name = name + end + t.class_eval(&block) + @types ||= {} + @types[name] = t + end + + # Retrieve a type by name + def self.type(name) + unless defined? @types + return nil + end + return @types[name] + end end -require 'puppet/util' require 'puppet/server' require 'puppet/type' require 'puppet/storage' diff --git a/lib/puppet/client.rb b/lib/puppet/client.rb index 56e78349a..80b9fe30f 100644 --- a/lib/puppet/client.rb +++ b/lib/puppet/client.rb @@ -20,6 +20,7 @@ begin require 'xmlrpc/server' rescue LoadError => detail $noclientnetworking = detail + raise Puppet::Error, "You must have the Ruby XMLRPC, CGI, and Webrick libraries installed" end module Puppet @@ -413,9 +414,17 @@ module Puppet if tmp = @driver.getfile(sum) newcontents = Base64.decode64(tmp) newsum = Digest::MD5.hexdigest(newcontents) + changed = nil + unless FileTest.writable?(file) + changed = File.stat(file).mode + File.chmod(changed | 0200, file) + end File.open(file,File::WRONLY|File::TRUNC) { |of| of.print(newcontents) } + if changed + File.chmod(changed, file) + end else Puppet.err "Could not find file with checksum %s" % sum return nil diff --git a/lib/puppet/element.rb b/lib/puppet/element.rb index 4d1ad8bac..7e499567a 100644 --- a/lib/puppet/element.rb +++ b/lib/puppet/element.rb @@ -40,7 +40,7 @@ class Puppet::Element def path unless defined? @path if defined? @parent and @parent - if self.is_a?(Puppet::Type::Component) + if self.is_a?(Puppet.type(:component)) @path = [@parent.path, self.name] else @path = [@parent.path, self.class.name.to_s + "=" + self.name] @@ -54,10 +54,10 @@ class Puppet::Element else # We assume that if we don't have a parent that we should not # cache the path - if self.is_a?(Puppet::Type::Component) + if self.is_a?(Puppet.type(:component)) @path = [self.name] else - @path = [self.class.name.to_s + "=" + self.name] + @path = [self.class.name.to_s + "=" + self.name.to_s] end end end diff --git a/lib/puppet/event.rb b/lib/puppet/event.rb index e2c364194..a7070bb3e 100644 --- a/lib/puppet/event.rb +++ b/lib/puppet/event.rb @@ -192,7 +192,7 @@ module Puppet end if transaction.triggered?(self.target, @callback) > 0 - Puppet.debug "%s has already run" % self + self.target.info "already applied %s" % [@callback] else # We need to call the method, so that it gets retrieved # as a real object. @@ -201,6 +201,7 @@ module Puppet # [@source,@event,@method,target] begin if target.respond_to?(@callback) + target.log "triggering %s" % @callback event = target.send(@callback) else Puppet.debug( @@ -240,14 +241,6 @@ module Puppet @event = args[:event] @source = args[:source] @transaction = args[:transaction] - - #Puppet.info "%s: %s(%s)" % - #Puppet.info "%s: %s changed from %s to %s" % - # [@object,@state.name, @state.is,@state.should] - - # initially, just stuff all instances into a central bucket - # to be handled as a batch - #@@events.push self end def to_s diff --git a/lib/puppet/log.rb b/lib/puppet/log.rb index 9fff07225..7d4b750c7 100644 --- a/lib/puppet/log.rb +++ b/lib/puppet/log.rb @@ -67,6 +67,12 @@ module Puppet # :nodoc: # Create a new log message. The primary role of this method is to # avoid creating log messages below the loglevel. def Log.create(hash) + unless hash.include?(:level) + raise Puppet::DevError, "Logs require a level" + end + unless @levels.index(hash[:level]) + raise Puppet::DevError, "Invalid log level %s" % hash[:level] + end if @levels.index(hash[:level]) >= @loglevel return Puppet::Log.new(hash) else @@ -159,6 +165,9 @@ module Puppet # :nodoc: # It's worth noting that there's a potential for a loop here, if # the machine somehow gets the destination set as itself. def Log.newmessage(msg) + if @levels.index(msg.level) < @loglevel + return + end @destinations.each { |type, dest| case dest when Module # This is the Syslog module @@ -230,6 +239,10 @@ module Puppet # :nodoc: } end + def Log.sendlevel?(level) + @levels.index(level) >= @loglevel + end + # Reopen all of our logs. def Log.reopen types = @destinations.keys @@ -273,6 +286,10 @@ module Puppet # :nodoc: raise Puppet::DevError, "Level is not a string or symbol: #{args[:level].class}" end + + # Just return unless we're actually at a level we should send + #return unless self.class.sendlevel?(@level) + @message = args[:message].to_s @time = Time.now # this should include the host name, and probly lots of other diff --git a/lib/puppet/parameter.rb b/lib/puppet/parameter.rb new file mode 100644 index 000000000..d2656b766 --- /dev/null +++ b/lib/puppet/parameter.rb @@ -0,0 +1,196 @@ +module Puppet + class Parameter < Puppet::Element + class << self + attr_reader :validater, :munger, :name, :default + attr_accessor :ismetaparameter, :element + + # This means that 'nil' is an invalid default value. + def defaultto(value = nil, &block) + if block + @default = block + else + @default = value + end + end + + # Store documentation for this parameter. + def desc(str) + @doc = str + end + + # This is how we munge the value. Basically, this is our + # opportunity to convert the value from one form into another. + def munge(&block) + # I need to wrap the unsafe version in begin/rescue statements, + # but if I directly call the block then it gets bound to the + # class's context, not the instance's, thus the two methods, + # instead of just one. + define_method(:unsafe_munge, &block) + + define_method(:munge) do |*args| + begin + unsafe_munge(*args) + rescue Puppet::Error => detail + Puppet.debug "Reraising %s" % detail + raise + rescue => detail + raise Puppet::DevError, "Munging failed for class %s: %s" % + [self.name, detail] + end + end + #@munger = block + end + + def inspect + "Parameter(#{self.name})" + end + + # Mark whether we're the namevar. + def isnamevar + @isnamevar = true + @required = true + end + + # Is this parameter the namevar? Defaults to false. + def isnamevar? + if defined? @isnamevar + return @isnamevar + else + return false + end + end + + # This parameter is required. + def isrequired + @required = true + end + + # Is this parameter required? Defaults to false. + def required? + if defined? @required + return @required + else + return false + end + end + + def to_s + if self.ismetaparameter + "Puppet::Type::" + @name.to_s.capitalize + else + self.element.to_s + @name.to_s.capitalize + end + end + + # Verify that we got a good value + def validate(&block) + #@validater = block + define_method(:unsafe_validate, &block) + + define_method(:validate) do |*args| + begin + unsafe_validate(*args) + rescue ArgumentError, Puppet::Error, TypeError + raise + rescue => detail + raise Puppet::DevError, + "Validate method failed for class %s: %s" % + [self.name, detail] + end + end + end + end + + # Just a simple method to proxy instance methods to class methods + def self.proxymethods(*values) + values.each { |val| + eval "def #{val}; self.class.#{val}; end" + } + end + + # And then define one of these proxies for each method in our + # ParamHandler class. + proxymethods("required?", "default", "isnamevar?") + + attr_accessor :parent + + # This doesn't work, because the instance_eval doesn't bind the inner block + # only the outer one. +# def munge(value) +# if munger = self.class.munger +# return @parent.instance_eval { +# munger.call(value) +# } +# else +# return value +# end +# end +# +# def validate(value) +# if validater = self.class.validater +# return @parent.instance_eval { +# validater.call(value) +# } +# end +# end + + def default + default = self.class.default + if default.is_a?(Proc) + val = self.instance_eval(&default) + return val + else + return default + end + end + + # This should only be called for parameters, but go ahead and make + # it possible to call for states, too. + def value + if self.is_a?(Puppet::State) + return self.should + else + return @value + end + end + + # Store the value provided. All of the checking should possibly be + # late-binding (e.g., users might not exist when the value is assigned + # but might when it is asked for). + def value=(value) + # If we're a state, just hand the processing off to the should method. + if self.is_a?(Puppet::State) + return self.should = value + end + if respond_to?(:validate) + validate(value) + end + + if respond_to?(:munge) + value = munge(value) + end + @value = value + end + + def to_s + "%s => %s" % [self.class.name, self.value] + end + + def inspect + s = "Parameter(%s = %s" % [self.name, self.value || "nil"] + if defined? @parent + s += ", @parent = %s)" % @parent + else + s += ")" + end + end + + def name + self.class.name + end + + def to_s + s = "Parameter(%s)" % self.name + end + end +end diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb index dd56c109a..193f85eb1 100644 --- a/lib/puppet/parser/ast.rb +++ b/lib/puppet/parser/ast.rb @@ -545,7 +545,7 @@ module Puppet raise Puppet::DevError, detail.to_s end next if pname == "name" # always allow these - unless type.validarg?(pname) + unless type.validattr?(pname) error = Puppet::ParseError.new( "Invalid parameter '%s' for type '%s'" % [pname,type.name] diff --git a/lib/puppet/server/fileserver.rb b/lib/puppet/server/fileserver.rb index 92bc129ec..9130b3df5 100755 --- a/lib/puppet/server/fileserver.rb +++ b/lib/puppet/server/fileserver.rb @@ -28,8 +28,8 @@ class Server end obj = nil - unless obj = Puppet::Type::PFile[dir] - obj = Puppet::Type::PFile.create( + unless obj = Puppet.type(:file)[dir] + obj = Puppet.type(:file).create( :name => dir, :check => CHECKPARAMS ) @@ -87,7 +87,7 @@ class Server # Deal with ignore parameters. def handleignore(children, path, ignore) - ignore.each { |ignore| + ignore.value.each { |ignore| Dir.glob(File.join(path,ignore), File::FNM_DOTMATCH) { |match| children.delete(File.basename(match)) } diff --git a/lib/puppet/server/logger.rb b/lib/puppet/server/logger.rb index 0ee2d75f0..d5feb0320 100755 --- a/lib/puppet/server/logger.rb +++ b/lib/puppet/server/logger.rb @@ -10,6 +10,11 @@ class Server # :nodoc: # accept a log message from a client, and route it accordingly def addlog(message, client = nil, clientip = nil) + unless message + raise Puppet::DevError, "Did not receive message" + end + + Puppet.info message.inspect # if the client is set, then we're not local if client begin @@ -22,6 +27,10 @@ class Server # :nodoc: end end + unless message + raise Puppet::DevError, "Could not resurrect message" + end + # Mark it as remote, so it's not sent to syslog message.remote = true diff --git a/lib/puppet/sslcertificates.rb b/lib/puppet/sslcertificates.rb index d543c5200..9196da61b 100755 --- a/lib/puppet/sslcertificates.rb +++ b/lib/puppet/sslcertificates.rb @@ -1,14 +1,19 @@ # The library for manipulating SSL certs. require 'puppet' -require 'openssl' + +begin + require 'openssl' +rescue LoadError + raise Puppet::Error, "You must have the Ruby openssl library installed" +end module Puppet module SSLCertificates def self.mkdir(dir) # this is all a bunch of stupid hackery unless FileTest.exists?(dir) - comp = Puppet::Type::Component.create( + comp = Puppet.type(:component).create( :name => "certdir creation" ) path = [''] diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index 47b18986e..888c4580d 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -160,7 +160,6 @@ class Transaction #--------------------------------------------------------------- def triggered(object, method) - Puppet.notice "Triggered %s" % method @triggered[object][method] += 1 #@triggerevents << ("%s_%sed" % [object.class.name.to_s, method.to_s]).intern end @@ -168,7 +167,6 @@ class Transaction #--------------------------------------------------------------- def triggered?(object, method) - Puppet.notice "Looking for triggered %s" % method @triggered[object][method] end #--------------------------------------------------------------- diff --git a/lib/puppet/transportable.rb b/lib/puppet/transportable.rb index ffa751eb6..e81401c41 100644 --- a/lib/puppet/transportable.rb +++ b/lib/puppet/transportable.rb @@ -102,7 +102,7 @@ module Puppet if parent hash[:parent] = parent end - container = Puppet::Type::Component.create(hash) + container = Puppet.type(:component).create(hash) if parent parent.push container diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index a71104fc0..b56312d07 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -4,15 +4,21 @@ require 'puppet/element' require 'puppet/event' require 'puppet/metric' require 'puppet/type/state' +require 'puppet/parameter' +require 'puppet/util' # see the bottom of the file for the rest of the inclusions module Puppet # :nodoc: -# This class is the abstract base class for the mechanism for organizing -# work. No work is actually done by this class or its subclasses; rather, -# the subclasses include states which do the actual work. -# See state.rb for how work is actually done. class Type < Puppet::Element - attr_accessor :children, :parameters, :parent + # Types (which map to elements in the languages) are entirely composed of + # attribute value pairs. Generally, Puppet calls any of these things an + # 'attribute', but these attributes always take one of three specific + # forms: parameters, metaparams, or states. + + # In naming methods, I have tried to consistently name the method so + # that it is clear whether it operates on all attributes (thus has 'attr' in + # the method name, or whether it operates on a specific type of attributes. + attr_accessor :children, :parent attr_accessor :file, :line, :tags attr_writer :implicit @@ -47,17 +53,6 @@ class Type < Puppet::Element @parameters = [:notused] # @paramdoc = Hash.new - - # the parameters that all instances will accept - @@metaparams = [ - :onerror, - :noop, - :schedule, - :check, - :subscribe, - :require, - :loglevel - ] @@metaparamdoc = Hash.new { |hash,key| if key.is_a?(String) @@ -70,25 +65,6 @@ class Type < Puppet::Element end } - @@metaparamdoc[:onerror] = "How to handle errors -- roll back innermost - transaction, roll back entire transaction, ignore, etc. Currently - non-functional." - @@metaparamdoc[:noop] = "Boolean flag indicating whether work should actually - be done." - @@metaparamdoc[:schedule] = "On what schedule the object should be managed. - Currently non-functional." - @@metaparamdoc[:check] = "States which should have their values retrieved - but which should not actually be modified. This is currently used - internally, but will eventually be used for querying." - @@metaparamdoc[:require] = "One or more objects that this object depends on. - This is used purely for guaranteeing that changes to required objects - happen before the dependent object." - @@metaparamdoc[:subscribe] = "One or more objects that this object depends on. - Changes in the subscribed to objects result in the dependent objects being - refreshed (e.g., a service will get restarted)." - @@metaparamdoc[:loglevel] = "Sets the level that information will be logged: - debug, info, verbose, notice, warning, err, alert, emerg or crit" - # class methods dealing with Type management public @@ -112,7 +88,15 @@ class Type < Puppet::Element # the Type class attribute accessors class << self - attr_reader :name, :namevar, :states, :parameters + attr_reader :name, :states, :parameters + + def inspect + "Type(%s)" % self.name + end + + def to_s + self.inspect + end end # Create @@typehash from @@typeary. This is meant to be run @@ -133,7 +117,12 @@ class Type < Puppet::Element # iterate across all of the subclasses of Type def self.eachtype - @@typeary.each { |type| yield type } + @@typeary.each do |type| + # Only consider types that have names + if type.name + yield type + end + end end # The work that gets done for every subclass of Type @@ -315,17 +304,126 @@ class Type < Puppet::Element } end - # set the parameters for a type; probably only used by FileRecord - # objects - def self.parameters=(params) - Puppet.debug "setting parameters to [%s]" % params.join(" ") - @parameters = params.collect { |param| - if param.class == Symbol - param - else - param.intern - end - } + # Find the namevar + def self.namevar + unless defined? @namevar + @namevar = @parameters.find { |name, param| + param.isnamevar? + unless param + raise Puppet::DevError, "huh? %s" % name + end + }[0] + end + @namevar + end + + # Copy an existing class parameter. This allows other types to avoid + # duplicating a parameter definition, and is mostly used by subclasses + # of the File class. + def self.copyparam(klass, name) + param = klass.attrclass(name) + + unless param + raise Puppet::DevError, "Class %s has no param %s" % [klass, name] + end + @parameters ||= [] + @parameters << param + + @paramhash ||= {} + @parameters.each { |p| @paramhash[name] = p } + + if param.isnamevar? + @namevar = param.name + end + end + + # Create a new metaparam. Requires a block and a name, stores it in the + # @parameters array, and does some basic checking on it. + def self.newmetaparam(name, &block) + Puppet::Util.symbolize(name) + param = Class.new(Puppet::Parameter) do + @name = name + end + param.ismetaparameter + param.class_eval(&block) + @@metaparams ||= [] + @@metaparams << param + + @@metaparamhash ||= {} + @@metaparams.each { |p| @@metaparamhash[name] = p } + end + + # Create a new parameter. Requires a block and a name, stores it in the + # @parameters array, and does some basic checking on it. + def self.newparam(name, &block) + Puppet::Util.symbolize(name) + param = Class.new(Puppet::Parameter) do + @name = name + end + param.element = self + param.class_eval(&block) + @parameters ||= [] + @parameters << param + + @paramhash ||= {} + @parameters.each { |p| @paramhash[name] = p } + + if param.isnamevar? + @namevar = param.name + end + end + + # Create a new state. + def self.newstate(name, parent = nil, &block) + parent ||= Puppet::State + if @validstates.include?(name) + raise Puppet::DevError, "Class %s already has a state named %s" % + [self.name, name] + end + s = Class.new(parent) do + @name = name + end + s.class_eval(&block) + @states ||= [] + @states << s + @validstates[name] = s + + return s + end + + # Return the parameter names + def self.parameters + @parameters.collect { |klass| klass.name } + end + + # Find the metaparameter class associated with a given metaparameter name. + def self.metaparamclass(name) + @@metaparamhash[name] + end + + # Find the parameter class associated with a given parameter name. + def self.paramclass(name) + @paramhash[name] + end + + # Find the class associated with any given attribute. + def self.attrclass(name) + case self.attrtype(name) + when :param: @paramhash[name] + when :meta: @@metaparamhash[name] + when :state: @validstates[name] + end + end + + def self.to_s + "Puppet::Type::" + @name.to_s.capitalize + end + + # Create a block to validate that our object is set up entirely. This will + # be run before the object is operated on. + def self.validate(&block) + define_method(:validate, &block) + #@validate = block end # does the name reflect a valid state? @@ -362,14 +460,43 @@ class Type < Puppet::Element unless defined? @parameters raise Puppet::DevError, "Class %s has not defined parameters" % self end - if @parameters.include?(name) or @@metaparams.include?(name) + if @paramhash.include?(name) or @@metaparamhash.include?(name) return true else return false end end - def self.validarg?(name) + # What type of parameter are we dealing with? + def self.attrtype(name) + case + when @paramhash.include?(name): return :param + when @@metaparamhash.include?(name): return :meta + when @validstates.include?(name): return :state + else + raise Puppet::DevError, "Invalid parameter %s" % [name] + end + end + + # All parameters, in the appropriate order. The namevar comes first, + # then the states, then the params and metaparams in the order they + # were specified in the files. + def self.allattrs + # now get all of the arguments, in a specific order + order = [self.namevar] + order << [self.states.collect { |state| state.name }, + self.parameters, + self.metaparams].flatten.reject { |param| + # we don't want our namevar in there multiple times + param == self.namevar + } + + order.flatten! + + return order + end + + def self.validattr?(name) if name.is_a?(String) name = name.intern end @@ -401,18 +528,26 @@ class Type < Puppet::Element end elsif Puppet::Type.metaparam?(name) if @metaparams.include?(name) - return @metaparams[name] + return @metaparams[name].value else - return nil + if default = self.class.metaattrclass(name).default + return default + else + return nil + end end elsif self.class.validparameter?(name) if @parameters.include?(name) - return @parameters[name] + return @parameters[name].value else - return nil + if default = self.class.attrclass(name).default + return default + else + return nil + end end else - raise TypeError.new("Invalid parameter %s" % [name]) + raise TypeError.new("Invalid parameter %s(%s)" % [name, name.inspect]) end end @@ -430,10 +565,9 @@ class Type < Puppet::Element if value.nil? raise Puppet::Error.new("Got nil value for %s" % name) end + if Puppet::Type.metaparam?(name) - @parameters[name] = value - # call the metaparam method - self.send(("meta" + name.id2name + "="),value) + self.newmetaparam(self.class.metaparamclass(name), value) elsif stateklass = self.class.validstate?(name) if value.is_a?(Puppet::State) self.debug "'%s' got handed a state for '%s'" % [self,name] @@ -452,13 +586,7 @@ class Type < Puppet::Element end elsif self.class.validparameter?(name) # if they've got a method to handle the parameter, then do it that way - method = "param" + name.id2name + "=" - if self.respond_to?(method) - self.send(method,value) - else - # else just set it - @parameters[name] = value - end + self.newparam(self.class.attrclass(name), value) else raise Puppet::Error, "Invalid parameter %s" % [name] end @@ -534,7 +662,7 @@ class Type < Puppet::Element # create a log at specified level def log(msg) Puppet::Log.create( - :level => @metaparams[:loglevel], + :level => @metaparams[:loglevel].value, :message => msg, :source => self ) @@ -558,10 +686,52 @@ class Type < Puppet::Element end end + # Create a new parameter. + def newparam(klass, value = nil) + newattr(:param, klass, value) + end + + # Create a new parameter or metaparameter. We'll leave the calling + # method to store it appropriately. + def newmetaparam(klass, value = nil) + newattr(:meta, klass, value) + end + + # The base function that the others wrap. + def newattr(type, klass, value = nil) + # This should probably be a bit, um, different, but... + if type == :state + return newstate(klass) + end + param = klass.new + param.parent = self + if value + param.value = value + end + + case type + when :meta + @metaparams[klass.name] = param + when :param + @parameters[klass.name] = param + else + raise Puppet::DevError, "Invalid param type %s" % type + end + + return param + end + # create a new state def newstate(name, hash = {}) - unless stateklass = self.class.validstate?(name) - raise Puppet::Error, "Invalid parameter %s" % name + stateklass = nil + if name.is_a?(Class) + stateklass = name + name = stateklass.name + else + stateklass = self.class.validstate?(name) + unless stateklass + raise Puppet::Error, "Invalid state %s" % name + end end if @states.include?(name) hash.each { |var,value| @@ -575,7 +745,7 @@ class Type < Puppet::Element # make sure the state doesn't have any errors newstate = stateklass.new(hash) @states[name] = newstate - return true + return newstate rescue Puppet::Error => detail # the state failed, so just ignore it self.warning "State %s failed: %s" % @@ -632,6 +802,14 @@ class Type < Puppet::Element end end + # Is the named state defined? + def statedefined?(name) + unless name.is_a? Symbol + name = name.intern + end + return @states.include?(name) + end + # return an actual type by name; to return the value, use 'inst[name]' # FIXME this method should go away def state(name) @@ -782,23 +960,9 @@ class Type < Puppet::Element unless defined? @metaparams @metaparams = Hash.new(false) end - - #unless defined? @paramdoc - # @paramdoc = Hash.new { |hash,key| - # if key.is_a?(String) - # key = key.intern - # end - # if hash.include?(key) - # hash[key] - # else - # "Param Documentation for %s not found" % key - # end - # } - #end # set defalts @noop = false - @metaparams[:loglevel] = :notice # keeping stats for the total number of changes, and how many were # completely sync'ed # this isn't really sufficient either, because it adds lots of special cases @@ -817,16 +981,7 @@ class Type < Puppet::Element hash = self.argclean(hash) - # now get all of the arguments, in a specific order - order = [self.class.namevar] - order << [self.class.states.collect { |state| state.name }, - self.class.parameters, - self.class.eachmetaparam { |param| param }].flatten.reject { |param| - # we don't want our namevar in there multiple times - param == self.class.namevar - } - - order.flatten.each { |name| + self.class.allattrs.each { |name| if hash.include?(name) begin self[name] = hash[name] @@ -839,6 +994,8 @@ class Type < Puppet::Element end } + self.setdefaults + if hash.length > 0 self.debug hash.inspect raise Puppet::Error.new("Class %s does not accept argument(s) %s" % @@ -848,6 +1005,40 @@ class Type < Puppet::Element # add this object to the specific class's list of objects #puts caller self.class[self.name] = self + + if self.respond_to?(:validate) + self.validate + end + end + + # Is the specified parameter set? + def attrset?(type, attr) + case type + when :state: return @states.include?(attr) + when :param: return @parameters.include?(attr) + when :meta: return @metaparams.include?(attr) + else + raise Puppet::DevError, "Invalid set type %s" % [type] + end + end + + # For any parameters or states that have defaults and have not yet been + # set, set them now. + def setdefaults + self.class.allattrs.each { |attr| + type = self.class.attrtype(attr) + next if self.attrset?(type, attr) + + klass = self.class.attrclass(attr) + unless klass + raise Puppet::DevError, "Could not retrieve class for %s" % attr + end + if klass.default + obj = self.newattr(type, klass) + obj.value = obj.default + end + } + end # Merge new information with an existing object, checking for conflicts @@ -903,7 +1094,7 @@ class Type < Puppet::Element unless defined? @name and @name namevar = self.class.namevar if self.class.validparameter?(namevar) - @name = @parameters[namevar] + @name = self[:name] elsif self.class.validstate?(namevar) @name = self.should(namevar) else @@ -913,7 +1104,7 @@ class Type < Puppet::Element end unless @name - raise Puppet::DevError, "Could not find name %s for %s" % + raise Puppet::DevError, "Could not find namevar '%s' for %s" % [namevar, self.class.name] end @@ -969,7 +1160,7 @@ class Type < Puppet::Element # FIXME this method is essentially obviated, but it's still used by tests # and i don't feel like fixing it yet def sync - #raise Puppet::DevError, "Type#sync called" + raise Puppet::DevError, "Type#sync called" events = self.collect { |child| child.sync }.reject { |event| @@ -1110,86 +1301,13 @@ class Type < Puppet::Element # Meta-parameter methods: These methods deal with the results # of specifying metaparameters - def self.eachmetaparam - @@metaparams.each { |param| - yield param - } - end - - # This just marks states that we definitely want to retrieve values - # on. There is currently no way to uncheck a parameter. - def metacheck=(args) - unless args.is_a?(Array) - args = [args] - end - - # these are states that we might not have 'should' - # values for but we want to retrieve 'is' values for anyway - args.each { |state| - unless state.is_a?(Symbol) - state = state.intern - end - next if @states.include?(state) - - stateklass = nil - self.newstate(state) - } + def self.metaparams + @@metaparams.collect { |param| param.name } end # Is the parameter in question a meta-parameter? def self.metaparam?(param) - @@metaparams.include?(param) - end - - # for each object we require, subscribe to all events that it - # generates - # we might reduce the level of subscription eventually, but for now... - def metarequire=(requires) - self.handledepends(requires, :NONE, nil) - end - - # for each object we require, subscribe to all events that it - # generates - # we might reduce the level of subscription eventually, but for now... - def metasubscribe=(requires) - self.handledepends(requires, :ALL_EVENTS, :refresh) - end - - def metanoop=(noop) - if noop == "true" or noop == true - @noop = true - elsif noop == "false" or noop == false - @noop = false - else - raise Puppet::Error.new("Invalid noop value '%s'" % noop) - end - end - - # Currently nonfunctional - def metaonerror=(response) - self.debug("Would have called metaonerror") - @onerror = response - end - - # Currently nonfunctional - def metaschedule=(schedule) - @schedule = schedule - end - - # Determines what our objects log at. Defaults to :notice. - def metaloglevel=(loglevel) - if loglevel.is_a?(String) - loglevel = loglevel.intern - end - if loglevel == :verbose - loglevel = :info - end - - if Puppet::Log.validlevel?(loglevel) - @metaparams[:loglevel] = loglevel - else - raise Puppet::Error.new("Invalid loglevel '%s'" % loglevel) - end + @@metaparamhash.include?(param) end # Subscription and relationship methods @@ -1291,15 +1409,6 @@ class Type < Puppet::Element } end - # return all of the subscriptions to a given event - #def subscribers?(event) - # Puppet::Event::Subscription.subscriptions(self).find_all { |sub| - # sub.match?(event) - # }.collect { |sub| - # sub.target - # } - #end - # we've received an event # we only support local events right now, so we can pass actual # objects around, including the transaction object @@ -1332,6 +1441,106 @@ class Type < Puppet::Element def self.metaparamdoc(metaparam) @@metaparamdoc[metaparam] end + + # Add all of the meta parameters. + newmetaparam(:onerror) do + desc "How to handle errors -- roll back innermost + transaction, roll back entire transaction, ignore, etc. Currently + non-functional." + end + + newmetaparam(:noop) do + desc "Boolean flag indicating whether work should actually + be done." + munge do |noop| + if noop == "true" or noop == true + return true + elsif noop == "false" or noop == false + return false + else + raise Puppet::Error.new("Invalid noop value '%s'" % noop) + end + end + end + + newmetaparam(:schedule) do + desc "On what schedule the object should be managed. + Currently non-functional." + end + + newmetaparam(:check) do + desc "States which should have their values retrieved + but which should not actually be modified. This is currently used + internally, but will eventually be used for querying." + + munge do |args| + unless args.is_a?(Array) + args = [args] + end + + unless defined? @parent + raise Puppet::DevError, "No parent for %s, %s?" % + [self.class, self.name] + end + + args.each { |state| + unless state.is_a?(Symbol) + state = state.intern + end + next if @parent.statedefined?(state) + + @parent.newstate(state) + } + end + end + # For each object we require, subscribe to all events that it generates. We + # might reduce the level of subscription eventually, but for now... + newmetaparam(:require) do + desc "One or more objects that this object depends on. + This is used purely for guaranteeing that changes to required objects + happen before the dependent object." + + munge do |requires| + @parent.handledepends(requires, :NONE, nil) + end + end + + # For each object we require, subscribe to all events that it generates. + # We might reduce the level of subscription eventually, but for now... + newmetaparam(:subscribe) do + desc "One or more objects that this object depends on. + Changes in the subscribed to objects result in the dependent objects being + refreshed (e.g., a service will get restarted)." + + munge do |requires| + @parent.handledepends(requires, :ALL_EVENTS, :refresh) + end + end + + newmetaparam(:loglevel) do + desc "Sets the level that information will be logged: + debug, info, verbose, notice, warning, err, alert, emerg or crit" + defaultto :notice + + validate do |loglevel| + if loglevel.is_a?(String) + loglevel = loglevel.intern + end + unless Puppet::Log.validlevel?(loglevel) + raise Puppet::Error, "Invalid log level %s" % loglevel + end + end + + munge do |loglevel| + if loglevel.is_a?(String) + loglevel = loglevel.intern + end + if loglevel == :verbose + loglevel = :info + end + loglevel + end + end end # Puppet::Type end diff --git a/lib/puppet/type/component.rb b/lib/puppet/type/component.rb index c7b553b22..78ccb36da 100644 --- a/lib/puppet/type/component.rb +++ b/lib/puppet/type/component.rb @@ -7,15 +7,20 @@ require 'puppet/type' require 'puppet/transaction' module Puppet - class Type - class Component < Puppet::Type + newtype(:component) do include Enumerable - @name = :component - @namevar = :name + newparam(:name) do + desc "The name of the component. Generally optional." + isnamevar + end + + newparam(:type) do + desc "The type that this component maps to. Generally some kind of + class from the language." - @states = [] - @parameters = [:name,:type] + defaultto "component" + end # topo sort functions def self.sort(objects) @@ -40,7 +45,7 @@ module Puppet self.recurse(req, inlist, list) } - if obj.is_a?(Puppet::Type::Component) + if obj.is_a? self obj.each { |child| self.recurse(child, inlist, list) } @@ -73,11 +78,6 @@ module Puppet # Initialize a new component def initialize(args) @children = [] - - # it makes sense to have a more reasonable default here than 'false' - unless args.include?(:type) or args.include?("type") - args[:type] = "component" - end super(args) end @@ -120,6 +120,7 @@ module Puppet @children.collect { |child| if child.respond_to?(:refresh) child.refresh + child.log "triggering %s" % :refresh end } end @@ -134,7 +135,6 @@ module Puppet return "component(%s)" % self.name end end - end end # $Id$ diff --git a/lib/puppet/type/cron.rb b/lib/puppet/type/cron.rb index e3bac17fc..9e10ae01b 100755 --- a/lib/puppet/type/cron.rb +++ b/lib/puppet/type/cron.rb @@ -1,6 +1,3 @@ -# A Puppet::Type class to manage cron jobs. Some abstraction is done, -# so that different platforms' versions of +crontab+ all work equivalently. - require 'etc' require 'facter' require 'puppet/type/state' @@ -16,7 +13,7 @@ module Puppet # writing. module CronType # Retrieve the uid of a user. This is duplication of code, but the unless - # I start using Puppet::Type::User objects here, it's a much better design. + # I start using Puppet.type(:user) objects here, it's a much better design. def self.uid(user) begin return Etc.getpwnam(user).uid @@ -87,77 +84,16 @@ module Puppet end end - class State - # This is Cron's single state. Somewhat uniquely, this state does not - # actually change anything -- it just calls +@parent.sync+, which writes - # out the whole cron tab for the user in question. There is no real way - # to change individual cron jobs without rewriting the entire cron file. - # - # Note that this means that managing many cron jobs for a given user - # could currently result in multiple write sessions for that user. - class CronCommand < Puppet::State - @name = :command - @doc = "The command to execute in the cron job. The environment - provided to the command varies by local system rules, and it is - best to always provide a fully qualified command. The user's - profile is not sourced when the command is run, so if the - user's environment is desired it should be sourced manually." - - # Normally this would retrieve the current value, but our state is not - # actually capable of doing so. The Cron class does the actual tab - # retrieval, so all this method does is default to :notfound for @is. - def retrieve - unless defined? @is and ! @is.nil? - @is = :notfound - end - end - - # Determine whether the cron job should be destroyed, and figure - # out which event to return. Finally, call @parent.sync to write the - # cron tab. - def sync - event = nil - if @is == :notfound - #@is = @should - event = :cron_created - # We're the first state, so if we're creating the job - # then sync all of the other states - @parent.eachstate { |state| - next if state == self - state.sync(true) - } - - @is = self.should - elsif self.should == :notfound - @parent.remove(true) - event = :cron_deleted - elsif self.insync? - return nil - else - @is = self.should - event = :cron_changed - end - - @parent.store - - return event - end - end - + # Model the actual cron jobs. Supports all of the normal cron job fields + # as parameters, with the 'command' as the single state. Also requires a + # completely symbolic 'name' paremeter, which gets written to the file + # and is used to manage the job. + newtype(:cron) do # A base class for all of the Cron parameters, since they all have # similar argument checking going on. class CronParam < Puppet::State class << self - attr_reader :checking - end - - def weekdays - %w{sunday monday tuesday wednesday thursday friday saturday} - end - - def months - %w{january february march april may june july - august september october november december} + attr_accessor :boundaries end # Normally this would retrieve the current value, but our state is not @@ -222,6 +158,9 @@ module Puppet # or the first three letters of the word. def alphacheck(value, ary) tmp = value.downcase + + # If they specified a shortened version of the name, then see + # if we can lengthen it (e.g., mon => monday). if tmp.length == 3 ary.each_with_index { |name, index| if name =~ /#{tmp}/i @@ -250,353 +189,392 @@ module Puppet # Requires the value, type, and bounds, and optionally supports # a boolean of whether to do alpha checking, and if so requires # the ary against which to do the checking. - def validate(value, lower, upper, arymethod = nil) + munge do |value| + lower, upper = self.class.boundaries retval = nil if num = numfix(value) retval = limitcheck(num, lower, upper) - elsif arymethod - retval = alphacheck(value, send(arymethod)) + elsif respond_to?(:alpha) + # If it has an alpha method defined, then we check + # to see if our value is in that list and if so we turn + # it into a number + retval = alphacheck(value, alpha()) end if retval - return retval + return retval.to_s else raise Puppet::Error, "%s is not a valid %s" % [value, self.class.name] end end + end + # Somewhat uniquely, this state does not actually change anything -- it + # just calls +@parent.sync+, which writes out the whole cron tab for + # the user in question. There is no real way to change individual cron + # jobs without rewriting the entire cron file. + # + # Note that this means that managing many cron jobs for a given user + # could currently result in multiple write sessions for that user. + newstate(:command) do + desc "The command to execute in the cron job. The environment + provided to the command varies by local system rules, and it is + best to always provide a fully qualified command. The user's + profile is not sourced when the command is run, so if the + user's environment is desired it should be sourced manually." + + # Normally this would retrieve the current value, but our state is not + # actually capable of doing so. The Cron class does the actual tab + # retrieval, so all this method does is default to :notfound for @is. + def retrieve + unless defined? @is and ! @is.nil? + @is = :notfound + end + end - def shouldprocess(value) - val = validate(value, *self.class.checking) - return val.to_s + # Determine whether the cron job should be destroyed, and figure + # out which event to return. Finally, call @parent.sync to write the + # cron tab. + def sync + event = nil + if @is == :notfound + #@is = @should + event = :cron_created + # We're the first state, so if we're creating the job + # then sync all of the other states + @parent.eachstate { |state| + next if state == self + state.sync(true) + } + + @is = self.should + elsif self.should == :notfound + @parent.remove(true) + event = :cron_deleted + elsif self.insync? + return nil + else + @is = self.should + event = :cron_changed + end + + @parent.store + + return event end end - class CronMinute < CronParam - @name = :minute - @checking = [0, 59] - @doc = "The minute at which to run the cron job. + newstate(:minute, CronParam) do + self.boundaries = [0, 59] + desc "The minute at which to run the cron job. Optional; if specified, must be between 0 and 59, inclusive." end - class CronHour < CronParam - @name = :hour - @checking = [0, 23] - @doc = "The hour at which to run the cron job. Optional; + newstate(:hour, CronParam) do + self.boundaries = [0, 23] + desc "The hour at which to run the cron job. Optional; if specified, must be between 0 and 23, inclusive." end - class CronWeekday < CronParam - @name = :weekday - @checking = [0, 6, :weekdays] - @doc = "The weekday on which to run the command. + newstate(:weekday, CronParam) do + def alpha + %w{sunday monday tuesday wednesday thursday friday saturday} + end + self.boundaries = [0, 6] + desc "The weekday on which to run the command. Optional; if specified, must be between 0 and 6, inclusive, with 0 being Sunday, or must be the name of the day (e.g., Tuesday)." end - class CronMonth < CronParam - @name = :month - @checking = [1, 12, :months] - @doc = "The month of the year. Optional; if specified + newstate(:month, CronParam) do + def alpha + %w{january february march april may june july + august september october november december} + end + self.boundaries = [1, 12] + desc "The month of the year. Optional; if specified must be between 1 and 12 or the month name (e.g., December)." end - class CronMonthDay < CronParam - @name = :monthday - @checking = [1, 31] - @doc = "The day of the month on which to run the + newstate(:monthday, CronParam) do + self.boundaries = [1, 31] + desc "The day of the month on which to run the command. Optional; if specified, must be between 1 and 31." end - end - class Type - # Model the actual cron jobs. Supports all of the normal cron job fields - # as parameters, with the 'command' as the single state. Also requires a - # completely symbolic 'name' paremeter, which gets written to the file - # and is used to manage the job. - class Cron < Type - @states = [ - Puppet::State::CronCommand, - Puppet::State::CronMinute, - Puppet::State::CronHour, - Puppet::State::CronWeekday, - Puppet::State::CronMonth, - Puppet::State::CronMonthDay - ] - @parameters = [ - :user, - :name - ] - - @paramdoc[:name] = "The symbolic name of the cron job. This name + newparam(:name) do + desc "The symbolic name of the cron job. This name is used for human reference only." - @paramdoc[:user] = "The user to run the command as. This user must + end + + newparam(:user) do + desc "The user to run the command as. This user must be allowed to run cron jobs, which is not currently checked by Puppet." + end - @doc = "Installs and manages cron jobs. All fields except the command - and the user are optional, although specifying no periodic - fields would result in the command being executed every - minute. While the name of the cron job is not part of the actual - job, it is used by Puppet to store and retrieve it. If you specify - a cron job that matches an existing job in every way except name, - then the jobs will be considered equivalent and the new name will - be permanently associated with that job. Once this association is - made and synced to disk, you can then manage the job normally." - @name = :cron - @namevar = :name - - @loaded = {} - - @synced = {} - - @instances = {} - - case Facter["operatingsystem"].value - when "SunOS": - @crontype = Puppet::CronType::SunOS - else - @crontype = Puppet::CronType::Default - end + @doc = "Installs and manages cron jobs. All fields except the command + and the user are optional, although specifying no periodic + fields would result in the command being executed every + minute. While the name of the cron job is not part of the actual + job, it is used by Puppet to store and retrieve it. If you specify + a cron job that matches an existing job in every way except name, + then the jobs will be considered equivalent and the new name will + be permanently associated with that job. Once this association is + made and synced to disk, you can then manage the job normally." + @name = :cron + @namevar = :name + + @loaded = {} + + @synced = {} + + @instances = {} + + case Facter["operatingsystem"].value + when "SunOS": + @crontype = Puppet::CronType::SunOS + else + @crontype = Puppet::CronType::Default + end - class << self - attr_accessor :crontype - end + class << self + attr_accessor :crontype + end - attr_accessor :uid + attr_accessor :uid - # Override the Puppet::Type#[]= method so that we can store the instances - # in per-user arrays. Then just call +super+. - def self.[]=(name, object) - self.instance(object) - super - end + # Override the Puppet::Type#[]= method so that we can store the instances + # in per-user arrays. Then just call +super+. + def self.[]=(name, object) + self.instance(object) + super + end - # In addition to removing the instances in @objects, Cron has to remove - # per-user cron tab information. - def self.clear - @instances = {} - @loaded = {} - @synced = {} - super - end + # In addition to removing the instances in @objects, Cron has to remove + # per-user cron tab information. + def self.clear + @instances = {} + @loaded = {} + @synced = {} + super + end - # Override the default Puppet::Type method, because instances - # also need to be deleted from the @instances hash - def self.delete(child) - if @instances.include?(child[:user]) - if @instances[child[:user]].include?(child) - @instances[child[:user]].delete(child) - end + # Override the default Puppet::Type method, because instances + # also need to be deleted from the @instances hash + def self.delete(child) + if @instances.include?(child[:user]) + if @instances[child[:user]].include?(child) + @instances[child[:user]].delete(child) end - super end + super + end - # Return the fields found in the cron tab. - def self.fields - return [:minute, :hour, :monthday, :month, :weekday, :command] - end + # Return the fields found in the cron tab. + def self.fields + return [:minute, :hour, :monthday, :month, :weekday, :command] + end - # Return the header placed at the top of each generated file, warning - # users that modifying this file manually is probably a bad idea. - def self.header + # Return the header placed at the top of each generated file, warning + # users that modifying this file manually is probably a bad idea. + def self.header %{#This file was autogenerated at #{Time.now} by puppet. While it # can still be managed manually, it is definitely not recommended. # Note particularly that the comments starting with 'Puppet Name' should # not be deleted, as doing so could cause duplicate cron jobs.\n} - end + end - # Store a new instance of a cron job. Called from Cron#initialize. - def self.instance(obj) - user = obj[:user] - if @instances.include?(user) - unless @instances[obj[:user]].include?(obj) - @instances[obj[:user]] << obj - end - else - @instances[obj[:user]] = [obj] + # Store a new instance of a cron job. Called from Cron#initialize. + def self.instance(obj) + user = obj[:user] + if @instances.include?(user) + unless @instances[obj[:user]].include?(obj) + @instances[obj[:user]] << obj end + else + @instances[obj[:user]] = [obj] end + end - # Parse a user's cron job into individual cron objects. - # - # Autogenerates names for any jobs that don't already have one; these - # names will get written back to the file. - # - # This method also stores existing comments, and it stores all cron - # jobs in order, mostly so that comments are retained in the order - # they were written and in proximity to the same jobs. - def self.parse(user, text) - count = 0 - hash = {} - name = nil - unless @instances.include?(user) - @instances[user] = [] - end - text.chomp.split("\n").each { |line| - case line - when /^# Puppet Name: (\w+)$/: name = $1 - when /^#/: - # add other comments to the list as they are - @instances[user] << line - else - if match = /^(\S+) (\S+) (\S+) (\S+) (\S+) (.+)$/.match(line) - fields().zip(match.captures).each { |param, value| - unless value == "*" - unless param == :command - if value =~ /,/ - value = value.split(",") - end + # Parse a user's cron job into individual cron objects. + # + # Autogenerates names for any jobs that don't already have one; these + # names will get written back to the file. + # + # This method also stores existing comments, and it stores all cron + # jobs in order, mostly so that comments are retained in the order + # they were written and in proximity to the same jobs. + def self.parse(user, text) + count = 0 + hash = {} + name = nil + unless @instances.include?(user) + @instances[user] = [] + end + text.chomp.split("\n").each { |line| + case line + when /^# Puppet Name: (\w+)$/: name = $1 + when /^#/: + # add other comments to the list as they are + @instances[user] << line + else + if match = /^(\S+) (\S+) (\S+) (\S+) (\S+) (.+)$/.match(line) + fields().zip(match.captures).each { |param, value| + unless value == "*" + unless param == :command + if value =~ /,/ + value = value.split(",") end - hash[param] = value end + hash[param] = value + end + } + else + raise Puppet::Error, "Could not match '%s'" % line + end + + cron = nil + unless name + Puppet.info "Autogenerating name for %s" % hash[:command] + name = "cron-%s" % hash.object_id + end + + unless hash.include?(:command) + raise Puppet::DevError, "No command for %s" % name + end + # if the cron already exists with that name... + if cron = Puppet.type(:cron)[name] + # do nothing... + elsif tmp = @instances[user].reject { |obj| + ! obj.is_a?(self) + }.find { |obj| + obj.should(:command) == hash[:command] } - else - raise Puppet::Error, "Could not match '%s'" % line - end + # if we can find a cron whose spec exactly matches - cron = nil - unless name - Puppet.info "Autogenerating name for %s" % hash[:command] - name = "cron-%s" % hash.object_id - end + # we now have a cron job whose command exactly matches + # let's see if the other fields match + txt = tmp.to_cron.sub(/#.+\n/,'') - unless hash.include?(:command) - raise Puppet::DevError, "No command for %s" % name - end - # if the cron already exists with that name... - if cron = Puppet::Type::Cron[name] - # do nothing... - elsif tmp = @instances[user].reject { |obj| - ! obj.is_a?(Cron) - }.find { |obj| - obj.should(:command) == hash[:command] - } - # if we can find a cron whose spec exactly matches - - # we now have a cron job whose command exactly matches - # let's see if the other fields match - txt = tmp.to_cron.sub(/#.+\n/,'') - - if txt == line - cron = tmp - end - else - # create a new cron job, since no existing one - # seems to match - cron = Puppet::Type::Cron.create( - :name => name - ) + if txt == line + cron = tmp end - - hash.each { |param, value| - cron.is = [param, value] - } - hash.clear - name = nil - count += 1 + else + # create a new cron job, since no existing one + # seems to match + cron = self.create( + :name => name + ) end - } - end - # Retrieve a given user's cron job, using the @crontype's +retrieve+ - # method. Returns nil if there was no cron job; else, returns the - # number of cron instances found. - def self.retrieve(user) - text = @crontype.read(user) - if $? != 0 - # there is no cron file - return nil - else - self.parse(user, text) + hash.each { |param, value| + cron.is = [param, value] + } + hash.clear + name = nil + count += 1 end + } + end - @loaded[user] = Time.now + # Retrieve a given user's cron job, using the @crontype's +retrieve+ + # method. Returns nil if there was no cron job; else, returns the + # number of cron instances found. + def self.retrieve(user) + text = @crontype.read(user) + if $? != 0 + # there is no cron file + return nil + else + self.parse(user, text) end - # Store the user's cron tab. Collects the text of the new tab and - # sends it to the +@crontype+ module's +write+ function. Also adds - # header warning users not to modify the file directly. - def self.store(user) - if @instances.include?(user) - @crontype.write(user, self.header + self.tab(user)) - @synced[user] = Time.now - else - Puppet.notice "No cron instances for %s" % user - end - end + @loaded[user] = Time.now + end - # Collect all Cron instances for a given user and convert them - # into literal text. - def self.tab(user) - if @instances.include?(user) - return @instances[user].collect { |obj| - if obj.is_a?(Cron) - obj.to_cron - else - obj.to_s - end - }.join("\n") + "\n" - else - Puppet.notice "No cron instances for %s" % user - end + # Store the user's cron tab. Collects the text of the new tab and + # sends it to the +@crontype+ module's +write+ function. Also adds + # header warning users not to modify the file directly. + def self.store(user) + if @instances.include?(user) + @crontype.write(user, self.header + self.tab(user)) + @synced[user] = Time.now + else + Puppet.notice "No cron instances for %s" % user end + end - # Return the last time a given user's cron tab was loaded. Could - # be used for reducing writes, but currently is not. - def self.loaded?(user) - if @loaded.include?(user) - return @loaded[user] - else - return nil - end + # Collect all Cron instances for a given user and convert them + # into literal text. + def self.tab(user) + if @instances.include?(user) + return @instances[user].collect { |obj| + if obj.is_a? self + obj.to_cron + else + obj.to_s + end + }.join("\n") + "\n" + else + Puppet.notice "No cron instances for %s" % user end + end - def paramuser=(user) - require 'etc' - - begin - obj = Etc.getpwnam(user) - @uid = obj.uid - rescue ArgumentError - raise Puppet::Error, "User %s not found" % user - end - @parameters[:user] = user + # Return the last time a given user's cron tab was loaded. Could + # be used for reducing writes, but currently is not. + def self.loaded?(user) + if @loaded.include?(user) + return @loaded[user] + else + return nil end + end - # Override the default Puppet::Type method because we need to call - # the +@crontype+ retrieve method. - def retrieve - unless @parameters.include?(:user) - raise Puppet::Error, "You must specify the cron user" - end + def paramuser=(user) + require 'etc' - self.class.retrieve(@parameters[:user]) - self.eachstate { |st| st.retrieve } + begin + obj = Etc.getpwnam(user) + @uid = obj.uid + rescue ArgumentError + raise Puppet::Error, "User %s not found" % user end + @parameters[:user] = user + end - # Write the entire user's cron tab out. - def store - self.class.store(@parameters[:user]) + # Override the default Puppet::Type method because we need to call + # the +@crontype+ retrieve method. + def retrieve + unless @parameters.include?(:user) + raise Puppet::Error, "You must specify the cron user" end - # Convert the current object a cron-style string. Adds the cron name - # as a comment above the cron job, in the form '# Puppet Name: <name>'. - def to_cron - hash = {:command => @states[:command].should || @states[:command].is } + self.class.retrieve(@parameters[:user]) + self.eachstate { |st| st.retrieve } + end - # Collect all of the values that we have - self.class.fields().reject { |f| f == :command }.each { |param| - if @states.include?(param) - hash[param] = @states[param].should_to_s - end - } + # Write the entire user's cron tab out. + def store + self.class.store(self[:user]) + end - return "# Puppet Name: %s\n" % self.name + - self.class.fields.collect { |f| - hash[f] || "*" - }.join(" ") - end + # Convert the current object a cron-style string. Adds the cron name + # as a comment above the cron job, in the form '# Puppet Name: <name>'. + def to_cron + hash = {:command => @states[:command].should || @states[:command].is } + + # Collect all of the values that we have + self.class.fields().reject { |f| f == :command }.each { |param| + if @states.include?(param) + hash[param] = @states[param].should_to_s + end + } + + return "# Puppet Name: %s\n" % self.name + + self.class.fields.collect { |f| + hash[f] || "*" + }.join(" ") end end end diff --git a/lib/puppet/type/exec.rb b/lib/puppet/type/exec.rb index 1c45b7b7f..4d4e1d624 100755 --- a/lib/puppet/type/exec.rb +++ b/lib/puppet/type/exec.rb @@ -1,18 +1,33 @@ +module Puppet + newtype(:exec) do + @doc = "Executes external commands. It is critical that all commands + executed using this mechanism can be run multiple times without + harm, i.e., they are *idempotent*. One useful way to create idempotent + commands is to use the *creates* parameter. + + It is worth nothing that ``exec`` is special, in that it is not + currently considered an error to have multiple ``exec`` instances + with the same name. This was done purely because it had to be this + way in order to get certain functionality, but it complicates things. + In particular, you will not be able to use ``exec`` instances that + share their commands with other instances as a dependency, since + Puppet has no way of knowing which instance you mean. + + It is recommended to avoid duplicate names whenever possible." + + require 'open3' + require 'puppet/type/state' + + newstate(:returns) do |state| + munge do |value| + value.to_s + end -require 'open3' -require 'puppet/type/state' + defaultto "0" -module Puppet - # okay, how do we deal with parameters that don't have operations - # associated with them? - class State - # this always runs - class Returns < Puppet::State attr_reader :output - - @doc = "The expected return code. An error will be returned if the + desc "The expected return code. An error will be returned if the executed command returns something else." - @name = :returns # Make output a bit prettier def change_to_s @@ -90,14 +105,6 @@ module Puppet # handlers correctly Puppet::Util.asuser(@parent[:user], @parent[:group]) { # capture both stdout and stderr - - #stdin, stdout, stderr = Open3.popen3(self.parent[:command]) - #@output = stdout.read - #err = stderr.read - - #stderr = Puppet::Util.capture_stderr { - # @output = %x{#{self.parent[:command]}} - #} if @parent[:user] unless defined? @@alreadywarned Puppet.warning( @@ -109,12 +116,6 @@ module Puppet else @output = %x{#{self.parent[:command]} 2>&1} end - - #if err != "" - # stderr.split(/\n/).each { |line| - # self.send(:err, line) - # } - #end } status = $? @@ -142,122 +143,52 @@ module Puppet return :executed_command end end - end - class Type - class Exec < Type - # this is kind of hackish, using the return value as the - # state, but apparently namevars can't also be states - # who knew? - @states = [ - Puppet::State::Returns - ] - - @parameters = [ - :path, - :user, - :group, - :creates, - :cwd, - :refreshonly, - :command - ] - - @paramdoc[:path] = "The search path used for command execution. - Commands must be fully qualified if no path is specified." - @paramdoc[:user] = "The user to run the command as. Note that if you use - this then any error output is not currently captured. This is mostly - because of a bug within Ruby." - @paramdoc[:group] = "The group to run the command as." - @paramdoc[:cwd] = "The directory from which to run the command. If - this directory does not exist, the command will fail." - @paramdoc[:refreshonly] = "The command should only be run as a - refresh mechanism for when a dependent object is changed." - @paramdoc[:command] = "The actual command to execute." - @paramdoc[:creates] = "A file that this command creates. If this - parameter is provided, then the command will only be run - if the specified file does not exist." + newparam(:command) do + isnamevar + desc "The actual command to execute." + end - @doc = "Executes external commands. It is critical that all commands - executed using this mechanism can be run multiple times without - harm, i.e., they are *idempotent*. One useful way to create idempotent - commands is to use the *creates* parameter. - - It is worth nothing that ``exec`` is special, in that it is not - currently considered an error to have multiple ``exec`` instances - with the same name. This was done purely because it had to be this - way in order to get certain functionality, but it complicates things. - In particular, you will not be able to use ``exec`` instances that - share their commands with other instances as a dependency, since - Puppet has no way of knowing which instance you mean. - - It is recommended to avoid duplicate names whenever possible." - @name = :exec - @namevar = :command - - # Exec names are not isomorphic with the objects. - @isomorphic = false - - def initialize(hash) - # default to erroring on a non-zero return - if hash.include?("returns") - if hash["returns"].is_a?(Fixnum) - hash["returns"] = hash["returns"].to_s - end - elsif hash.include?(:returns) - if hash[:returns].is_a?(Fixnum) - hash[:returns] = hash[:returns].to_s - end - else - hash[:returns] = "0" - end + newparam(:path) do + desc "The search path used for command execution. + Commands must be fully qualified if no path is specified." + end - super + newparam(:user) do + desc "The user to run the command as. Note that if you + use this then any error output is not currently captured. This + is mostly because of a bug within Ruby." - if self[:command].nil? - raise TypeError.new("Somehow the command is nil") + munge do |user| + unless Process.uid == 0 + raise Puppet::Error, + "Only root can execute commands as other users" end + require 'etc' - # if we're not fully qualified, require a path - if self[:command] !~ /^\// - if self[:path].nil? - raise TypeError, - "both unqualifed and specified no search path" - end + method = :getpwnam + case user + when Integer + method = :getpwuid + when /^\d+$/ + user = user.to_i + method = :getpwuid end - end - - def output - if self.state(:returns).nil? - return nil - else - return self.state(:returns).output + begin + Etc.send(method, user) + rescue ArgumentError + raise Puppet::Error, "No such user %s" % user end - end - # FIXME if they try to set this and fail, then we should probably - # fail the entire exec, right? - def paramcreates=(file) - unless file =~ %r{^#{File::SEPARATOR}} - raise Puppet::Error, "'creates' files must be fully qualified." - end - @parameters[:creates] = file + return user end + end - def paramcwd=(dir) - if dir.is_a?(Array) - dir = dir[0] - end - - unless File.directory?(dir) - raise Puppet::Error, "Directory '%s' does not exist" % dir - end - - @parameters[:cwd] = dir - end + newparam(:group) do + desc "The group to run the command as." # Execute the command as the specified group - def paramgroup=(group) + munge do |group| require 'etc' method = :getgrnam case group @@ -273,43 +204,75 @@ module Puppet raise Puppet::Error, "No such group %s" % group end - @parameters[:group] = group + group end + end - # Execute the command as the specified user - def paramuser=(user) - unless Process.uid == 0 - raise Puppet::Error, - "Only root can execute commands as other users" - end - require 'etc' + newparam(:cwd) do + desc "The directory from which to run the command. If + this directory does not exist, the command will fail." - method = :getpwnam - case user - when Integer - method = :getpwuid - when /^\d+$/ - user = user.to_i - method = :getpwuid + munge do |dir| + if dir.is_a?(Array) + dir = dir[0] end - begin - Etc.send(method, user) - rescue ArgumentError - raise Puppet::Error, "No such user %s" % user + + unless File.directory?(dir) + raise Puppet::Error, "Directory '%s' does not exist" % dir end + + dir + end + end + + newparam(:refreshonly) do + desc "The command should only be run as a + refresh mechanism for when a dependent object is changed." + end + + newparam(:creates) do + desc "A file that this command creates. If this + parameter is provided, then the command will only be run + if the specified file does not exist." - @parameters[:user] = user + # FIXME if they try to set this and fail, then we should probably + # fail the entire exec, right? + validate do |file| + unless file =~ %r{^#{File::SEPARATOR}} + raise Puppet::Error, "'creates' files must be fully qualified." + end end + end + + # Exec names are not isomorphic with the objects. + @isomorphic = false - # this might be a very, very bad idea... - def refresh - self.state(:returns).sync + validate do + # if we're not fully qualified, require a path + if self[:command] !~ /^\// + if self[:path].nil? + raise TypeError, + "both unqualifed and specified no search path" + end end + end - def to_s - "exec(%s)" % self.name + def output + if self.state(:returns).nil? + return nil + else + return self.state(:returns).output end end + + # this might be a very, very bad idea... + def refresh + self.state(:returns).sync + end + + def to_s + "exec(%s)" % self.name + end end end diff --git a/lib/puppet/type/group.rb b/lib/puppet/type/group.rb index 0f057b869..8a2301838 100755 --- a/lib/puppet/type/group.rb +++ b/lib/puppet/type/group.rb @@ -12,18 +12,24 @@ require 'puppet/type/state' require 'puppet/type/nameservice' module Puppet - class State - module GroupGID - def self.doc - "The group ID. Must be specified numerically. If not + newtype(:group, Puppet::Type::NSSType) do + @doc = "Manage groups. This type can only create groups. Group + membership must be managed on individual users." + + case Facter["operatingsystem"].value + when "Darwin": + @parentstate = Puppet::NameService::NetInfo::NetInfoState + @parentmodule = Puppet::NameService::NetInfo + else + @parentstate = Puppet::NameService::ObjectAdd::ObjectAddGroup + @parentmodule = Puppet::NameService::ObjectAdd + end + + newstate(:gid, @parentstate) do + desc "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 GID is picked according to local system standards." - end - - def self.name - :gid - end def autogen highest = 0 @@ -38,7 +44,7 @@ module Puppet return highest + 1 end - def shouldprocess(gid) + munge do |gid| case gid when String if gid =~ /^[-0-9]+$/ @@ -62,88 +68,57 @@ module Puppet return gid end end - end - - class Type - class Group < Type - statenames = [ - "GroupGID" - ] - case Facter["operatingsystem"].value - when "Darwin": - @statemodule = Puppet::NameService::NetInfo - else - @statemodule = Puppet::NameService::ObjectAdd - end - @states = statenames.collect { |name| - fullname = @statemodule.to_s + "::" + name - begin - eval(fullname) - rescue NameError - raise Puppet::DevError, "Could not retrieve state class %s" % - fullname - end - }.each { |klass| - klass.complete - } - - @name = :group - @namevar = :name - - @parameters = [:name] - - class << self - attr_accessor :netinfodir - attr_accessor :statemodule - end + class << self + attr_accessor :netinfodir + end - @netinfodir = "groups" + @netinfodir = "groups" - @paramdoc[:name] = "The group name. While naming limitations vary by + newparam(:name) do + desc "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." - @doc = "Manage groups. This type can only create groups. Group - membership must be managed on individual users." + isnamevar + end - def exists? - self.class.statemodule.exists?(self) - end + def exists? + self.class.parentmodule.exists?(self) + end - def getinfo(refresh = false) - if @groupinfo.nil? or refresh == true - begin - @groupinfo = Etc.getgrnam(self.name) - rescue ArgumentError => detail - @groupinfo = nil - end + def getinfo(refresh = false) + if @groupinfo.nil? or refresh == true + begin + @groupinfo = Etc.getgrnam(self.name) + rescue ArgumentError => detail + @groupinfo = nil end - - @groupinfo - end - - def initialize(hash) - @groupinfo = nil - super end - def retrieve - if self.exists? - super - else - # the group does not exist - unless @states.include?(:gid) - self[:gid] = :auto - end + @groupinfo + end - @states.each { |name, state| - state.is = :notfound - } + def initialize(hash) + @groupinfo = nil + super + end - return + def retrieve + if self.exists? + super + else + # the group does not exist + unless @states.include?(:gid) + self[:gid] = :auto end + + @states.each { |name, state| + state.is = :notfound + } + + return end end end diff --git a/lib/puppet/type/nameservice.rb b/lib/puppet/type/nameservice.rb index 2e289936a..4fccaeb17 100755 --- a/lib/puppet/type/nameservice.rb +++ b/lib/puppet/type/nameservice.rb @@ -1,3 +1,225 @@ -require 'puppet/type/nameservice/posix' -require 'puppet/type/nameservice/netinfo' -require 'puppet/type/nameservice/objectadd' +require 'puppet/type' + +module Puppet +class Type + class NSSType < Puppet::Type + class << self + attr_reader :parentstate, :parentmodule + + def newstate(*args, &block) + s = super(*args, &block) + + if s.respond_to?(:finish) + s.finish + end + end + end + end +end + +class State + # This is the state that all other Nameservice states descend from. It sets + # the standard for how one interacts with these state objects, but it leaves + # out any implementation details. See the 'posix' stuff for the basics + # on how to retrieve information on most systems, but any adding of information + # is done specially per-system (e.g., netinfo, useradd, adduser). + class NSSState < Puppet::State + class << self + # Are all changes to states done in one step or do all states need + # to be synced individually? This differentiates between netinfo, + # in which creation cannot be used to fill out information, and + # things like useradd, in which creation can be done with all + # information in one swell foop. + def allatonce? + Puppet.info "Returning allatonce %s" % @allatonce + if defined? @allatonce + return @allatonce + else + return false + end + end + + # Yes, this value will autogenerate. + def isautogen + @isautogen = true + end + + # Can we autogenerate a value for this field? If a required field + # can be autogenerated then we don't require a value. + def autogen? + if self.method_defined?(:autogen) or + (defined? @isautogen and @isautogen) + return true + else + return false + end + end + + # Yes, this field is optional + def isoptional + @isoptional = true + end + + # Is this field optional? Defaults to false. + def isoptional? + if defined? @isoptional + return @isoptional + else + return false + end + end + + # What method is used to retrieve the value from the POSIX struct? + # Really, these method names should be stored in this class somewhere, + # in a map or something, but then I would have to differentiate + # between the different posix classes (e.g., user and group). In the + # end, we're _only_ using classes that _do_ have posix structs, + # so we might as well store the method in the class definition, + # rather than forcing it to be abstracted out or whatever. + def posixmethod + if defined? @posixmethod + return @posixmethod + else + return self.name + end + end + end + + # We use the POSIX interfaces to retrieve all information, so we don't + # have to worry about abstracting that across the system. Any class + # can still override this, but it should work for the vast majority of + # cases. + def retrieve + if obj = @parent.getinfo(true) + + if method = self.class.posixmethod || self.class.name + @is = obj.send(method) + else + raise Puppet::DevError, + "%s has no posixmethod" % self.class + end + else + @is = :notfound + end + end + + # Sync the information. + def sync + event = nil + # they're in sync some other way + if self.insync? + return nil + end + if @is == :notfound + self.retrieve + 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 self.should == :notfound + Puppet.info "creating" + event = syncname() + + Puppet.info "created with event %s" % event + + return event + # if the whole object is created at once, just return + # an event saying so + #if self.class.allatonce? + # return event + #end + end + + unless @parent.exists? + raise Puppet::DevError, + "%s %s does not exist; cannot set %s" % + [@parent.class.name, @parent.name, self.class.name] + end + + # this needs to be set either by the individual state + # or its parent class + cmd = self.modifycmd + + self.debug "Executing %s" % cmd.inspect + + output = %x{#{cmd} 2>&1} + + + unless $? == 0 + raise Puppet::Error, "Could not modify %s on %s %s: %s" % + [self.class.name, @parent.class.name, + @parent.name, output] + end + + if event + return event + else + return "#{@parent.class.name}_modified".intern + end + end + + private + # This is only used when creating or destroying the object. + def syncname + Puppet.info "Creating %s" % self.name + cmd = nil + event = nil + if self.should == :notfound + # we need to remove the object... + unless @parent.exists? + # the group already doesn't exist + return nil + end + + # again, needs to be set by the ind. state or its + # parent + cmd = self.deletecmd + type = "delete" + else + if @parent.exists? + return nil + end + + # blah blah, define elsewhere, blah blah + cmd = self.addcmd + type = "create" + end + self.debug "Executing %s" % cmd.inspect + + output = %x{#{cmd} 2>&1} + + unless $? == 0 + raise Puppet::Error, "Could not %s %s %s: %s" % + [type, @parent.class.name, @parent.name, output] + end + + # we want object creation to show up as one event, + # not many + unless self.class.allatonce? + Puppet.debug "%s is not allatonce" % self.class.name + if type == "create" + @parent.eachstate { |state| + state.sync + state.retrieve + } + end + end + + return "#{@parent.class.name}_#{type}d".intern + end + end +end +end + +# Here's where we decide what type of objects we'll be dealing with. +case Facter["operatingsystem"].value +when "Darwin": + require 'puppet/type/nameservice/netinfo' +else + require 'puppet/type/nameservice/objectadd' +end + + +# $Id$ diff --git a/lib/puppet/type/nameservice/netinfo.rb b/lib/puppet/type/nameservice/netinfo.rb index e3e2bdece..f8334c774 100644 --- a/lib/puppet/type/nameservice/netinfo.rb +++ b/lib/puppet/type/nameservice/netinfo.rb @@ -5,11 +5,6 @@ require 'puppet' require 'puppet/type/nameservice/posix' module Puppet - class Type - # Return the NetInfo directory in which a given object type is stored. - # Defaults to the type's name if @netinfodir is unset. - end - module NameService module NetInfo # Verify that we've got all of the commands we need. @@ -56,16 +51,22 @@ module Puppet end end - class NetInfoState < POSIX::POSIXState - # Should we do all of the changes at once? - def self.allatonce? - false - end + # The state responsible for handling netinfo objects. Because they + # are all accessed using the exact same interface, we can just + # abstract the differents using a simple map where necessary + # (the netinfokeymap). + class NetInfoState < Puppet::State::NSSState + @netinfokeymap = { + :comment => "realname" + } - # Similar to posixmethod, what key do we use to get data? + @@allatonce = false + + # Similar to posixmethod, what key do we use to get data? Defaults + # to being the object name. def self.netinfokey - if defined? @netinfokey and @netinfokey - return @netinfokey + if @netinfokeymap.include?(self.name) + return @netinfokeymap[self.name] else return self.name end @@ -147,34 +148,6 @@ module Puppet cmd.join(" ") end end - - class GroupGID < NetInfoState - end - - class UserUID < NetInfoState - end - - class UserGID < NetInfoState - end - - class UserComment < NetInfoState - @netinfokey = "realname" - end - - class UserHome < NetInfoState - end - - class UserShell < NetInfoState - end - - class UserLocked < NetInfoState - end - - class UserExpire < NetInfoState - end - - class UserInactive < NetInfoState - end end end end diff --git a/lib/puppet/type/nameservice/objectadd.rb b/lib/puppet/type/nameservice/objectadd.rb index cc25c5f5f..df2a94902 100644 --- a/lib/puppet/type/nameservice/objectadd.rb +++ b/lib/puppet/type/nameservice/objectadd.rb @@ -1,17 +1,6 @@ require 'puppet' module Puppet - class State - # The flag to use to add an object - def self.objectaddflag - if defined? @objectaddflag and @objectaddflag - return @objectaddflag - else - return @name - end - end - end - module NameService module ObjectAdd # Verify that we've got the commands necessary to manage flat files. @@ -35,16 +24,45 @@ module Puppet end end - class ObjectAddGroup < POSIX::POSIXState - def self.allatonce? - true + # The base state class for <object>add operations. + class ObjectAddState < Puppet::State::NSSState + class << self + # Determine the flag to pass to our command. + def objectaddflag + unless defined? @objectaddflag + # Else, return the first letter of the name. I have to + # user a range here, else the character will show up + # as a number, rather than as a string, for some reason. + @objectaddflag = "-" + self.name.to_s[0,1] + Puppet.debug "Setting flag on %s to %s" % + [self.name, @objectaddflag] + end + return @objectaddflag + end + + # Set the flag manually. + def setflag(value) + @objectaddflag = value + end + end + end + + # The state class for doing group operations using groupadd or whatever. + # I could probably have abstracted the User and Group classes into + # a single class, but eh, it just didn't seem worth it. + class ObjectAddGroup < ObjectAddState + class << self + # This is hackish, but hey, it works. + def finish + @allatonce = true + end end def addcmd cmd = ["groupadd"] if gid = @parent.should(:gid) unless gid == :auto - cmd << "-g" << gid + cmd << self.class.objectaddflag << gid end end cmd << @parent.name @@ -66,16 +84,17 @@ module Puppet end end - class GroupGID < ObjectAddGroup - @objectaddflag = "-g" - @autogen = true - end - - class ObjectAddUser < POSIX::POSIXState - def self.allatonce? - true + # The class for adding users using 'adduser'. + class ObjectAddUser < ObjectAddState + class << self + # This is hackish, but hey, it works. + def finish + @allatonce = true + case self.name + when :home: setflag "-d" + end + end end - def addcmd cmd = ["useradd"] @parent.eachstate { |state| @@ -99,50 +118,14 @@ module Puppet end def modifycmd - cmd = [ - "usermod", - self.class.objectaddflag, - "'%s'" % self.should, - @parent.name - ].join(" ") + cmd = [ + "usermod", + self.class.objectaddflag, + "'%s'" % self.should, + @parent.name + ].join(" ") end end - class UserUID < ObjectAddUser - @objectaddflag = "-u" - @autogen = true - end - - class UserGID < ObjectAddUser - @objectaddflag = "-g" - @autogen = true - end - - class UserComment < ObjectAddUser - @objectaddflag = "-c" - end - - class UserHome < ObjectAddUser - @objectaddflag = "-d" - @autogen = true - end - - class UserShell < ObjectAddUser - @objectaddflag = "-s" - @autogen = true - end - - class UserLocked < ObjectAddUser - end - - class UserExpire < ObjectAddUser - @objectaddflag = "-e" - @autogen = true - end - - class UserInactive < ObjectAddUser - @objectaddflag = "-f" - @autogen = true - end end end end diff --git a/lib/puppet/type/nameservice/posix.rb b/lib/puppet/type/nameservice/posix.rb index 41b069f9d..cf8c371ef 100644 --- a/lib/puppet/type/nameservice/posix.rb +++ b/lib/puppet/type/nameservice/posix.rb @@ -1,210 +1,12 @@ require 'puppet' +require 'puppet/type/nameservice' module Puppet - class Type # :nodoc: - # The method that returns what we want from the POSIX struct - def self.posixmethod - if defined? @posixmethod and @posixmethod - return @posixmethod - else - return @name - end - end - end - - module NameService - # This is the base module for basically all of the NSS stuff. It - # should be able to retrieve the info for almost any system, but - # it can't create any information on its own. You need to define - # a subclass of these classes to actually modify the system. - module POSIX - class POSIXState < Puppet::State - class << self - attr_accessor :extender - - # Can we autogenerate a value for this field? If a required field - # can be autogenerated then we don't require a value. - def autogen? - if defined? @autogen - return @autogen - else - return false - end - end - - # Is this field optional? - def optional? - if defined? @optional - return @optional - else - return false - end - end - end - - # The documentation for our module comes from the extending class, - # not directly from the object - def self.doc - if defined? @extender - @extender.doc - else - nil - end - end - - # Find the module to use to extend the current class - def self.complete - mod = "Puppet::State::%s" % - self.to_s.sub(/.+::/,'') - begin - modklass = eval(mod) - rescue NameError - raise Puppet::Error, - "Could not find extender module %s for %s" % - [mod, self.to_s] - end - include modklass - - self.extender = modklass - end - - # Return the method that we'll call on the POSIX struct. - def self.posixmethod - if defined? @extender - if @extender.respond_to?(:posixmethod) - return @extender.posixmethod - else - return @extender.name - end - else - raise Puppet::DevError, - "%s could not retrieve posixmethod" % self - end - end - - def self.name - @extender.name - end - - # we use the POSIX interfaces to retrieve all information, - # so we don't have to worry about abstracting that across - # the system - def retrieve - if obj = @parent.getinfo(true) - - if method = self.class.posixmethod || self.class.name - @is = obj.send(method) - else - raise Puppet::DevError, - "%s has no posixmethod" % self.class - end - else - @is = :notfound - end - end - - # Sync the information. - def sync - event = nil - # they're in sync some other way - if self.insync? - return nil - end - if @is == :notfound - self.retrieve - 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 self.should == :notfound - event = syncname() - - return event - # if the whole object is created at once, just return - # an event saying so - #if self.class.allatonce? - # return event - #end - end - - unless @parent.exists? - raise Puppet::DevError, - "%s %s does not exist; cannot set %s" % - [@parent.class.name, @parent.name, self.class.name] - end - - # this needs to be set either by the individual state - # or its parent class - cmd = self.modifycmd - - self.debug "Executing %s" % cmd.inspect - - output = %x{#{cmd} 2>&1} - - - unless $? == 0 - raise Puppet::Error, "Could not modify %s on %s %s: %s" % - [self.class.name, @parent.class.name, - @parent.name, output] - end - - if event - return event - else - return "#{@parent.class.name}_modified".intern - end - end - - private - # This is only used when creating or destroying the object. - def syncname - cmd = nil - event = nil - if self.should == :notfound - # we need to remove the object... - unless @parent.exists? - # the group already doesn't exist - return nil - end - - # again, needs to be set by the ind. state or its - # parent - cmd = self.deletecmd - type = "delete" - else - if @parent.exists? - return nil - end - - # blah blah, define elsewhere, blah blah - cmd = self.addcmd - type = "create" - end - self.debug "Executing %s" % cmd.inspect - - output = %x{#{cmd} 2>&1} - - unless $? == 0 - raise Puppet::Error, "Could not %s %s %s: %s" % - [type, @parent.class.name, @parent.name, output] - end - - # we want object creation to show up as one event, - # not many - unless self.class.allatonce? - if type == "create" - @parent.eachstate { |state| - state.sync - state.retrieve - } - end - end - - return "#{@parent.class.name}_#{type}d".intern - end - end + class State + # The lowest-level state class for managing NSS/POSIX information. It + # at least knows how to retrieve information, but it does not know how + # to sync anything. + class POSIXState < NSSState end end end diff --git a/lib/puppet/type/package.rb b/lib/puppet/type/package.rb index 45032c206..24246f908 100644 --- a/lib/puppet/type/package.rb +++ b/lib/puppet/type/package.rb @@ -12,20 +12,47 @@ require 'puppet/type/package/sun.rb' module Puppet class PackageError < Puppet::Error; end - class State - class PackageInstalled < Puppet::State - @name = :install - - @doc = "What state the package should be in. Specifying *true* will - only result in a change if the package is not installed at all; use - *latest* to keep the package (and, depending on the package system, its - prerequisites) up to date. Specifying *false* will uninstall the - package if it is installed. *true*/*false*/*latest*/``version``" + newtype(:package) do + @doc = "Manage packages. Eventually will support retrieving packages + from remote sources but currently only supports packaging + systems which can retrieve their own packages, like ``apt``." + + newstate(:install) do + desc "What state the package should be in. Specifying *true* will + only result in a change if the package is not installed at all; + use *latest* to keep the package (and, depending on the package + system, its prerequisites) up to date. Specifying *false* will + uninstall the package if it is installed. + *true*/*false*/*latest*/``version``" + + munge do |value| + # possible values are: true, false, and a version number + case value + when "latest": + unless @parent.respond_to?(:latest) + self.err @parent.inspect + raise Puppet::Error, + "Package type %s does not support querying versions" % + @parent[:type] + end + return :latest + when true, :installed: + return :installed + when false, :notinstalled: + return :notinstalled + else + # We allow them to set a should value however they want, + # but only specific package types will be able to use this + # value + return value + end + end # Override the parent method, because we've got all kinds of # funky definitions of 'in sync'. def insync? - # Iterate across all of the should values, and see how they turn out. + # Iterate across all of the should values, and see how they + # turn out. @should.each { |should| case should when :installed @@ -58,29 +85,6 @@ module Puppet end end - def shouldprocess(value) - # possible values are: true, false, and a version number - case value - when "latest": - unless @parent.respond_to?(:latest) - self.err @parent.inspect - raise Puppet::Error, - "Package type %s does not support querying versions" % - @parent[:type] - end - return :latest - when true, :installed: - return :installed - when false, :notinstalled: - return :notinstalled - else - # We allow them to set a should value however they want, - # but only specific package types will be able to use this - # value - return value - end - end - def sync method = nil event = nil @@ -126,251 +130,249 @@ module Puppet return event end end - end - - class Type # packages are complicated because each package format has completely # different commands. We need some way to convert specific packages # into the general package object... - class Package < Type - attr_reader :version, :pkgtype - - @pkgtypes = [ - Puppet::PackagingType::APT, - Puppet::PackagingType::DPKG, - Puppet::PackagingType::RPM, - Puppet::PackagingType::Yum, - Puppet::PackagingType::Sun - ] - - @pkgtypehash = {} - - # Now collect the name of each type and store it thusly - @pkgtypes.each { |type| - if type.respond_to?(:typename) - @pkgtypehash[type.typename] = type - else - name = type.to_s.sub(/.+::/, '').downcase.intern - @pkgtypehash[name] = type - end - } - - @states = [ - Puppet::State::PackageInstalled - ] - #:version, - @parameters = [ - :name, - :type, - :instance, - :status, - :category, - :platform, - :root, - :vendor, - :description - ] - - @paramdoc[:name] = "The package name." - @paramdoc[:type] = "The package format. Currently supports " + - @pkgtypes.collect {|t| - "``" + t.name.to_s + "``" - }.join(", ") + "." - @paramdoc[:instance] = "A read-only parameter set by the package." - @paramdoc[:status] = "A read-only parameter set by the package." - #@paramdoc[:version] = "A read-only parameter set by the package." - @paramdoc[:category] = "A read-only parameter set by the package." - @paramdoc[:platform] = "A read-only parameter set by the package." - @paramdoc[:root] = "A read-only parameter set by the package." - @paramdoc[:vendor] = "A read-only parameter set by the package." - @paramdoc[:description] = "A read-only parameter set by the package." - - @doc = "Manage packages. Eventually will support retrieving packages - from remote sources but currently only supports packaging - systems which can retrieve their own packages, like ``apt``." - @name = :package - @namevar = :name - @listed = false + attr_reader :version, :pkgtype + + @pkgtypes = [ + Puppet::PackagingType::APT, + Puppet::PackagingType::DPKG, + Puppet::PackagingType::RPM, + Puppet::PackagingType::Yum, + Puppet::PackagingType::Sun + ] + + pkgstr = @pkgtypes.collect {|t| + "``" + t.name.to_s + "``" + }.join(", ") + + @pkgtypehash = {} + + # Now collect the name of each type and store it thusly + @pkgtypes.each { |type| + if type.respond_to?(:typename) + @pkgtypehash[type.typename] = type + else + name = type.to_s.sub(/.+::/, '').downcase.intern + @pkgtypehash[name] = type + end + } - @allowedmethods = [:types] + newparam(:name) do + desc "The package name." + isnamevar + end - @default = nil - @platform = nil + newparam(:type) do + desc "The package format. Currently supports " + pkgstr + end + newparam(:instance) do + desc "A read-only parameter set by the package." + end + newparam(:status) do + desc "A read-only parameter set by the package." + end + #newparam(:version) do + # desc "A read-only parameter set by the package." + #end + newparam(:category) do + desc "A read-only parameter set by the package." + end + newparam(:platform) do + desc "A read-only parameter set by the package." + end + newparam(:root) do + desc "A read-only parameter set by the package." + end + newparam(:vendor) do + desc "A read-only parameter set by the package." + end + newparam(:description) do + desc "A read-only parameter set by the package." + end + @name = :package + @namevar = :name + @listed = false + + @allowedmethods = [:types] + + @default = nil + @platform = nil - class << self - attr_reader :listed + class << self + attr_reader :listed + end + + def self.clear + @listed = false + super + end + + # Cache and return the default package type for our current + # platform. + def self.default + if @default.nil? + self.init end - def self.clear - @listed = false - super + return @default + end + + # Figure out what the default package type is for the platform + # on which we're running. + def self.init + unless @platform = Facter["operatingsystem"].value.downcase + raise Puppet::DevError.new( + "Must know platform for package management" + ) end + case @platform + when "sunos": @default = :sun + when "linux": + case Facter["distro"].value.downcase + when "gentoo": + Puppet.notice "No support for gentoo yet" + @default = nil + when "debian": @default = :apt + when "fedora": @default = :yum + when "redhat": @default = :rpm + else + Puppet.warning "Using rpm as default type for %s" % + Facter["distro"].value + @default = :rpm + end + else + @default = nil + end + end - # Cache and return the default package type for our current - # platform. - def self.default + def self.getpkglist + if @types.nil? if @default.nil? self.init end - - return @default + @types = [@default] end - # Figure out what the default package type is for the platform - # on which we're running. - def self.init - unless @platform = Facter["operatingsystem"].value.downcase - raise Puppet::DevError.new( - "Must know platform for package management" - ) - end - case @platform - when "sunos": @default = :sun - when "linux": - case Facter["distro"].value.downcase - when "gentoo": - Puppet.notice "No support for gentoo yet" - @default = nil - when "debian": @default = :apt - when "fedora": @default = :yum - when "redhat": @default = :rpm - else - Puppet.warning "Using rpm as default type for %s" % - Facter["distro"].value - @default = :rpm - end + list = @types.collect { |type| + if typeobj = Puppet::PackagingType[type] + # pull all of the objects + typeobj.list else - @default = nil - end - end - - def self.getpkglist - if @types.nil? - if @default.nil? - self.init - end - @types = [@default] + raise "Could not find package type '%s'" % type end + }.flatten + @listed = true + return list + end - list = @types.collect { |type| - if typeobj = Puppet::PackagingType[type] - # pull all of the objects - typeobj.list + def self.installedpkg(hash) + # this is from code, so we don't have to do as much checking + name = hash[:name] + hash.delete(:name) + + # if it already exists, modify the existing one + if object = Package[name] + states = {} + object.eachstate { |state| + Puppet.debug "Adding %s" % state.name.inspect + states[state.name] = state + } + hash.each { |var,value| + if states.include?(var) + Puppet.debug "%s is a set state" % var.inspect + states[var].is = value else - raise "Could not find package type '%s'" % type - end - }.flatten - @listed = true - return list - end + Puppet.debug "%s is not a set state" % var.inspect + if object[var] and object[var] != value + Puppet.warning "Overriding %s => %s on %s with %s" % + [var,object[var],name,value] + end - def Package.installedpkg(hash) - # this is from code, so we don't have to do as much checking - name = hash[:name] - hash.delete(:name) - - # if it already exists, modify the existing one - if object = Package[name] - states = {} - object.eachstate { |state| - Puppet.debug "Adding %s" % state.name.inspect - states[state.name] = state - } - hash.each { |var,value| + #object.state(var).is = value + + # swap the values if we're a state if states.include?(var) - Puppet.debug "%s is a set state" % var.inspect + Puppet.debug "Swapping %s because it's a state" % var states[var].is = value + states[var].should = nil else - Puppet.debug "%s is not a set state" % var.inspect - if object[var] and object[var] != value - Puppet.warning "Overriding %s => %s on %s with %s" % - [var,object[var],name,value] - end - - #object.state(var).is = value - - # swap the values if we're a state - if states.include?(var) - Puppet.debug "Swapping %s because it's a state" % var - states[var].is = value - states[var].should = nil - else - Puppet.debug "%s is not a state" % var.inspect - Puppet.debug "States are %s" % states.keys.collect { |st| - st.inspect - }.join(" ") - end + Puppet.debug "%s is not a state" % var.inspect + Puppet.debug "States are %s" % states.keys.collect { |st| + st.inspect + }.join(" ") end - } - return object - else # just create it - obj = self.create(:name => name) - hash.each { |var,value| - obj.addis(var,value) - } - return obj - end + end + } + return object + else # just create it + obj = self.create(:name => name) + hash.each { |var,value| + obj.addis(var,value) + } + return obj end + end - # Retrieve a package type by name; names are symbols. - def self.pkgtype(pkgtype) - if pkgtype.is_a?(String) - pkgtype = pkgtype.intern - end - return @pkgtypehash[pkgtype] + # Retrieve a package type by name; names are symbols. + def self.pkgtype(pkgtype) + if pkgtype.is_a?(String) + pkgtype = pkgtype.intern end + return @pkgtypehash[pkgtype] + end - # okay, there are two ways that a package could be created... - # either through the language, in which case the hash's values should - # be set in 'should', or through comparing against the system, in which - # case the hash's values should be set in 'is' - def initialize(hash) - type = hash["type"] || hash[:type] || self.class.default - self.type2module(type) + # okay, there are two ways that a package could be created... + # either through the language, in which case the hash's values should + # be set in 'should', or through comparing against the system, in which + # case the hash's values should be set in 'is' + def initialize(hash) + type = hash["type"] || hash[:type] || self.class.default + self.type2module(type) - super + super - self.debug "Extending to package type %s" % [@type] + self.debug "Extending to package type %s" % [@type] - unless @states.include?(:install) - self.debug "Defaulting to installing a package" - self[:install] = true - end + unless @states.include?(:install) + self.debug "Defaulting to installing a package" + self[:install] = true + end - unless @parameters.include?(:type) - self[:type] = self.class.default - end + unless @parameters.include?(:type) + self[:type] = self.class.default end + end - def retrieve - if hash = self.query - hash.each { |param, value| - unless self.class.validarg?(param) - hash.delete(param) - end - } + def retrieve + if hash = self.query + hash.each { |param, value| + unless self.class.validattr?(param) + hash.delete(param) + end + } - hash.each { |param, value| - self.is = [param, value] - } - else - self.class.validstates.each { |name| - self.is = [name, :notinstalled] - } - end + hash.each { |param, value| + self.is = [param, value] + } + else + self.class.validstates.each { |name| + self.is = [name, :notinstalled] + } end + end - # Extend the package with the appropriate package type. - def type2module(typename) - if type = self.class.pkgtype(typename) - @type = type - self.extend(type) - else - raise Puppet::Error, "Invalid package type %s" % typename - end + # Extend the package with the appropriate package type. + def type2module(typename) + if type = self.class.pkgtype(typename) + @type = type + self.extend(type) + else + raise Puppet::Error, "Invalid package type %s" % typename end - end # Puppet::Type::Package - end + end + end # Puppet.type(:package) # this is how we retrieve packages class PackageSource diff --git a/lib/puppet/type/package/dpkg.rb b/lib/puppet/type/package/dpkg.rb index 1d9b54aaf..48a1a497b 100755 --- a/lib/puppet/type/package/dpkg.rb +++ b/lib/puppet/type/package/dpkg.rb @@ -32,7 +32,7 @@ module Puppet fields.zip(match.captures) { |field,value| hash[field] = value } - #packages.push Puppet::Type::Package.installedpkg(hash) + #packages.push Puppet.type(:package).installedpkg(hash) else raise Puppet::DevError, "failed to match dpkg line %s" % line @@ -82,7 +82,7 @@ module Puppet fields.zip(match.captures) { |field,value| hash[field] = value } - packages.push Puppet::Type::Package.installedpkg(hash) + packages.push Puppet.type(:package).installedpkg(hash) else raise Puppet::DevError, "Failed to match dpkg line %s" % line diff --git a/lib/puppet/type/package/rpm.rb b/lib/puppet/type/package/rpm.rb index ed88af507..af340ff40 100755 --- a/lib/puppet/type/package/rpm.rb +++ b/lib/puppet/type/package/rpm.rb @@ -53,7 +53,7 @@ module Puppet fields.zip(match.captures) { |field,value| hash[field] = value } - packages.push Puppet::Type::Package.installedpkg(hash) + packages.push Puppet.type(:package).installedpkg(hash) else raise "failed to match rpm line %s" % line end diff --git a/lib/puppet/type/package/sun.rb b/lib/puppet/type/package/sun.rb index 65da29a31..90e5c579e 100755 --- a/lib/puppet/type/package/sun.rb +++ b/lib/puppet/type/package/sun.rb @@ -79,7 +79,7 @@ module Puppet process.each { |line| case line when /^$/: - packages.push Puppet::Type::Package.installedpkg(hash) + packages.push Puppet.type(:package).installedpkg(hash) hash.clear when /\s*(\w+):\s+(.+)/: name = $1 diff --git a/lib/puppet/type/pfile.rb b/lib/puppet/type/pfile.rb index 3d5a87011..dfe76b401 100644 --- a/lib/puppet/type/pfile.rb +++ b/lib/puppet/type/pfile.rb @@ -6,558 +6,517 @@ require 'fileutils' require 'puppet/type/state' require 'puppet/server/fileserver' -# We put all of the states in separate files, because there are so many -# of them. -require 'puppet/type/pfile/type' -require 'puppet/type/pfile/create' -require 'puppet/type/pfile/checksum' -require 'puppet/type/pfile/uid' -require 'puppet/type/pfile/mode' -require 'puppet/type/pfile/group' -require 'puppet/type/pfile/source' - module Puppet - class Type - class PFile < Type - @doc = "Manages local files, including setting ownership and - permissions, and allowing creation of both files and directories." - - @states = [ - Puppet::State::PFileCreate, - Puppet::State::PFileChecksum, - Puppet::State::PFileSource, - Puppet::State::PFileUID, - Puppet::State::PFileGroup, - Puppet::State::PFileMode, - Puppet::State::PFileType - ] - - @parameters = [ - :path, - :backup, - :linkmaker, - :recurse, - :ignore - ] - - @paramdoc[:path] = "The path to the file to manage. Must be fully - qualified." - - @paramdoc[:backup] = "Whether files should be backed up before + newtype(:file) do + @doc = "Manages local files, including setting ownership and + permissions, and allowing creation of both files and directories." + + newparam(:path) do + desc "The path to the file to manage. Must be fully qualified." + isnamevar + end + + newparam(:backup) do + desc "Whether files should be backed up before being replaced. If a ``filebucket`` is specified, files will be backed up there; else, they will be backed up in the same directory with a ``.puppet-bak`` extension." - @paramdoc[:linkmaker] = "An internal parameter used by the *symlink* + defaultto true + + munge do |value| + case value + when false, "false": + false + when true, "true": + ".puppet-bak" + when Array: + case value[0] + when "filebucket": + if bucket = Puppet.type(:filebucket).bucket(value[1]) + bucket + else + raise Puppet::Error, + "Could not retrieve filebucket %s" % + value[1] + end + else + raise Puppet::Error, "Invalid backup object type %s" % + value[0].inspect + end + else + raise Puppet::Error, "Invalid backup type %s" % + value.inspect + end + end + end + + newparam(:linkmaker) do + desc "An internal parameter used by the *symlink* type to do recursive link creation." + end - @paramdoc[:recurse] = "Whether and how deeply to do recursive + newparam(:recurse) do + desc "Whether and how deeply to do recursive management. **false**/*true*/*inf*/*number*" + end - @paramdoc[:ignore] = "A parameter which omits action on files matching + newparam(:ignore) do + desc "A parameter which omits action on files matching specified patterns during recursion. Uses Ruby's builtin globbing engine, so shell metacharacters are fully supported, e.g. ``[a-z]*``. Matches that would descend into the directory structure are ignored, e.g., ``*/*``." + + validate do |value| + unless value.is_a?(Array) or value.is_a?(String) + raise Puppet::DevError.new("Ignore must be a string or an Array") + end + end + end - #no longer a parameter - # @paramdoc[:source] = "Where to retrieve the contents of the files. - # Currently only supports local copying, but will eventually - # support multiple protocols for copying. Arguments are specified - # using either a full local path or using a URI (currently only - # *file* is supported as a protocol)." - - - - @name = :file - @namevar = :path - - @depthfirst = false + @depthfirst = false - PINPARAMS = [:mode, :type, :owner, :group, :checksum] + def argument?(arg) + @arghash.include?(arg) + end - def argument?(arg) - @arghash.include?(arg) + def handlebackup(file = nil) + # let the path be specified + file ||= self[:path] + # if they specifically don't want a backup, then just say + # we're good + unless FileTest.exists?(file) + return true end - def handlebackup(file = nil) - # let the path be specified - file ||= self[:path] - # if they specifically don't want a backup, then just say - # we're good - unless FileTest.exists?(file) - return true - end - - unless self[:backup] - return true - end + unless self[:backup] + return true + end - case File.stat(file).ftype - when "directory": - # we don't need to backup directories + case File.stat(file).ftype + when "directory": + # we don't need to backup directories + return true + when "file": + backup = self[:backup] + case backup + when Puppet::Client::Dipper: + sum = backup.backup(file) + self.info "Filebucketed %s with sum %s" % + [file, sum] return true - when "file": - backup = self[:backup] - case backup - when Puppet::Client::Dipper: - sum = backup.backup(file) - self.info "Filebucketed %s with sum %s" % - [file, sum] - return true - when String: - newfile = file + backup - if FileTest.exists?(newfile) - begin - File.unlink(newfile) - rescue => detail - self.err "Could not remove old backup: %s" % - detail - return false - end - end + when String: + newfile = file + backup + if FileTest.exists?(newfile) begin - FileUtils.cp(file, - file + backup) - return true + File.unlink(newfile) rescue => detail - # since they said they want a backup, let's error out - # if we couldn't make one - raise Puppet::Error.new("Could not back %s up: %s" % - [file, detail.message]) + self.err "Could not remove old backup: %s" % + detail + return false end - else - self.err "Invalid backup type %s" % backup - return false + end + begin + FileUtils.cp(file, + file + backup) + return true + rescue => detail + # since they said they want a backup, let's error out + # if we couldn't make one + raise Puppet::Error.new("Could not back %s up: %s" % + [file, detail.message]) end else - self.notice "Cannot backup files of type %s" % - File.stat(file).ftype + self.err "Invalid backup type %s" % backup return false end + else + self.notice "Cannot backup files of type %s" % + File.stat(file).ftype + return false end - - def handleignore(children) - @parameters[:ignore].each { |ignore| - ignored = [] - Dir.glob(File.join(self.name,ignore), File::FNM_DOTMATCH) { |match| - ignored.push(File.basename(match)) - } - children = children - ignored + end + + def handleignore(children) + @parameters[:ignore].value.each { |ignore| + ignored = [] + Dir.glob(File.join(self.name,ignore), File::FNM_DOTMATCH) { |match| + ignored.push(File.basename(match)) } - return children + children = children - ignored + } + return children + end + + def initialize(hash) + # clean out as many references to any file paths as possible + # this was the source of many, many bugs + + @arghash = self.argclean(hash) + @arghash.delete(self.class.namevar) + + if @arghash.include?(:source) + @arghash.delete(:source) end - - def initialize(hash) - # clean out as many references to any file paths as possible - # this was the source of many, many bugs - - @arghash = self.argclean(hash) - @arghash.delete(self.class.namevar) - - if @arghash.include?(:source) - @arghash.delete(:source) - end - @stat = nil - @parameters = Hash.new(false) + @stat = nil - # default to true - self[:backup] = true + # Used for caching clients + @clients = {} - # Used for caching clients - @clients = {} + super + end + + def newchild(path, hash = {}) + # make local copy of arguments + args = @arghash.dup - super + if path =~ %r{^#{File::SEPARATOR}} + raise Puppet::DevError.new( + "Must pass relative paths to PFile#newchild()" + ) + else + path = File.join(self.name, path) end - - def parambackup=(value) - case value - when false, "false": - @parameters[:backup] = false - when true, "true": - @parameters[:backup] = ".puppet-bak" - when Array: - case value[0] - when "filebucket": - if bucket = Puppet::Type::PFileBucket.bucket(value[1]) - @parameters[:backup] = bucket - else - @parameters[:backup] = ".puppet-bak" - raise Puppet::Error, - "Could not retrieve filebucket %s" % - value[1] - end - else - raise Puppet::Error, "Invalid backup object type %s" % - value[0].inspect - end - else - raise Puppet::Error, "Invalid backup type %s" % - value.inspect - end - end - - def paramignore=(value) - - #Make sure the value of ignore is in correct type - unless value.is_a?(Array) or value.is_a?(String) - raise Puppet::DevError.new("Ignore must be a string or an Array") - end - - @parameters[:ignore] = value - end - - def newchild(path, hash = {}) - # make local copy of arguments - args = @arghash.dup - if path =~ %r{^#{File::SEPARATOR}} - raise Puppet::DevError.new( - "Must pass relative paths to PFile#newchild()" - ) - else - path = File.join(self.name, path) - end + args[:path] = path - args[:path] = path - - unless hash.include?(:recurse) - if args.include?(:recurse) - if args[:recurse].is_a?(Integer) - self.notice "Decrementing recurse on %s" % path - args[:recurse] -= 1 # reduce the level of recursion - end + unless hash.include?(:recurse) + if args.include?(:recurse) + if args[:recurse].is_a?(Integer) + self.notice "Decrementing recurse on %s" % path + args[:recurse] -= 1 # reduce the level of recursion end - end - hash.each { |key,value| - args[key] = value + end + + hash.each { |key,value| + args[key] = value + } + + child = nil + klass = nil + if @parameters[:linkmaker] and args.include?(:source) and + ! FileTest.directory?(args[:source]) + klass = Puppet.type(:symlink) + + self.debug "%s is a link" % path + # clean up the args a lot for links + old = args.dup + args = { + :target => old[:source], + :path => path } + else + klass = self.class + end - child = nil - klass = nil - if @parameters[:linkmaker] and args.include?(:source) and - ! FileTest.directory?(args[:source]) - klass = Puppet::Type::Symlink - - self.debug "%s is a link" % path - # clean up the args a lot for links - old = args.dup - args = { - :target => old[:source], - :path => path - } - else - klass = self.class + # 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] + unless @children.include?(child) + self.notice "Not managing more explicit file %s" % + path + return nil end - - # 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] - unless @children.include?(child) - self.notice "Not managing more explicit file %s" % - path - return nil + args.each { |var,value| + next if var == :path + next if var == :name + # behave idempotently + unless child.should(var) == value + child[var] = value 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 - args[:parent] = self - begin - child = klass.implicitcreate(args) - - # implicit creation can return nil - if child.nil? - return nil - end - @children << child - rescue Puppet::Error => detail - self.notice( - "Cannot manage: %s" % - [detail.message] - ) - self.debug args.inspect - child = nil - rescue => detail - self.notice( - "Cannot manage: %s" % - [detail] - ) - self.debug args.inspect - child = nil + } + else # create it anew + #notice "Creating new file with args %s" % args.inspect + args[:parent] = self + begin + child = klass.implicitcreate(args) + + # implicit creation can return nil + if child.nil? + return nil end + @children << child + rescue Puppet::Error => detail + self.notice( + "Cannot manage: %s" % + [detail.message] + ) + self.debug args.inspect + child = nil + rescue => detail + self.notice( + "Cannot manage: %s" % + [detail] + ) + self.debug args.inspect + child = nil end - return child end - - # Paths are special for files, because we don't actually want to show - # the parent's full path. - def path - unless defined? @path - if defined? @parent - # We only need to behave specially when our parent is also - # a file - if @parent.is_a?(self.class) - # Remove the parent file name - ppath = @parent.path.sub(/\/?file=.+/, '') - @path = [] - if ppath != "/" and ppath != "" - @path << ppath - end - @path << self.class.name.to_s + "=" + self.name - else - super + return child + end + + # Paths are special for files, because we don't actually want to show + # the parent's full path. + def path + unless defined? @path + if defined? @parent + # We only need to behave specially when our parent is also + # a file + if @parent.is_a?(self.class) + # Remove the parent file name + ppath = @parent.path.sub(/\/?file=.+/, '') + @path = [] + if ppath != "/" and ppath != "" + @path << ppath end + @path << self.class.name.to_s + "=" + self.name else - # The top-level name is always puppet[top], so we don't - # bother with that. And we don't add the hostname - # here, it gets added in the log server thingy. - if self.name == "puppet[top]" - @path = ["/"] - else - # We assume that if we don't have a parent that we - # should not cache the path - @path = [self.class.name.to_s + "=" + self.name] - end + super + end + else + # The top-level name is always puppet[top], so we don't + # bother with that. And we don't add the hostname + # here, it gets added in the log server thingy. + if self.name == "puppet[top]" + @path = ["/"] + else + # We assume that if we don't have a parent that we + # should not cache the path + @path = [self.class.name.to_s + "=" + self.name] end end - - return @path.join("/") 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 - if recurse.is_a?(String) - if recurse =~ /^[0-9]+$/ - recurse = Integer(recurse) - #elsif recurse =~ /^inf/ # infinite recursion - else # anything else is infinite recursion - recurse = true - end + return @path.join("/") + 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 + if recurse.is_a?(String) + if recurse =~ /^[0-9]+$/ + recurse = Integer(recurse) + #elsif recurse =~ /^inf/ # infinite recursion + else # anything else is infinite recursion + recurse = true end + end - # are we at the end of the recursion? - if recurse == 0 - self.info "finished recursing" - return - end + # are we at the end of the recursion? + if recurse == 0 + self.info "finished recursing" + return + end - if recurse.is_a?(Integer) - recurse -= 1 - end + if recurse.is_a?(Integer) + recurse -= 1 + end - self.localrecurse(recurse) - if @states.include?(:source) - self.sourcerecurse(recurse) - end + self.localrecurse(recurse) + if @states.include?(:source) + self.sourcerecurse(recurse) end + end - def localrecurse(recurse) - unless FileTest.exist?(self.name) and self.stat.directory? - #self.info "%s is not a directory; not recursing" % - # self.name - return - end + def localrecurse(recurse) + unless FileTest.exist?(self.name) and self.stat.directory? + #self.info "%s is not a directory; not recursing" % + # self.name + return + end - unless FileTest.directory? self.name - raise Puppet::Error.new( - "Uh, somehow trying to manage non-dir %s" % self.name - ) - end - unless FileTest.readable? self.name - self.notice "Cannot manage %s: permission denied" % self.name - return - end + unless FileTest.directory? self.name + raise Puppet::Error.new( + "Uh, somehow trying to manage non-dir %s" % self.name + ) + end + unless FileTest.readable? self.name + self.notice "Cannot manage %s: permission denied" % self.name + return + end - children = Dir.entries(self.name) - - #Get rid of ignored children - if @parameters.include?(:ignore) - children = handleignore(children) - end - - added = [] - children.each { |file| - file = File.basename(file) - next if file =~ /^\.\.?$/ # skip . and .. - if child = self.newchild(file, :recurse => recurse) - unless @children.include?(child) - self.push child - added.push file + children = Dir.entries(self.name) + + #Get rid of ignored children + if @parameters.include?(:ignore) + children = handleignore(children) + end + + added = [] + children.each { |file| + file = File.basename(file) + next if file =~ /^\.\.?$/ # skip . and .. + if child = self.newchild(file, :recurse => recurse) + unless @children.include?(child) + self.push child + added.push file - end - child.retrieve end - } + child.retrieve + end + } + end + + # This recurses against the remote source and makes sure the local + # and remote structures match. It's run after 'localrecurse'. + def sourcerecurse(recurse) + # FIXME sourcerecurse should support purging non-remote files + source = @states[:source].source + + sourceobj, path = uri2obj(source) + + # we'll set this manually as necessary + if @arghash.include?(:create) + @arghash.delete(:create) end - # This recurses against the remote source and makes sure the local - # and remote structures match. It's run after 'localrecurse'. - def sourcerecurse(recurse) - # FIXME sourcerecurse should support purging non-remote files - source = @states[:source].source - - sourceobj, path = uri2obj(source) - - # we'll set this manually as necessary - if @arghash.include?(:create) - @arghash.delete(:create) - end + # okay, we've got our source object; now we need to + # build up a local file structure to match the remote + # one - # okay, we've got our source object; now we need to - # build up a local file structure to match the remote - # one + server = sourceobj.server + sum = "md5" + if state = self.state(:checksum) + sum = state.checktype + end + r = false + if recurse + unless recurse == 0 + r = 1 + end + end - server = sourceobj.server - sum = "md5" - if state = self.state(:checksum) - sum = state.checktype + ignore = @parameters[:ignore] + + #self.warning "Listing path %s" % path.inspect + desc = server.list(path, r, ignore) + + desc.split("\n").each { |line| + file, type = line.split("\t") + next if file == "/" + name = file.sub(/^\//, '') + #self.warning "child name is %s" % name + args = {:source => source + file} + if type == file + args[:recurse] = nil end - r = false - if recurse - unless recurse == 0 - r = 1 - end + self.newchild(name, args) + #self.newchild(hash, source, recurse) + #hash2child(hash, source, recurse) + } + end + + # a wrapper method to make sure the file exists before doing anything + def retrieve + if @states.include?(:source) + # This probably isn't the best place for it, but we need + # to make sure that we have a corresponding checksum state. + unless @states.include?(:checksum) + self[:checksum] = "md5" end + @states[:source].retrieve + end - ignore = @parameters[:ignore] - - #self.warning "Listing path %s" % path.inspect - desc = server.list(path, r, ignore) - - desc.split("\n").each { |line| - file, type = line.split("\t") - next if file == "/" - name = file.sub(/^\//, '') - #self.warning "child name is %s" % name - args = {:source => source + file} - if type == file - args[:recurse] = nil - end - self.newchild(name, args) - #self.newchild(hash, source, recurse) - #hash2child(hash, source, recurse) + if @parameters.include?(:recurse) + self.recurse + end + + unless stat = self.stat(true) + self.debug "File does not exist" + @states.each { |name,state| + # We've already retreived the source, and we don't + # want to overwrite whatever it did. This is a bit + # of a hack, but oh well, source is definitely special. + next if name == :source + state.is = :notfound } + return end - # a wrapper method to make sure the file exists before doing anything - def retrieve - if @states.include?(:source) - # This probably isn't the best place for it, but we need - # to make sure that we have a corresponding checksum state. - unless @states.include?(:checksum) - self[:checksum] = "md5" - end - @states[:source].retrieve - end + super + end - if @parameters.include?(:recurse) - self.recurse - end - - unless stat = self.stat(true) - self.debug "File does not exist" - @states.each { |name,state| - # We've already retreived the source, and we don't - # want to overwrite whatever it did. This is a bit - # of a hack, but oh well, source is definitely special. - next if name == :source - state.is = :notfound - } - return + def stat(refresh = false) + if @stat.nil? or refresh == true + begin + @stat = File.lstat(self.name) + rescue Errno::ENOENT => error + @stat = nil + rescue => error + self.debug "Failed to stat %s: %s" % + [self.name,error] + @stat = nil end - - super end - def stat(refresh = false) - if @stat.nil? or refresh == true - begin - @stat = File.lstat(self.name) - rescue Errno::ENOENT => error - @stat = nil - rescue => error - self.debug "Failed to stat %s: %s" % - [self.name,error] - @stat = nil - end - end + return @stat + end - return @stat + def uri2obj(source) + sourceobj = FileSource.new + path = nil + if source =~ /^\// + source = "file://localhost/%s" % source + sourceobj.mount = "localhost" + sourceobj.local = true + end + begin + uri = URI.parse(source) + rescue => detail + raise Puppet::Error, "Could not understand source %s: %s" % + [source, detail.to_s] end - def uri2obj(source) - sourceobj = FileSource.new - path = nil - if source =~ /^\// - source = "file://localhost/%s" % source - sourceobj.mount = "localhost" - sourceobj.local = true + case uri.scheme + when "file": + unless defined? @@localfileserver + @@localfileserver = Puppet::Server::FileServer.new( + :Local => true, + :Mount => { "/" => "localhost" }, + :Config => false + ) + #@@localfileserver.mount("/", "localhost") end - begin - uri = URI.parse(source) - rescue => detail - raise Puppet::Error, "Could not understand source %s: %s" % - [source, detail.to_s] + sourceobj.server = @@localfileserver + path = "/localhost" + uri.path + when "puppet": + args = { :Server => uri.host } + if uri.port + args[:Port] = uri.port end + # FIXME We should cache a copy of this server + #sourceobj.server = Puppet::NetworkClient.new(args) + unless @clients.include?(source) + @clients[source] = Puppet::Client::FileClient.new(args) + end + sourceobj.server = @clients[source] - case uri.scheme - when "file": - unless defined? @@localfileserver - @@localfileserver = Puppet::Server::FileServer.new( - :Local => true, - :Mount => { "/" => "localhost" }, - :Config => false - ) - #@@localfileserver.mount("/", "localhost") - end - sourceobj.server = @@localfileserver - path = "/localhost" + uri.path - when "puppet": - args = { :Server => uri.host } - if uri.port - args[:Port] = uri.port - end - # FIXME We should cache a copy of this server - #sourceobj.server = Puppet::NetworkClient.new(args) - unless @clients.include?(source) - @clients[source] = Puppet::Client::FileClient.new(args) - end - sourceobj.server = @clients[source] - - tmp = uri.path - if tmp =~ %r{^/(\w+)} - sourceobj.mount = $1 - path = tmp - #path = tmp.sub(%r{^/\w+},'') || "/" - else - raise Puppet::Error, "Invalid source path %s" % tmp - end + tmp = uri.path + if tmp =~ %r{^/(\w+)} + sourceobj.mount = $1 + path = tmp + #path = tmp.sub(%r{^/\w+},'') || "/" else - raise Puppet::Error, - "Got other recursive file proto %s from %s" % - [uri.scheme, source] + raise Puppet::Error, "Invalid source path %s" % tmp end - - return [sourceobj, path.sub(/\/\//, '/')] + else + raise Puppet::Error, + "Got other recursive file proto %s from %s" % + [uri.scheme, source] end - end # Puppet::Type::PFile - end # Puppet::Type + + return [sourceobj, path.sub(/\/\//, '/')] + end + end # Puppet.type(:pfile) # the filesource class can't include the path, because the path # changes for every file instance @@ -566,4 +525,14 @@ module Puppet end end +# We put all of the states in separate files, because there are so many +# of them. The order these are loaded is important, because it determines +# the order they are in the state list. +require 'puppet/type/pfile/create' +require 'puppet/type/pfile/checksum' +require 'puppet/type/pfile/source' +require 'puppet/type/pfile/uid' +require 'puppet/type/pfile/group' +require 'puppet/type/pfile/mode' +require 'puppet/type/pfile/type' # $Id$ diff --git a/lib/puppet/type/pfile/checksum.rb b/lib/puppet/type/pfile/checksum.rb index 33687c517..179c1f613 100755 --- a/lib/puppet/type/pfile/checksum.rb +++ b/lib/puppet/type/pfile/checksum.rb @@ -3,215 +3,212 @@ # 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 + Puppet.type(:file).newstate(:checksum) do + desc "How to check whether a file has changed. **md5**/*lite-md5*/ + *time*/*mtime*" + @event = :file_modified - @unmanaged = true + @unmanaged = true - @validtypes = %w{md5 md5lite timestamp mtime time} + @validtypes = %w{md5 md5lite timestamp mtime time} - def self.validtype?(type) - @validtypes.include?(type) - end + def self.validtype?(type) + @validtypes.include?(type) + end - def checktype - @checktypes[0] - end + def checktype + @checktypes[0] + end - def getsum(checktype) - sum = "" - case checktype - when "md5", "md5lite": - unless FileTest.file?(@parent[:path]) - #@parent.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? - self.info "Not checksumming empty file %s" % - @parent.name - sum = 0 - else - sum = Digest::MD5.hexdigest(text) - end - } - rescue Errno::EACCES => detail - self.notice "Cannot checksum %s: permission denied" % - @parent.name - @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 = File.stat(@parent[:path]).mtime.to_s - when "time": - sum = File.stat(@parent[:path]).ctime.to_s + def getsum(checktype) + sum = "" + case checktype + when "md5", "md5lite": + unless FileTest.file?(@parent[:path]) + #@parent.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 - raise Puppet::Error, "Invalid sum type %s" % checktype + begin + File.open(@parent[:path]) { |file| + text = nil + if checktype == "md5" + text = file.read + else + text = file.read(512) + end + if text.nil? + self.info "Not checksumming empty file %s" % + @parent.name + sum = 0 + else + sum = Digest::MD5.hexdigest(text) + end + } + rescue Errno::EACCES => detail + self.notice "Cannot checksum %s: permission denied" % + @parent.name + @parent.delete(self.class.name) + rescue => detail + self.notice "Cannot checksum %s: %s" % + detail + @parent.delete(self.class.name) + end end - - return sum + 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 - # Convert from the sum type to the stored checksum. - def shouldprocess(value) - unless defined? @checktypes - @checktypes = [] - end - unless self.class.validtype?(value) - raise Puppet::Error, "Invalid checksum type '%s'" % value - end + return sum + end - @checktypes << value - state = Puppet::Storage.state(self) - if hash = state[@parent[:path]] - if hash.include?(value) - return hash[value] - #@parent.debug "Found checksum %s for %s" % - # [self.should,@parent[:path]] - else - #@parent.debug "Found checksum for %s but not of type %s" % - # [@parent[:path],@checktype] - return :nosum - end + # Convert from the sum type to the stored checksum. + munge do |value| + unless defined? @checktypes + @checktypes = [] + end + unless self.class.validtype?(value) + raise Puppet::Error, "Invalid checksum type '%s'" % value + end + + @checktypes << value + state = Puppet::Storage.state(self) + if hash = state[@parent[:path]] + if hash.include?(value) + return hash[value] + #@parent.debug "Found checksum %s for %s" % + # [self.should,@parent[:path]] else - # We can't use :notfound here, because then it'll match on - # non-existent files + #@parent.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 - unless defined? @checktypes - @checktypes = ["md5"] - 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 + unless defined? @checktypes + @checktypes = ["md5"] + end - unless FileTest.exists?(@parent.name) - self.is = :notfound - return - end + unless FileTest.exists?(@parent.name) + self.is = :notfound + return + end - # Just use the first allowed check type - @is = getsum(@checktypes[0]) - - # 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 + # Just use the first allowed check type + @is = getsum(@checktypes[0]) + + # 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 - #@parent.debug "checksum state is %s" % self.is + #@parent.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 - # 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 + 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 == :notfound - 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 == :notfound - # 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.name - ) - end - return nil + # 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.name + ) 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) - self.debug "Initializing state hash" + # If the sums are different, then return an event. + if self.updatesum + return :file_modified + else + return nil + end + end - state[@parent.name] = Hash.new - end + # Store the new sum to the state db. + def updatesum + result = false + state = Puppet::Storage.state(self) + unless state.include?(@parent.name) + self.debug "Initializing state hash" - 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 + state[@parent.name] = Hash.new + end - # if we're replacing, vs. updating - if state[@parent.name].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.name, state[@parent.name][@checktypes[0]],@is] - #@parent.debug "@is: %s; @should: %s" % [@is,@should] - result = true - else - @parent.debug "Creating checksum %s of type %s" % - [@is,@checktypes[0]] - result = false + 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?(@checktypes[0]) + unless defined? @should + raise Puppet::Error.new( + ("@should is not initialized for %s, even though we " + + "found a checksum") % @parent[:path] + ) end - state[@parent.name][@checktypes[0]] = @is - return result + self.debug "Replacing %s checksum %s with %s" % + [@parent.name, state[@parent.name][@checktypes[0]],@is] + #@parent.debug "@is: %s; @should: %s" % [@is,@should] + result = true + else + @parent.debug "Creating checksum %s of type %s" % + [@is,@checktypes[0]] + result = false end + state[@parent.name][@checktypes[0]] = @is + return result end end end diff --git a/lib/puppet/type/pfile/create.rb b/lib/puppet/type/pfile/create.rb index d4c593018..449978de7 100755 --- a/lib/puppet/type/pfile/create.rb +++ b/lib/puppet/type/pfile/create.rb @@ -1,106 +1,104 @@ 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 + Puppet.type(:file).newstate(:create) do + require 'etc' + desc "Whether to create files that don't currently exist. + **false**/*true*/*file*/*directory*" - 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 + @event = :file_created - def retrieve - if stat = @parent.stat(true) - @is = stat.ftype - else - @is = :notfound - end + munge do |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 - #self.debug "'exists' state is %s" % self.is + def retrieve + if stat = @parent.stat(true) + @is = stat.ftype + else + @is = :notfound end + #self.debug "'exists' state is %s" % self.is + end - def sync - event = nil - mode = @parent.should(:mode) - # First, determine if a user has been specified and if so if - # that user has write access to the parent dir - asuser = nil - if @parent.should(:owner) and ! @parent.should(:owner).is_a?(Symbol) - writeable = Puppet::Util.asuser(@parent.should(:owner)) { - FileTest.writable?(File.dirname(@parent[:path])) - } + def sync + event = nil + mode = @parent.should(:mode) - # If the parent directory is writeable, then we execute - # as the user in question. Otherwise we'll rely on - # the 'owner' state to do things. - if writeable - asuser = @parent.should(:owner) - end - end - begin - case self.should - when "file": - # just create an empty file - Puppet::Util.asuser(asuser, @parent.should(:group)) { - if mode - File.open(@parent[:path],"w", mode) { - } - else - File.open(@parent[:path],"w") { - } - end - } - event = :file_created - when "directory": - Puppet::Util.asuser(asuser) { - if mode - Dir.mkdir(@parent.name,mode) - else - Dir.mkdir(@parent.name) - end - } - event = :directory_created - when :notfound: - # this is where the file should be deleted... + # First, determine if a user has been specified and if so if + # that user has write access to the parent dir + asuser = nil + if @parent.should(:owner) and ! @parent.should(:owner).is_a?(Symbol) + writeable = Puppet::Util.asuser(@parent.should(:owner)) { + FileTest.writable?(File.dirname(@parent[:path])) + } - # This value is only valid when we're rolling back a creation, - # so we verify that the file has not been modified since then. - unless FileTest.size(@parent.name) == 0 - raise Puppet::Error.new( - "Created file %s has since been modified; cannot roll back." - ) + # If the parent directory is writeable, then we execute + # as the user in question. Otherwise we'll rely on + # the 'owner' state to do things. + if writeable + asuser = @parent.should(:owner) + end + end + begin + case self.should + when "file": + # just create an empty file + Puppet::Util.asuser(asuser, @parent.should(:group)) { + if mode + File.open(@parent[:path],"w", mode) { + } + else + File.open(@parent[:path],"w") { + } + end + } + event = :file_created + when "directory": + Puppet::Util.asuser(asuser) { + if mode + Dir.mkdir(@parent.name,mode) + else + Dir.mkdir(@parent.name) end + } + event = :directory_created + when :notfound: + # this is where the file should be deleted... - File.unlink(@parent.name) - else - error = Puppet::Error.new( - "Somehow got told to create a %s file" % self.should) - raise error + # This value is only valid when we're rolling back a creation, + # so we verify that the file has not been modified since then. + unless FileTest.size(@parent.name) == 0 + raise Puppet::Error.new( + "Created file %s has since been modified; cannot roll back." + ) end - rescue => detail - raise Puppet::Error.new("Could not create %s: %s" % - [self.should, detail] - ) + + File.unlink(@parent.name) + else + error = Puppet::Error.new( + "Somehow got told to create a %s file" % self.should) + raise error end - return event + rescue => detail + raise Puppet::Error.new("Could not create %s: %s" % + [self.should, detail] + ) end + return event end end end diff --git a/lib/puppet/type/pfile/group.rb b/lib/puppet/type/pfile/group.rb index 63098b892..5bb297fa2 100755 --- a/lib/puppet/type/pfile/group.rb +++ b/lib/puppet/type/pfile/group.rb @@ -1,127 +1,124 @@ # 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 id2name(id) - begin - group = Etc.getgrgid(id) - rescue ArgumentError - return nil - end - if group.gid == "" - return nil - else - return group.name - end + Puppet.type(:file).newstate(:group) do + require 'etc' + desc "Which group should own the file. Argument can be either group + name or group ID." + @event = :inode_changed + + def id2name(id) + begin + group = Etc.getgrgid(id) + rescue ArgumentError + return nil end - - # We want to print names, not numbers - def is_to_s - id2name(@is) || @is + if group.gid == "" + return nil + else + return group.name end + end - def should_to_s - id2name(self.should) || self.should - end + # We want to print names, not numbers + def is_to_s + id2name(@is) || @is + end - def retrieve - stat = @parent.stat(true) + def should_to_s + id2name(self.should) || self.should + end - self.is = stat.gid - end + def retrieve + stat = @parent.stat(true) - def shouldprocess(value) - method = nil - gid = nil - gname = nil + self.is = stat.gid + end - if value.is_a?(Integer) - method = :getgrgid - else - method = :getgrnam - end + munge do |value| + method = nil + gid = nil + gname = nil - 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 + if value.is_a?(Integer) + method = :getgrgid + else + method = :getgrnam + end - #unless Process.uid == 0 - # groups = %x{groups}.chomp.split(/\s/) - # unless groups.include?(gname) - # self.notice "Cannot chgrp: not in group %s" % gname - # raise Puppet::Error.new( - # "Cannot chgrp: not in group %s" % gname) - # 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 - if gid.nil? - raise Puppet::Error.new( - "Nil gid for %s" % @parent.name) - else - return gid - 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 - # 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 + # groups = %x{groups}.chomp.split(/\s/) + # unless groups.include?(gname) + # self.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 + if @is == :notfound + @parent.stat(true) + self.retrieve + if @is == :notfound - @parent.stat(true) - self.retrieve - - if @is == :notfound - self.err "File '%s' does not exist; cannot chgrp" % - @parent[:path] - return nil - end - - if self.insync? - return nil - end + self.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 + if self.insync? + return nil end - return :inode_changed 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 diff --git a/lib/puppet/type/pfile/mode.rb b/lib/puppet/type/pfile/mode.rb index 7ef04441d..297e5cb1c 100755 --- a/lib/puppet/type/pfile/mode.rb +++ b/lib/puppet/type/pfile/mode.rb @@ -2,128 +2,125 @@ # 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 + Puppet.type(:file).newstate(:mode) do + require 'etc' + desc "Mode the file should be. Currently relatively limited: + you must specify the exact mode the file should be." + @event = :inode_changed - # Our modes are octal, so make sure they print correctly. Other - # valid values are symbols, basically - def is_to_s - case @is - when Integer - return "%o" % @is - when Symbol - return @is - else - raise Puppet::DevError, "Invalid 'is' value for mode: %s" % - @is.inspect - end + # Our modes are octal, so make sure they print correctly. Other + # valid values are symbols, basically + def is_to_s + case @is + when Integer + return "%o" % @is + when Symbol + return @is + else + raise Puppet::DevError, "Invalid 'is' value for mode: %s" % + @is.inspect end + end - def should_to_s - case self.should - when Integer - return "%o" % self.should - when Symbol - return self.should - else - raise Puppet::DevError, "Invalid 'should' value for mode: %s" % - self.should.inspect - end + def should_to_s + case self.should + when Integer + return "%o" % self.should + when Symbol + return self.should + else + raise Puppet::DevError, "Invalid 'should' value for mode: %s" % + self.should.inspect end + 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-9]+$/ - raise Puppet::Error, "File modes can only be numbers" - end - unless value =~ /^0/ - value = "0" + value - end - begin - value = Integer(value) - rescue ArgumentError => detail - raise Puppet::DevError, "Could not convert %s to integer" % - value.inspect - end + munge do |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-9]+$/ + raise Puppet::Error, "File modes can only be numbers" end - - 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 + unless value =~ /^0/ + value = "0" + value + end + begin + value = Integer(value) + rescue ArgumentError => detail + raise Puppet::DevError, "Could not convert %s to integer" % + value.inspect 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 + return value + end - #self.debug "chmod state is %o" % self.is + # 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 - def sync - if @is == :notfound - @parent.stat(true) - self.retrieve - #self.debug "%s: after refresh, is '%s'" % [self.class.name,@is] - if @is == :notfound - self.info "File does not exist; cannot set mode" % - @parent.name - return nil - end + return value + end - if self.insync? - # we're already in sync - return nil + 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 - mode = self.should + #self.debug "chmod state is %o" % self.is + end - if mode == :notfound - # This is really only valid for create states... + def sync + if @is == :notfound + @parent.stat(true) + self.retrieve + #self.debug "%s: after refresh, is '%s'" % [self.class.name,@is] + if @is == :notfound + self.info "File does not exist; cannot set mode" % + @parent.name 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 + if self.insync? + # we're already in sync + return nil end - return :inode_changed 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 diff --git a/lib/puppet/type/pfile/source.rb b/lib/puppet/type/pfile/source.rb index a035878b1..b8f6e3045 100755 --- a/lib/puppet/type/pfile/source.rb +++ b/lib/puppet/type/pfile/source.rb @@ -1,262 +1,262 @@ 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 + Puppet.type(:file).newstate(:source) do + PINPARAMS = [:mode, :type, :owner, :group, :checksum] + + attr_accessor :source, :local + desc "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*." + + # 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 + self.err "Could not describe %s: %s" % + [path, detail] + return nil + end - begin - desc = server.describe(path) - rescue NetworkClientError => detail - self.err "Could not describe %s: %s" % - [path, detail] - return nil + args = {} + PINPARAMS.zip( + desc.split("\t") + ).each { |param, value| + if value =~ /^[0-9]+$/ + value = value.to_i end - - args = {} - Puppet::Type::PFile::PINPARAMS.zip( - desc.split("\t") - ).each { |param, value| - if value =~ /^[0-9]+$/ - value = value.to_i - end - unless value.nil? - args[param] = value - end - } - - # we can't manage ownership as root, so don't even try - unless Process.uid == 0 - args.delete(:owner) + unless value.nil? + args[param] = value end + } - if args.empty? - return nil - else - return args - end + # we can't manage ownership as root, so don't even try + unless Process.uid == 0 + args.delete(:owner) 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 + if args.empty? + return nil + else + return args + end + end - unless defined? @shouldorig - raise Puppet::DevError, "No sources defined for %s" % - @parent.name - 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 - @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 - } + unless defined? @shouldorig + raise Puppet::DevError, "No sources defined for %s" % + @parent.name + end - if @stats.nil? or @stats[:type].nil? - @is = :notdescribed - @source = nil - return nil + @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 + } - # 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 @stats.nil? or @stats[:type].nil? + @is = :notdescribed + @source = nil + return nil + 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 + # 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 - self.info "File does not have checksum" @is = :notfound end + else + self.info "File does not have checksum" + @is = :notfound + end - @should = [@stats[:checksum]] + @should = [@stats[:checksum]] - if state = @parent.state(:create) - unless state.should == "file" - self.notice( - "File %s had both create and source enabled" % - @parent.name - ) - @parent.delete(:create) - end + if state = @parent.state(:create) + unless state.should == "file" + self.notice( + "File %s had both create and source enabled" % + @parent.name + ) + @parent.delete(:create) 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 + # 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 - # we'll let the :create state do our work - @should.clear - @is = true - # FIXME We should at least support symlinks, I would think... else - self.err "Cannot use files of type %s as sources" % - @stats[:type] - @should = nil - @is = true + @parent[:create] = "directory" + @parent.state(:create).retrieve end + # we'll let the :create state do our work + @should.clear + @is = true + # FIXME We should at least support symlinks, I would think... + else + self.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 + # 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'. + munge do |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 + 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 - if @stats[:type] == "directory" - @is = true - return nil - else - return source - end + return source end end + end - def sync + def sync + if @is == :notdescribed + self.retrieve # try again 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.log "Could not retreive information on %s" % @parent.name + return nil + end + if @is == @should + return nil end + end - unless defined? @source - raise Puppet::DevError, "Somehow source is still undefined" + 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) + unless defined? @source + raise Puppet::DevError, "Somehow source is still undefined" + end - begin - contents = sourceobj.server.retrieve(path) - rescue NetworkClientError => detail - self.err "Could not retrieve %s: %s" % - [path, detail] - return nil - end + sourceobj, path = @parent.uri2obj(@source) - # FIXME It's stupid that this isn't taken care of in the - # protocol. - unless sourceobj.server.local - contents = CGI.unescape(contents) - end + begin + contents = sourceobj.server.retrieve(path) + rescue NetworkClientError => detail + self.err "Could not retrieve %s: %s" % + [path, detail] + return nil + end - if contents == "" - self.notice "Could not retrieve contents for %s" % - @source - 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 FileTest.exists?(@parent.name) - # this makes sure we have a copy for posterity - @backed = @parent.handlebackup - end + if contents == "" + self.notice "Could not retrieve contents for %s" % + @source + end - # create the file in a tmp location - args = [@parent.name + ".puppettmp", - File::CREAT | File::WRONLY | File::TRUNC] + if FileTest.exists?(@parent.name) + # this makes sure we have a copy for posterity + @backed = @parent.handlebackup + end - # 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 + # create the file in a tmp location + args = [@parent.name + ".puppettmp", + File::CREAT | File::WRONLY | File::TRUNC] - # FIXME we should also change our effective user and group id + # 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 - 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 + # FIXME we should also change our effective user and group id - if FileTest.exists?(@parent.name) - begin - File.unlink(@parent.name) - rescue => detail - self.err "Could not remove %s for replacing: %s" % - [@parent.name, detail] - end - end + 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.rename(@parent.name + ".puppettmp", @parent.name) + File.unlink(@parent.name) rescue => detail - self.err "Could not rename tmp %s for replacing: %s" % + self.err "Could not remove %s for replacing: %s" % [@parent.name, detail] end + end - return :file_changed + begin + File.rename(@parent.name + ".puppettmp", @parent.name) + rescue => detail + self.err "Could not rename tmp %s for replacing: %s" % + [@parent.name, detail] end + + return :file_changed end end end diff --git a/lib/puppet/type/pfile/type.rb b/lib/puppet/type/pfile/type.rb index 1d03634e7..7d761c40b 100755 --- a/lib/puppet/type/pfile/type.rb +++ b/lib/puppet/type/pfile/type.rb @@ -1,29 +1,26 @@ module Puppet - class State - class PFileType < Puppet::State - require 'etc' - @doc = "A read-only state to check the file type." - @name = :type + Puppet.type(:file).newstate(:type) do + require 'etc' + desc "A read-only state to check the file type." - def shouldprocess(value) - raise Puppet::Error, ":type is read-only" + #munge do |value| + # raise Puppet::Error, ":type is read-only" + #end + + def retrieve + if stat = @parent.stat(true) + @is = stat.ftype + else + @is = :notfound 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 + # so this state is never marked out of sync + @should = [@is] + end - def sync - raise Puppet::Error, ":type is read-only" - end + def sync + raise Puppet::Error, ":type is read-only" end end end diff --git a/lib/puppet/type/pfile/uid.rb b/lib/puppet/type/pfile/uid.rb index 8a2f7ec79..3f1add774 100755 --- a/lib/puppet/type/pfile/uid.rb +++ b/lib/puppet/type/pfile/uid.rb @@ -1,164 +1,161 @@ 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 id2name(id) - if id.is_a?(Symbol) - return id.to_s - end - begin - user = Etc.getpwuid(id) - rescue TypeError - return nil - rescue ArgumentError - return nil - end + Puppet.type(:file).newstate(:owner) do + require 'etc' + desc "To whom the file should belong. Argument can be user name or + user ID." + @event = :inode_changed + + def id2name(id) + if id.is_a?(Symbol) + return id.to_s + end + begin + user = Etc.getpwuid(id) + rescue TypeError + return nil + rescue ArgumentError + return nil + end + if user.uid == "" + return nil + else + return user.name + end + end + + def name2id(value) + if value.is_a?(Symbol) + return value.to_s + end + begin + user = Etc.getpwnam(value) if user.uid == "" return nil - else - return user.name end + return user.uid + rescue ArgumentError => detail + return nil end + end - def name2id(value) - if value.is_a?(Symbol) - return value.to_s - end - begin - user = Etc.getpwnam(value) - if user.uid == "" - return nil - end - return user.uid - rescue ArgumentError => detail - return nil - end + # Determine if the user is valid, and if so, return the UID + def validuser?(value) + if value =~ /^\d+$/ + value = value.to_i end - # Determine if the user is valid, and if so, return the UID - def validuser?(value) - if value =~ /^\d+$/ - value = value.to_i + if value.is_a?(Integer) + # verify the user is a valid user + if tmp = id2name(value) + return value + else + return false end - - if value.is_a?(Integer) - # verify the user is a valid user - if tmp = id2name(value) - return value - else - return false - end + else + if tmp = name2id(value) + return tmp else - if tmp = name2id(value) - return tmp - else - return false - end + return false end end + end + + # We want to print names, not numbers + def is_to_s + id2name(@is) || @is + end - # We want to print names, not numbers - def is_to_s - id2name(@is) || @is + def should_to_s + case self.should + when Symbol + self.should.to_s + when Integer + id2name(self.should) || self.should + when String + self.should + else + raise Puppet::DevError, "Invalid uid type %s(%s)" % + [self.should.class, self.should] end + end - def should_to_s - case self.should - when Symbol - self.should.to_s - when Integer - id2name(self.should) || self.should - when String - self.should - else - raise Puppet::DevError, "Invalid uid type %s(%s)" % - [self.should.class, self.should] - end + def retrieve + unless stat = @parent.stat(true) + @is = :notfound + return end - def retrieve - unless stat = @parent.stat(true) - @is = :notfound - return - end + self.is = stat.uid - self.is = stat.uid + # On OS X, files that are owned by -2 get returned as really + # large UIDs instead of negative ones. This isn't a Ruby bug, + # it's an OS X bug, since it shows up in perl, too. + if @is > 120000 + self.warning "current state is silly: %s" % @is + @is = :notfound + end + end - # On OS X, files that are owned by -2 get returned as really - # large UIDs instead of negative ones. This isn't a Ruby bug, - # it's an OS X bug, since it shows up in perl, too. - if @is > 120000 - self.warning "current state is silly: %s" % @is - @is = :notfound - end + # If we're not root, we can check the values but we cannot change + # them. We can't really do any processing here, because users + # might not exist yet. FIXME There's still a bit of a problem here + # if the user's UID changes at run time, but we're just going to + # have to be okay with that for now, unfortunately. + munge do |value| + if tmp = self.validuser?(value) + return tmp + else + return value end + end - # If we're not root, we can check the values but we cannot change - # them. We can't really do any processing here, because users - # might not exist yet. FIXME There's still a bit of a problem here - # if the user's UID changes at run time, but we're just going to - # have to be okay with that for now, unfortunately. - def shouldprocess(value) - if tmp = self.validuser?(value) - return tmp - else - return value + def sync + unless Process.uid == 0 + unless defined? @@notifieduid + self.notice "Cannot manage ownership unless running as root" + #@parent.delete(self.name) + @@notifieduid = true end + return nil end - def sync - unless Process.uid == 0 - unless defined? @@notifieduid - self.notice "Cannot manage ownership unless running as root" - #@parent.delete(self.name) - @@notifieduid = true - end - return nil + user = nil + unless user = self.validuser?(self.should) + tmp = self.should + unless defined? @@usermissing + @@usermissing = {} end - user = nil - unless user = self.validuser?(self.should) - tmp = self.should - unless defined? @@usermissing - @@usermissing = {} - end - - if @@usermissing.include?(tmp) - @@usermissing[tmp] += 1 - else - self.notice "user %s does not exist" % tmp - @@usermissing[tmp] = 1 - return nil - end + if @@usermissing.include?(tmp) + @@usermissing[tmp] += 1 + else + self.notice "user %s does not exist" % tmp + @@usermissing[tmp] = 1 + return nil end + end + if @is == :notfound + @parent.stat(true) + self.retrieve if @is == :notfound - @parent.stat(true) - self.retrieve - if @is == :notfound - self.info "File does not exist; cannot set owner" - return nil - end - if self.insync? - return nil - end - #self.debug "%s: after refresh, is '%s'" % [self.class.name,@is] + self.info "File does not exist; cannot set owner" + return nil end - - begin - File.chown(user, nil, @parent[:path]) - rescue => detail - raise Puppet::Error, "Failed to set owner to '%s': %s" % - [user, detail] + if self.insync? + return nil end + #self.debug "%s: after refresh, is '%s'" % [self.class.name,@is] + end - return :inode_changed + begin + File.chown(user, nil, @parent[:path]) + rescue => detail + raise Puppet::Error, "Failed to set owner to '%s': %s" % + [user, detail] end + + return :inode_changed end end end diff --git a/lib/puppet/type/pfilebucket.rb b/lib/puppet/type/pfilebucket.rb index 7d38a612a..d17f681af 100755 --- a/lib/puppet/type/pfilebucket.rb +++ b/lib/puppet/type/pfilebucket.rb @@ -1,80 +1,76 @@ -# An interface for managing filebuckets from puppet - -# $Id$ - require 'puppet/server/filebucket' module Puppet - class Type - class PFileBucket < Type - attr_reader :bucket + newtype(:filebucket) do + attr_reader :bucket - @states = [] + @doc = "A repository for backing up files. If no filebucket is + defined, then files will be backed up in their current directory, + but the filebucket can be either a host- or site-global repository + for backing up. It stores files and returns the MD5 sum, which + can later be used to retrieve the file if restoration becomes + necessary. A filebucket does not do any work itself; instead, + it can be specified as the value of *backup* in a **file** object." - @parameters = [ - :name, - :server, - :path, - :port - ] + @states = [] - @doc = "A repository for backing up files. If no filebucket is - defined, then files will be backed up in their current directory, - but the filebucket can be either a host- or site-global repository - for backing up. It stores files and returns the MD5 sum, which - can later be used to retrieve the file if restoration becomes - necessary. A filebucket does not do any work itself; instead, - it can be specified as the value of *backup* in a **file** object." + newparam(:name) do + desc "The name of the filebucket." + isnamevar + end + + newparam(:server) do + desc "The server providing the filebucket. If this is + not specified, then the bucket is local and *path* must be + specified." + end - @paramdoc[:name] = "The name of the filebucket." - @paramdoc[:server] = "The server providing the filebucket. If this is - not specified, then the bucket is local and *path* must be specified." - @paramdoc[:port] = "The port on which the remote server is listening. + newparam(:port) do + desc "The port on which the remote server is listening. Defaults to the normal Puppet port, %s." % Puppet[:masterport] - @paramdoc[:path] = "The path to the local filebucket. If this is + end + + newparam(:path) do + desc "The path to the local filebucket. If this is not specified, then the bucket is remote and *server* must be specified." + end - @name = :filebucket - @namevar = :name - - # get the actual filebucket object - def self.bucket(name) - oname, object = @objects.find { |oname, o| oname == name } - return object.bucket - end + # get the actual filebucket object + def self.bucket(name) + oname, object = @objects.find { |oname, o| oname == name } + return object.bucket + end - def initialize(hash) - super + def initialize(hash) + super - if @parameters.include?(:server) - @parameters[:port] ||= FileBucket::DEFAULTPORT - begin - @bucket = Puppet::Client::Dipper.new( - :Server => @parameters[:server], - :Port => @parameters[:port] - ) - rescue => detail - raise Puppet::Error.new( - "Could not create remote filebucket: %s" % detail - ) - end - else - @parameters[:path] ||= Puppet[:bucketdir] - begin - @bucket = Puppet::Client::Dipper.new( - :Path => @parameters[:path] - ) - rescue => detail - raise Puppet::Error.new( - "Could not create local filebucket: %s" % detail - ) - end + if @parameters.include?(:server) + @parameters[:port] ||= FileBucket::DEFAULTPORT + begin + @bucket = Puppet::Client::Dipper.new( + :Server => @parameters[:server], + :Port => @parameters[:port] + ) + rescue => detail + raise Puppet::Error.new( + "Could not create remote filebucket: %s" % detail + ) + end + else + @parameters[:path] ||= Puppet[:bucketdir] + begin + @bucket = Puppet::Client::Dipper.new( + :Path => @parameters[:path] + ) + rescue => detail + raise Puppet::Error.new( + "Could not create local filebucket: %s" % detail + ) end end - - end - end + end + end end # $Id$ diff --git a/lib/puppet/type/pprocess.rb b/lib/puppet/type/pprocess.rb index c2038987f..dad39cd2c 100644 --- a/lib/puppet/type/pprocess.rb +++ b/lib/puppet/type/pprocess.rb @@ -89,7 +89,7 @@ module Puppet :pattern => :pattern }) - end # Puppet::Type::PProcess + end # Puppet.type(:pprocess) end # Puppet::Type end diff --git a/lib/puppet/type/service.rb b/lib/puppet/type/service.rb index 24b5a2492..d8c4c6bd2 100644 --- a/lib/puppet/type/service.rb +++ b/lib/puppet/type/service.rb @@ -5,58 +5,64 @@ # which is why they have a search path for initscripts and such 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*" - @name = :enabled - def retrieve - unless @parent.respond_to?(:enabled?) - raise Puppet::Error, "Service %s does not support enabling" - end - @is = @parent.enabled? - end - - def shouldprocess(should) - @runlevel = nil - case should - when true: return :enabled - when false: return :disabled - when /^\d+$/: - @runlevel = should - return :enabled - else - raise Puppet::Error, "Invalid 'enabled' value %s" % should - end - end - - def sync - case self.should - when :enabled - unless @parent.respond_to?(:enable) - raise Puppet::Error, "Service %s does not support enabling" - end - @parent.enable(@runlevel) - return :service_enabled - when :disabled - unless @parent.respond_to?(:disable) - raise Puppet::Error, - "Service %s does not support disabling" - end - @parent.disable - return :service_disabled - end - end - end + newtype(:service) do + @doc = "Manage running services. Rather than supporting managing + individual processes, puppet uses init scripts to simplify + specification of how to start, stop, or test processes. The + `path` parameter is provided to enable creation of multiple + init script directories, including supporting them for normal + users." + attr_reader :stat + +# newstate(:enabled) do +# desc "Whether a service should be enabled to start at boot. +# **true**/*false*/*runlevel*" +# +# def retrieve +# unless @parent.respond_to?(:enabled?) +# raise Puppet::Error, "Service %s does not support enabling" +# end +# @is = @parent.enabled? +# end +# +# munge do |should| +# @runlevel = nil +# case should +# when true: return :enabled +# when false: return :disabled +# when /^\d+$/: +# @runlevel = should +# return :enabled +# else +# raise Puppet::Error, "Invalid 'enabled' value %s" % should +# end +# end +# +# def sync +# case self.should +# when :enabled +# unless @parent.respond_to?(:enable) +# raise Puppet::Error, "Service %s does not support enabling" +# end +# @parent.enable(@runlevel) +# return :service_enabled +# when :disabled +# unless @parent.respond_to?(:disable) +# raise Puppet::Error, +# "Service %s does not support disabling" +# end +# @parent.disable +# return :service_disabled +# end +# 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 + newstate(:running) do + desc "Whether a service should be running. **true**/*false*" - def shouldprocess(should) + munge do |should| case should when false,0,"0", "stopped", :stopped: should = :stopped @@ -81,260 +87,317 @@ module Puppet case self.should when :running @parent.start - event = :service_started + return :service_started when :stopped @parent.stop - event = :service_stopped + return :service_stopped else self.debug "Not running '%s' and shouldn't be running" % self end end end - end - class Type - class Service < Type - attr_reader :stat - @states = [ - Puppet::State::ServiceRunning - ] - @parameters = [ - :binary, - :hasstatus, - :name, - :path, - :pattern, - :restart, - :start, - :status, - :stop, - :type - ] - - @paramdoc[:binary] = "The path to the daemon. This is only used for + newparam(:type) do + desc "The service type" + + defaultto { @parent.class.defaulttype } + + # Make sure we've got the actual module, not just a string + # representing the module. + munge do |type| + if type.is_a?(String) + type = @parent.class.svctype(type.intern) + end + Puppet.debug "Service type is %s" % type.name + @parent.extend(type) + + return type + end + end + newparam(:binary) do + desc "The path to the daemon. This is only used for systems that do not support init scripts." - @paramdoc[:hasstatus] = "Declare the the service's init script has a + end + newparam(:hasstatus) do + desc "Declare the the service's init script has a functional status command. This is assumed to be default for most systems, although there might be platforms on which this is assumed to be true." - @paramdoc[:name] = "The name of the service to run. This name + end + newparam(:name) do + desc "The name of the service to run. This name is used to find the init script in the search path." - @paramdoc[:path] = "The search path for finding init scripts. + isnamevar + end + newparam(:path) do + desc "The search path for finding init scripts. There is currently no default, but hopefully soon there will be a reasonable default for all platforms." - @paramdoc[:pattern] = "The pattern to search for in the process table. + + munge do |value| + paths = [] + if value.is_a?(Array) + paths += value.flatten.collect { |p| + p.split(":") + }.flatten + else + paths = value.split(":") + end + + paths.each do |path| + if FileTest.directory?(path) + next + end + unless FileTest.directory?(path) + @parent.info("Search path %s is not a directory" % [path]) + end + unless FileTest.exists?(path) + @parent.info("Search path %s does not exist" % [path]) + end + paths.delete(path) + end + + paths + end + end + newparam(:pattern) do + desc "The pattern to search for in the process table. This is used for stopping services on platforms that do not support init scripts, and is also used for determining service status on those service whose init scripts do not include a status command." - @paramdoc[:restart] = "Specify a *restart* command manually. If left + defaultto { @parent.name } + end + newparam(:restart) do + desc "Specify a *restart* command manually. If left unspecified, the restart method will be determined automatically." - @paramdoc[:start] = "Specify a *start* command manually. If left + end + newparam(:start) do + desc "Specify a *start* command manually. If left unspecified, the start method will be determined automatically." - @paramdoc[:status] = "Specify a *status* command manually. If left + end + newparam(:status) do + desc "Specify a *status* command manually. If left unspecified, the status method will be determined automatically." - @paramdoc[:stop] = "Specify a *stop* command manually. If left + end + + newparam(:stop) do + desc "Specify a *stop* command manually. If left unspecified, the stop method will be determined automatically." + end - @doc = "Manage running services. Rather than supporting managing - individual processes, puppet uses init scripts to simplify - specification of how to start, stop, or test processes. The - `path` parameter is provided to enable creation of multiple - init script directories, including supporting them for normal - users." - @name = :service - @namevar = :name - - # Return the service type we're using. Default to the Service - # class itself, but could be set to a module. - class << self - attr_accessor :svctype + # Create new subtypes of service management. + def self.newsvctype(name, parent = nil, &block) + if parent + parent = self.svctype(parent) end + svcname = name + mod = Module.new - # Retrieve the default type for the current platform. - def self.defaulttype - unless defined? @defsvctype - @defsvctype = nil - os = Facter["operatingsystem"].value - case os - when "Linux": - case Facter["distro"].value - when "Debian": - @defsvctype = Puppet::ServiceTypes::DebianSvc - end - when "SunOS": - release = Float(Facter["operatingsystemrelease"].value) - if release < 5.10 - @defsvctype = Puppet::ServiceTypes::InitSvc - else - @defsvctype = Puppet::ServiceTypes::SMFSvc - end - end - - unless @defsvctype - Puppet.notice "Defaulting to base service type" - @defsvctype = Puppet::ServiceTypes::BaseSvc - end - end - - return @defsvctype + # Add our parent, if it exists + if parent + mod.send(:include, parent) end - # Execute a command. Basically just makes sure it exits with a 0 - # code. - def execute(type, cmd) - output = %x(#{cmd} 2>&1) - unless $? == 0 - raise Puppet::Error, "Could not %s %s: %s" % - [type, self.name, output.chomp] + # And now define the support methods + code = %{ + def self.name + "#{svcname}" end - end - # Get the process ID for a running process. Requires the 'pattern' - # parameter. - def getpid - unless self[:pattern] - raise Puppet::Error, - "Either a stop command or a pattern must be specified" + def self.to_s + "SvcType(#{svcname})" end - ps = Facter["ps"].value - unless ps and ps != "" - raise Puppet::Error, - "You must upgrade Facter to a version that includes 'ps'" + + def svctype + "#{svcname}" end - regex = Regexp.new(self[:pattern]) - IO.popen(ps) { |table| - table.each { |line| - if regex.match(line) - ary = line.sub(/^\s+/, '').split(/\s+/) - return ary[1] - end - } - } + } - return nil - end + mod.module_eval(code) - # Initialize the service. This is basically responsible for merging - # in the right module. - def initialize(hash) - # We have to extend the object before we call 'super', so that - # the parameter methods are called correctly. - type = hash[:type] || - hash["type"] || - self.class.defaulttype + mod.module_eval(&block) - if type.is_a?(String) - type = type2module(type) + @modules ||= Hash.new do |hash, key| + if key.is_a?(String) + key = key.intern end - # Extend the object with the service type - #self.info "extending with %s" % type - self.extend(type) + if hash.include?(key) + hash[key] + else + nil + end + end + @modules[name] = mod + end - super + # Retrieve a service type. + def self.svctype(name) + @modules[name] + end - unless @parameters.include?(:pattern) - # default to using the service name as the pattern - self[:pattern] = self.name + # Retrieve the default type for the current platform. + def self.defaulttype + unless defined? @defsvctype + @defsvctype = nil + os = Facter["operatingsystem"].value + case os + when "Linux": + case Facter["distro"].value + when "Debian": + @defsvctype = self.svctype(:debian) + else + @defsvctype = self.svctype(:init) + end + when "SunOS": + release = Facter["operatingsystemrelease"].value + if release.sub(/5\./,'').to_f < 10 + @defsvctype = self.svctype(:init) + else + @defsvctype = self.svctype(:smf) + end end - # and then see if it needs to be checked - if self.respond_to?(:configchk) - self.configchk + unless @defsvctype + Puppet.notice "Defaulting to base service type" + @defsvctype = self.svctype(:base) end end - # Retrieve the service type. - def type2module(type) - case type - when "smf": - return Puppet::ServiceTypes::SMFSvc - when "init": - return Puppet::ServiceTypes::InitSvc - when "launchd": - #return Puppet::ServiceTypes::LaunchDSvc - else - raise Puppet::Error, "Invalid service type %s" % type - end + Puppet.debug "Default service type is %s" % @defsvctype.name + + return @defsvctype + end + + # Execute a command. Basically just makes sure it exits with a 0 + # code. + def execute(type, cmd) + self.info "Executing %s" % cmd.inspect + output = %x(#{cmd} 2>&1) + unless $? == 0 + raise Puppet::Error, "Could not %s %s: %s" % + [type, self.name, output.chomp] + end + end + + # Get the process ID for a running process. Requires the 'pattern' + # parameter. + def getpid + unless self[:pattern] + raise Puppet::Error, + "Either a stop command or a pattern must be specified" + end + ps = Facter["ps"].value + unless ps and ps != "" + raise Puppet::Error, + "You must upgrade Facter to a version that includes 'ps'" end + regex = Regexp.new(self[:pattern]) + IO.popen(ps) { |table| + table.each { |line| + if regex.match(line) + ary = line.sub(/^\s+/, '').split(/\s+/) + return ary[1] + end + } + } + + return nil + end - # Basically just a synonym for restarting. Used to respond - # to events. - def refresh - self.restart + # Initialize the service. This is basically responsible for merging + # in the right module. + def initialize(hash) + super + + # and then see if it needs to be checked + if self.respond_to?(:configchk) + self.configchk end + end - # How to restart the process. - def restart - if self[:restart] or self.respond_to?(:restartcmd) - cmd = self[:restart] || self.restartcmd - self.execute("restart", cmd) - else - self.stop - self.start - end + # Retrieve the service type. + def type2module(type) + self.class.svctype(type) + end + + # Basically just a synonym for restarting. Used to respond + # to events. + def refresh + self.restart + end + + # How to restart the process. + def restart + if self[:restart] or self.respond_to?(:restartcmd) + cmd = self[:restart] || self.restartcmd + self.execute("restart", cmd) + else + self.stop + self.start end + end - # Check if the process is running. Prefer the 'status' parameter, - # then 'statuscmd' method, then look in the process table. We give - # the object the option to not return a status command, which might - # happen if, for instance, it has an init script (and thus responds to - # 'statuscmd') but does not have 'hasstatus' enabled. - def status - if self[:status] or ( - self.respond_to?(:statuscmd) and self.statuscmd - ) - cmd = self[:status] || self.statuscmd - output = %x(#{cmd} 2>&1) - self.debug "%s status returned %s" % - [self.name, output] - if $? == 0 - return :running - else - return :stopped - end - elsif pid = self.getpid + # Check if the process is running. Prefer the 'status' parameter, + # then 'statuscmd' method, then look in the process table. We give + # the object the option to not return a status command, which might + # happen if, for instance, it has an init script (and thus responds to + # 'statuscmd') but does not have 'hasstatus' enabled. + def status + if self[:status] or ( + self.respond_to?(:statuscmd) and self.statuscmd + ) + cmd = self[:status] || self.statuscmd + self.info "Executing %s" % cmd.inspect + output = %x(#{cmd} 2>&1) + self.debug "%s status returned %s" % + [self.name, output.inspect] + if $? == 0 return :running else return :stopped end + elsif pid = self.getpid + return :running + else + return :stopped end + end - # Run the 'start' parameter command, or the specified 'startcmd'. - def start - cmd = self[:start] || self.startcmd - self.execute("start", cmd) - end + # Run the 'start' parameter command, or the specified 'startcmd'. + def start + cmd = self[:start] || self.startcmd + self.execute("start", cmd) + end - # Stop the service. If a 'stop' parameter is specified, it - # takes precedence; otherwise checks if the object responds to - # a 'stopcmd' method, and if so runs that; otherwise, looks - # for the process in the process table. - # This method will generally not be overridden by submodules. - def stop - if self[:stop] - return self[:stop] - elsif self.respond_to?(:stopcmd) - self.execute("stop", self.stopcmd) - else - pid = getpid - unless pid - self.info "%s is not running" % self.name - return false - end - output = %x("kill #{pid} 2>&1") - if $? != 0 - raise Puppet::Error, - "Could not kill %s, PID %s: %s" % - [self.name, pid, output] - end - return true + # Stop the service. If a 'stop' parameter is specified, it + # takes precedence; otherwise checks if the object responds to + # a 'stopcmd' method, and if so runs that; otherwise, looks + # for the process in the process table. + # This method will generally not be overridden by submodules. + def stop + if self[:stop] + return self[:stop] + elsif self.respond_to?(:stopcmd) + self.execute("stop", self.stopcmd) + else + pid = getpid + unless pid + self.info "%s is not running" % self.name + return false + end + output = %x("kill #{pid} 2>&1") + if $? != 0 + raise Puppet::Error, + "Could not kill %s, PID %s: %s" % + [self.name, pid, output] end + return true end - end - end + end + end end # Load all of the different service types. We could probably get away with diff --git a/lib/puppet/type/service/base.rb b/lib/puppet/type/service/base.rb index 6c0642541..f84fa85dc 100755 --- a/lib/puppet/type/service/base.rb +++ b/lib/puppet/type/service/base.rb @@ -1,17 +1,12 @@ -module Puppet - module ServiceTypes - module BaseSvc - - # The command used to start. Generated if the 'binary' argument - # is passed. - def startcmd - if self[:binary] - return self[:binary] - else - raise Puppet::Error, - "Services must specify a start command or a binary" - end - end +Puppet.type(:service).newsvctype(:base) do + # The command used to start. Generated if the 'binary' argument + # is passed. + def startcmd + if self[:binary] + return self[:binary] + else + raise Puppet::Error, + "Services must specify a start command or a binary" end end end diff --git a/lib/puppet/type/service/debian.rb b/lib/puppet/type/service/debian.rb index aa7303d18..37c33d333 100755 --- a/lib/puppet/type/service/debian.rb +++ b/lib/puppet/type/service/debian.rb @@ -2,49 +2,43 @@ require 'puppet/type/service/init' # Manage debian services. Start/stop is the same as InitSvc, but enable/disable # is special. -module Puppet - module ServiceTypes - module DebianSvc - include Puppet::ServiceTypes::InitSvc - - # Remove the symlinks - def disable - output = %x{update-rc.d -f #{self.name} remove 2>/dev/null} +Puppet.type(:service).newsvctype(:debian, :init) do + # Remove the symlinks + def disable + output = %x{update-rc.d -f #{self.name} remove 2>/dev/null} - unless $? == 0 - raise Puppet::Error, "Could not disable %s: %s" % - [self.name, output] - end - end + unless $? == 0 + raise Puppet::Error, "Could not disable %s: %s" % + [self.name, output] + end + end - def enabled? - output = %x{update-rc.d -n -f #{self.name} remove 2>/dev/null} - unless $? == 0 - raise Puppet::Error, "Could not check %s: %s" % - [self.name, output] - end + def enabled? + output = %x{update-rc.d -n -f #{self.name} remove 2>/dev/null} + unless $? == 0 + raise Puppet::Error, "Could not check %s: %s" % + [self.name, output] + end - # If it's enabled, then it will print output showing removal of - # links. - if output =~ /etc\/rc\d.d/ - return true - else - return false - end - end + # If it's enabled, then it will print output showing removal of + # links. + if output =~ /etc\/rc\d.d/ + return true + else + return false + end + end - def enable(runlevel) - if runlevel - raise Puppet::Error, "Specification of runlevels is not supported" - else - output = %x{update-rc.d #{self.name} defaults 2>/dev/null} - end + def enable(runlevel) + if runlevel + raise Puppet::Error, "Specification of runlevels is not supported" + else + output = %x{update-rc.d #{self.name} defaults 2>/dev/null} + end - unless $? == 0 - raise Puppet::Error, "Could not check %s: %s" % - [self.name, output] - end - end + unless $? == 0 + raise Puppet::Error, "Could not check %s: %s" % + [self.name, output] end end end diff --git a/lib/puppet/type/service/init.rb b/lib/puppet/type/service/init.rb index 27e0d779b..d47c3b969 100755 --- a/lib/puppet/type/service/init.rb +++ b/lib/puppet/type/service/init.rb @@ -1,145 +1,145 @@ -module Puppet - module ServiceTypes - module InitSvc - # Make sure we've got a search path set up. If they don't - # specify one, try to determine one. - def configchk - unless defined? @searchpaths - @searchpaths = [] - end - unless @searchpaths.length > 0 - if init = self.defaultinit - self.notice "Adding default init" - @searchpaths << init - else - self.notice "No default init for %s" % - Facter["operatingsystem"].value - - raise Puppet::Error.new( - "You must specify a valid search path for service %s" % - self.name - ) - end - end - end - - # Get the default init path. - def defaultinit - unless defined? @defaultinit - case Facter["operatingsystem"].value - when "FreeBSD": - @defaultinit = "/etc/rc.d" - else - @defaultinit = "/etc/init.d" - @defaultrc = "/etc/rc%s.d" - end - end - - return @defaultinit - end - - # Mark that our init script supports 'status' commands. - def hasstatus=(value) - case value - when true, "true": @parameters[:hasstatus] = true - when false, "false": @parameters[:hasstatus] = false - else - raise Puppet::Error, "Invalid 'hasstatus' value %s" % - value.inspect - end - end - - # it'd be nice if i didn't throw the output away... - # this command returns true if the exit code is 0, and returns - # false otherwise - def initcmd(cmd) - script = self.initscript +# The standard init-based service type. Many other service types are +# customizations of this module. +Puppet.type(:service).newsvctype(:init) do + + # Set the default init directory. + Puppet.type(:service).attrclass(:path).defaultto do + case Facter["operatingsystem"].value + when "FreeBSD": + @defaultinit = "/etc/rc.d" + else + @defaultinit = "/etc/init.d" + @defaultrc = "/etc/rc%s.d" + end + end +# # Make sure we've got a search path set up. If they don't +# # specify one, try to determine one. +# def configchk +# unless defined? @searchpaths +# Puppet.notice "Initting search paths" +# @searchpaths = [] +# end +# unless @searchpaths.length > 0 +# if init = self.defaultinit +# self.notice "Adding default init" +# @searchpaths << init +# else +# self.notice "No default init for %s" % +# Facter["operatingsystem"].value +# +# raise Puppet::Error.new( +# "You must specify a valid search path for service %s" % +# self.name +# ) +# end +# end +# end +# +# # Get the default init path. +# def defaultinit +# unless defined? @defaultinit +# case Facter["operatingsystem"].value +# when "FreeBSD": +# @defaultinit = "/etc/rc.d" +# else +# @defaultinit = "/etc/init.d" +# @defaultrc = "/etc/rc%s.d" +# end +# end +# +# return @defaultinit +# end + + # Mark that our init script supports 'status' commands. + def hasstatus=(value) + case value + when true, "true": @parameters[:hasstatus] = true + when false, "false": @parameters[:hasstatus] = false + else + raise Puppet::Error, "Invalid 'hasstatus' value %s" % + value.inspect + end + end - self.debug "Executing '%s %s' as initcmd for '%s'" % - [script,cmd,self] + # it'd be nice if i didn't throw the output away... + # this command returns true if the exit code is 0, and returns + # false otherwise + def initcmd(cmd) + script = self.initscript - rvalue = Kernel.system("%s %s" % - [script,cmd]) + self.debug "Executing '%s %s' as initcmd for '%s'" % + [script,cmd,self] - self.debug "'%s' ran with exit status '%s'" % - [cmd,rvalue] + rvalue = Kernel.system("%s %s" % + [script,cmd]) + self.debug "'%s' ran with exit status '%s'" % + [cmd,rvalue] - rvalue - end - # Where is our init script? - def initscript - if defined? @initscript - return @initscript - else - @initscript = self.search(self.name) - end - end + rvalue + end - # Store the search path for init scripts. This will generally not - # be called. - def parampath=(ary) - unless ary.is_a?(Array) - ary = [ary] - end - @parameters[:path] = ary - @searchpaths = ary.find_all { |dir| - File.directory?(dir) - } - end + # Where is our init script? + def initscript + if defined? @initscript + return @initscript + else + @initscript = self.search(self.name) + end + end - # Enable a service, so it's started at boot time. This basically - # just creates links in the RC directories, which means that, well, - # we need to know where the rc directories are. - # FIXME This should probably be a state or something, and - # it should actually create use Symlink objects... - # At this point, people should just link objects for enabling, - # if they're running on a system that doesn't have a tool to - # manage init script links. - #def enable - #end - - #def disable - #end - - def search(name) - @searchpaths.each { |path| - fqname = File.join(path,name) - begin - stat = File.stat(fqname) - rescue - # should probably rescue specific errors... - self.debug("Could not find %s in %s" % [name,path]) - next - end - - # if we've gotten this far, we found a valid script - return fqname - } - raise Puppet::Error, "Could not find init script for '%s'" % name + # Enable a service, so it's started at boot time. This basically + # just creates links in the RC directories, which means that, well, + # we need to know where the rc directories are. + # FIXME This should probably be a state or something, and + # it should actually create use Symlink objects... + # At this point, people should just link objects for enabling, + # if they're running on a system that doesn't have a tool to + # manage init script links. + #def enable + #end + + #def disable + #end + + def search(name) + self[:path].each { |path| + fqname = File.join(path,name) + begin + stat = File.stat(fqname) + rescue + # should probably rescue specific errors... + self.debug("Could not find %s in %s" % [name,path]) + next end - # The start command is just the init scriptwith 'start'. - def startcmd - self.initscript + " start" - end + # if we've gotten this far, we found a valid script + return fqname + } + raise Puppet::Error, "Could not find init script for '%s'" % name + end - # If it was specified that the init script has a 'status' command, then - # we just return that; otherwise, we return false, which causes it to - # fallback to other mechanisms. - def statuscmd - if self[:hasstatus] - return self.initscript + " status" - else - return false - end - end + # The start command is just the init scriptwith 'start'. + def startcmd + self.initscript + " start" + end - # The stop command is just the init script with 'stop'. - def stopcmd - self.initscript + " stop" - end + # If it was specified that the init script has a 'status' command, then + # we just return that; otherwise, we return false, which causes it to + # fallback to other mechanisms. + def statuscmd + if self[:hasstatus] + return self.initscript + " status" + else + return false end end + + # The stop command is just the init script with 'stop'. + def stopcmd + self.initscript + " stop" + end end + +# $Id$ diff --git a/lib/puppet/type/service/smf.rb b/lib/puppet/type/service/smf.rb index 9f59c9d53..f16eb422a 100755 --- a/lib/puppet/type/service/smf.rb +++ b/lib/puppet/type/service/smf.rb @@ -1,29 +1,29 @@ -module Puppet - module ServiceTypes - module SMFSvc - def restartcmd - end - - # The start command is just the init scriptwith 'start'. - def startcmd - self.initscript + " start" - end +# Solaris 10 SMF-style services. This is not yet implemented, which is probably +# somewhat obvious. +Puppet.type(:service).newsvctype(:smf) do + def restartcmd + end - # If it was specified that the init script has a 'status' command, then - # we just return that; otherwise, we return false, which causes it to - # fallback to other mechanisms. - def statuscmd - if self[:hasstatus] - return self.initscript + " status" - else - return false - end - end + # The start command is just the init scriptwith 'start'. + def startcmd + self.initscript + " start" + end - # The stop command is just the init script with 'stop'. - def stopcmd - self.initscript + " stop" - end + # If it was specified that the init script has a 'status' command, then + # we just return that; otherwise, we return false, which causes it to + # fallback to other mechanisms. + def statuscmd + if self[:hasstatus] + return self.initscript + " status" + else + return false end end + + # The stop command is just the init script with 'stop'. + def stopcmd + self.initscript + " stop" + end end + +# $Id$ diff --git a/lib/puppet/type/state.rb b/lib/puppet/type/state.rb index fdc54ffcc..6bd1d3b07 100644 --- a/lib/puppet/type/state.rb +++ b/lib/puppet/type/state.rb @@ -4,10 +4,11 @@ require 'puppet' require 'puppet/element' require 'puppet/statechange' +require 'puppet/parameter' module Puppet -class State < Puppet::Element - attr_accessor :is, :parent +class State < Puppet::Parameter + attr_accessor :is # 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 @@ -19,10 +20,19 @@ class State < Puppet::Element class << self attr_accessor :unmanaged attr_reader :name - end + def inspect + "State(%s)" % self.name + end + + def to_s + self.inspect + end + end + # initialize our state def initialize(hash) + super() @is = nil unless hash.include?(:parent) @@ -39,6 +49,21 @@ class State < Puppet::Element end end + def inspect + str = "State('%s', " % self.name + if defined? @is and @is + str += "@is = '%s', " % @is + else + str += "@is = nil, " + end + + if defined? @should and @should + str += "@should = '%s')" % @should.join(", ") + else + str += "@should = nil)" + 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 @@ -112,7 +137,6 @@ class State < Puppet::Element def should if defined? @should unless @should.is_a?(Array) - self.warning @should.inspect raise Puppet::DevError, "should for %s on %s is not an array" % [self.class.name, @parent.name] end @@ -130,16 +154,20 @@ class State < Puppet::Element @shouldorig = values - if self.respond_to?(:shouldprocess) + if self.respond_to?(:validate) + values.each { |val| + validate(val) + } + end + if self.respond_to?(:munge) @should = values.collect { |val| - self.shouldprocess(val) + self.munge(val) } else @should = values end end - # How should a state change be printed as a string? def change_to_s begin diff --git a/lib/puppet/type/symlink.rb b/lib/puppet/type/symlink.rb index c1ac27b8b..2be33cb1d 100755 --- a/lib/puppet/type/symlink.rb +++ b/lib/puppet/type/symlink.rb @@ -1,13 +1,11 @@ - require 'etc' require 'puppet/type/state' require 'puppet/type/pfile' module Puppet - # okay, how do we deal with parameters that don't have operations - # associated with them? - class State - class SymlinkTarget < Puppet::State + newtype(:symlink) do + @doc = "Create symbolic links to existing files." + newstate(:target) do require 'etc' attr_accessor :file @@ -85,46 +83,26 @@ module Puppet #self.parent.newevent(:event => :inode_changed) end end - end - - class Type - class Symlink < Type - attr_reader :stat, :path, :params - # class instance variable - @states = [ - Puppet::State::SymlinkTarget - ] - - @parameters = [ - :path, - :recurse - ] - - @paramdoc[:path] = "Path of link to create." - @paramdoc[:recurse] = "If target is a directory, recursively create - directories (using `file`'s `source` parameter) and link all - contained files." - @doc = "Create symbolic links to existing files." - @name = :symlink - @namevar = :path - def initialize(hash) - @arghash = self.argclean(hash.dup) - @arghash.delete(self.class.namevar) + attr_reader :stat, :path, :params - super - end + copyparam(Puppet.type(:file), :path) + + newparam(:recurse) do + desc "If target is a directory, recursively create + directories (using `file`'s `source` parameter) and link all + contained files." - def paramrecurse=(value) + munge do |value| @stat = nil - @target = self.state(:target).should + @target = @parent.state(:target).should # we want to remove our state, because we're creating children # to do the links if FileTest.exist?(@target) @stat = File.stat(@target) else - self.info "Target %s must exist for recursive links" % + @parent.info "Target %s must exist for recursive links" % @target return end @@ -135,7 +113,7 @@ module Puppet return end - self.delete(:target) + @parent.delete(:target) recurse = value # we might have a string, rather than a number @@ -158,25 +136,25 @@ module Puppet # working in pfile args = { - :name => self.name, + :name => @parent.name, :linkmaker => true, :recurse => recurse, :source => @target } - dir = Puppet::Type::PFile.implicitcreate(args) - dir.parent = self - self.debug "Got dir %s" % dir.name - self.push dir - #Dir.foreach(@target) { |file| - # next if file =~ /^\.\.?$/ # skip . and .. - # newtarget = File.join(@target,file) - # #stat = File.stat(File.join(@target,file)) - # self.newchild(file, :target => newtarget) - #} + dir = Puppet.type(:file).implicitcreate(args) + dir.parent = @parent + @parent.debug "Got dir %s" % dir.name + @parent.push dir end - end # Puppet::Type::Symlink - end # Puppet::Type + end + + def initialize(hash) + @arghash = self.argclean(hash.dup) + @arghash.delete(self.class.namevar) + super + end + end # Puppet.type(:symlink) end # $Id$ diff --git a/lib/puppet/type/tidy.rb b/lib/puppet/type/tidy.rb index f91eaa754..59d51e680 100755 --- a/lib/puppet/type/tidy.rb +++ b/lib/puppet/type/tidy.rb @@ -4,10 +4,87 @@ require 'puppet/type/state' require 'puppet/type/pfile' module Puppet - # okay, how do we deal with parameters that don't have operations - # associated with them? - class State - class TidyUp < Puppet::State + newtype(:tidy, Puppet.type(:file)) do + @doc = "Remove unwanted files based on specific criteria." + + newparam(:path) do + desc "The path to the file to manage. Must be fully qualified." + isnamevar + end + + copyparam(Puppet.type(:file), :backup) + + newparam(:age) do + desc "Tidy files whose age is equal to or greater than + the specified number of days." + + munge do |age| + case age + when /^[0-9]+$/, /^[0-9]+[dD]/: + Integer(age.gsub(/[^0-9]+/,'')) * + 60 * 60 * 24 + when /^[0-9]+$/, /^[0-9]+[hH]/: + Integer(age.gsub(/[^0-9]+/,'')) * 60 * 60 + when /^[0-9]+[mM]/: + Integer(age.gsub(/[^0-9]+/,'')) * 60 + when /^[0-9]+[sS]/: + Integer(age.gsub(/[^0-9]+/,'')) + else + raise Puppet::Error.new("Invalid tidy age %s" % age) + end + end + end + + newparam(:size) do + desc "Tidy files whose size is equal to or greater than + the specified size. Unqualified values are in kilobytes, but + *b*, *k*, and *m* can be appended to specify *bytes*, *kilobytes*, + and *megabytes*, respectively. Only the first character is + significant, so the full word can also be used." + + munge do |size| + if FileTest.directory?(@parent[:path]) + # don't do size comparisons for directories + return + end + case size + when /^[0-9]+$/, /^[0-9]+[kK]/: + Integer(size.gsub(/[^0-9]+/,'')) * 1024 + when /^[0-9]+[bB]/: + Integer(size.gsub(/[^0-9]+/,'')) + when /^[0-9]+[mM]/: + Integer(size.gsub(/[^0-9]+/,'')) * + 1024 * 1024 + else + raise Puppet::Error.new("Invalid tidy size %s" % size) + end + end + end + + newparam(:type) do + desc "Set the mechanism for determining age. Access + time is the default mechanism, but modification." + + munge do |type| + case type + when "atime", "mtime", "ctime": + @parameters[:type] = type.intern + else + raise Puppet::Error.new("Invalid tidy type %s" % type) + end + end + end + + newparam(:recurse) do + desc "If target is a directory, recursively descend + into the directory looking for files to tidy." + end + + newparam(:rmdirs) do + desc "Tidy directories in addition to files." + end + + newstate(:tidyup) do require 'etc' @nodoc = true @@ -73,110 +150,30 @@ module Puppet return :file_tidied end end - end - - class Type - class Tidy < PFile - - # class instance variable - @states = [ - Puppet::State::TidyUp - ] - - @parameters = [ - :path, - :age, - :size, - :type, - :backup, - :rmdirs, - :recurse - ] - - @paramdoc[:age] = "Tidy files whose age is equal to or greater than - the specified number of days." - @paramdoc[:size] = "Tidy files whose size is equal to or greater than - the specified size. Unqualified values are in kilobytes, but - *b*, *k*, and *m* can be appended to specify *bytes*, *kilobytes*, - and *megabytes*, respectively. Only the first character is - significant, so the full word can also be used." - @paramdoc[:type] = "Set the mechanism for determining age. Access - time is the default mechanism, but modification." - @paramdoc[:recurse] = "If target is a directory, recursively descend - into the directory looking for files to tidy." - @paramdoc[:rmdirs] = "Tidy directories in addition to files." - @doc = "Remove unwanted files based on specific criteria." - @name = :tidy - @namevar = :path - - @depthfirst = true - - def initialize(hash) - super - unless @parameters.include?(:age) or - @parameters.include?(:size) - unless FileTest.directory?(self[:path]) - # don't do size comparisons for directories - raise Puppet::Error, "Tidy must specify size, age, or both" - end - end + @depthfirst = true - # only allow backing up into filebuckets - unless self[:backup].is_a? Puppet::Client::Dipper - self[:backup] = false - end - self[:tidyup] = [:age, :size].collect { |param| - @parameters[param] - }.reject { |p| p == false } - end + def initialize(hash) + super - def paramage=(age) - @parameters[:age] = age - case age - when /^[0-9]+$/, /^[0-9]+[dD]/: - @parameters[:age] = Integer(age.gsub(/[^0-9]+/,'')) * - 60 * 60 * 24 - when /^[0-9]+$/, /^[0-9]+[hH]/: - @parameters[:age] = Integer(age.gsub(/[^0-9]+/,'')) * 60 * 60 - when /^[0-9]+[mM]/: - @parameters[:age] = Integer(age.gsub(/[^0-9]+/,'')) * 60 - when /^[0-9]+[sS]/: - @parameters[:age] = Integer(age.gsub(/[^0-9]+/,'')) - else - raise Puppet::Error.new("Invalid tidy age %s" % age) - end - end - - def paramsize=(size) - if FileTest.directory?(self[:path]) + unless @parameters.include?(:age) or + @parameters.include?(:size) + unless FileTest.directory?(self[:path]) # don't do size comparisons for directories - return - end - case size - when /^[0-9]+$/, /^[0-9]+[kK]/: - @parameters[:size] = Integer(size.gsub(/[^0-9]+/,'')) * 1024 - when /^[0-9]+[bB]/: - @parameters[:size] = Integer(size.gsub(/[^0-9]+/,'')) - when /^[0-9]+[mM]/: - @parameters[:size] = Integer(size.gsub(/[^0-9]+/,'')) * - 1024 * 1024 - else - raise Puppet::Error.new("Invalid tidy size %s" % size) + raise Puppet::Error, "Tidy must specify size, age, or both" end end - def paramtype=(type) - case type - when "atime", "mtime", "ctime": - @parameters[:type] = type.intern - else - raise Puppet::Error.new("Invalid tidy type %s" % type) - end + # only allow backing up into filebuckets + unless self[:backup].is_a? Puppet::Client::Dipper + self[:backup] = false end + self[:tidyup] = [:age, :size].collect { |param| + @parameters[param] + }.reject { |p| p == false } + end - end # Puppet::Type::Symlink - end # Puppet::Type + end end # $Id$ diff --git a/lib/puppet/type/typegen/filerecord.rb b/lib/puppet/type/typegen/filerecord.rb index 6e502c170..537523463 100644 --- a/lib/puppet/type/typegen/filerecord.rb +++ b/lib/puppet/type/typegen/filerecord.rb @@ -5,7 +5,7 @@ require 'etc' require 'puppet/type' require 'puppet/type/typegen' -class Puppet::Type::FileRecord < Puppet::Type::TypeGenerator +class Puppet.type(:filerecord) < Puppet::Type::TypeGenerator class << self # The name of the record type. Probably superfluous. attr_accessor :name @@ -131,7 +131,7 @@ class Puppet::Type::FileRecord < Puppet::Type::TypeGenerator end def initialize(hash) - if self.class == Puppet::Type::FileRecord + if self.class == Puppet.type(:filerecord) self.class.newtype(hash) return end diff --git a/lib/puppet/type/typegen/filetype.rb b/lib/puppet/type/typegen/filetype.rb index c7ed89b25..9fc6b41f0 100644 --- a/lib/puppet/type/typegen/filetype.rb +++ b/lib/puppet/type/typegen/filetype.rb @@ -3,7 +3,7 @@ require 'puppet/type' require 'puppet/type/typegen' -class Puppet::Type::FileType < Puppet::Type::TypeGenerator +class Puppet.type(:filetype) < Puppet::Type::TypeGenerator @parameters = [:name, :recordsep, :escapednewlines] @namevar = :name @@ -32,11 +32,11 @@ class Puppet::Type::FileType < Puppet::Type::TypeGenerator # Add a new record to our filetype. This should never be called on the FileType # class itself, only on its subclasses. def FileType.addrecord(hash = {}) - if self == Puppet::Type::FileRecord + if self == Puppet.type(:filerecord) raise Puppet::DevError, "Cannot add records to the FileType base class" end - newrecord = Puppet::Type::FileRecord.newtype(hash) + newrecord = Puppet.type(:filerecord).newtype(hash) newrecord.filetype = self if block_given? @@ -186,7 +186,7 @@ class Puppet::Type::FileType < Puppet::Type::TypeGenerator # if we are the FileType object itself, we create a new type # otherwise, we create an instance of an existing type # yes, this should be more straightforward - if self.class == Puppet::Type::FileType + if self.class == Puppet.type(:filetype) self.class.newtype(hash) return end diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb index 183b6bb98..fddf64a85 100755 --- a/lib/puppet/type/user.rb +++ b/lib/puppet/type/user.rb @@ -4,19 +4,24 @@ require 'puppet/type/state' require 'puppet/type/nameservice' module Puppet - class State - module UserUID - def self.doc - "The user ID. Must be specified numerically. For new users + newtype(:user, Puppet::Type::NSSType) do + case Facter["operatingsystem"].value + when "Darwin": + @parentstate = Puppet::NameService::NetInfo::NetInfoState + @parentmodule = Puppet::NameService::NetInfo + else + @parentstate = Puppet::NameService::ObjectAdd::ObjectAddUser + @parentmodule = Puppet::NameService::ObjectAdd + end + + newstate(:uid, @parentstate) do + desc "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." - end - def self.name - :uid - end + isautogen def autogen highest = 0 @@ -31,7 +36,7 @@ module Puppet return highest + 1 end - def shouldprocess(value) + munge do |value| case value when String if value =~ /^[-0-9]+$/ @@ -51,17 +56,13 @@ module Puppet end end - module UserGID - def self.doc - "The user's primary group. Can be specified numerically or + newstate(:gid, @parentstate) do + desc "The user's primary group. Can be specified numerically or by name." - end - def self.name - :gid - end + isautogen - def shouldprocess(gid) + munge do |gid| method = :getgrgid case gid when String @@ -93,195 +94,121 @@ module Puppet end end - module UserComment - def self.doc - "A description of the user. Generally is a user's full name." - end + newstate(:comment, @parentstate) do + desc "A description of the user. Generally is a user's full name." - def self.name - :comment - end - - def self.optional? - true - end + isoptional - def self.posixmethod - :gecos - end + @posixmethod = :gecos end - module UserHome - def self.doc - "The home directory of the user. The directory must be created + newstate(:home, @parentstate) do + desc "The home directory of the user. The directory must be created separately and is not currently checked for existence." - end - def self.name - :home - end - - def self.posixmethod - :dir - end + isautogen + @posixmethod = :dir end - module UserShell - def self.doc - "The user's login shell. The shell must exist and be + newstate(:shell, @parentstate) do + desc "The user's login shell. The shell must exist and be executable." - end - - def self.name - :shell - end + isautogen 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 - module UserLocked - def self.doc - "The expected return code. An error will be returned if the - executed command returns something else." - end - - def self.name - :locked - end - end + #newstate(:locked, @parentstate) do + # desc "The expected return code. An error will be returned if the + # executed command returns something else." + #end # FIXME Puppet::State::UserExpire is currently non-functional - module UserExpire - def self.doc - "The expected return code. An error will be returned if the - executed command returns something else." - end - - def self.name; :expire; end - end + #newstate(:expire, @parentstate) do + # desc "The expected return code. An error will be returned if the + # executed command returns something else." + # @objectaddflag = "-e" + # isautogen + #end # FIXME Puppet::State::UserInactive is currently non-functional - module UserInactive - def self.doc - "The expected return code. An error will be returned if the - executed command returns something else." - end - - def self.name; :inactive; end + #newstate(:inactive, @parentstate) do + # desc "The expected return code. An error will be returned if the + # executed command returns something else." + # @objectaddflag = "-f" + # isautogen + #end + + newparam(:name) do + desc "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." + isnamevar end - end + @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." - class Type - class User < Type - statenames = [ - "UserUID", - "UserGID", - "UserComment", - "UserHome", - "UserShell" - ] - @statemodule = nil - case Facter["operatingsystem"].value - when "Darwin": - @statemodule = Puppet::NameService::NetInfo - else - @statemodule = Puppet::NameService::ObjectAdd - end - - class << self - attr_accessor :netinfodir - attr_accessor :statemodule - end + @netinfodir = "users" - @states = [] + def exists? + self.class.parentmodule.exists?(self) + end - @states = statenames.collect { |name| - fullname = @statemodule.to_s + "::" + name + def getinfo(refresh = false) + if @userinfo.nil? or refresh == true begin - eval(fullname) - rescue NameError - raise Puppet::DevError, "Could not retrieve state class %s" % - fullname + @userinfo = Etc.getpwnam(self[:name]) + rescue ArgumentError => detail + @userinfo = nil end - }.each { |klass| - klass.complete - } - - @parameters = [ - :name - ] - - @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 - - @netinfodir = "users" - - def exists? - self.class.statemodule.exists?(self) end - 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 + @userinfo + end - # Verify that they have provided everything necessary, if we - # are trying to manage the user - if self.managed? - self.class.states.each { |state| - next if @states.include?(state.name) - - unless state.autogen? or state.optional? - if state.method_defined?(:autogen) - self[state.name] = :auto - else - raise Puppet::Error, - "Users require a value for %s" % state.name - end + def initialize(hash) + @userinfo = nil + super + + # Verify that they have provided everything necessary, if we + # are trying to manage the user + if self.managed? + self.class.states.each { |state| + next if @states.include?(state.name) + + unless state.autogen? or state.optional? + if state.method_defined?(:autogen) + self[state.name] = :auto + else + raise Puppet::Error, + "Users require a value for %s" % state.name end - } - - if @states.empty? - self[:comment] = self[:name] end + } + + if @states.empty? + self[:comment] = self[:name] end end + end - def retrieve - info = self.getinfo(true) - - if info.nil? - # the user does not exist - @states.each { |name, state| - state.is = :notfound - } - return - else - super - end + def retrieve + info = self.getinfo(true) + + if info.nil? + # the user does not exist + @states.each { |name, state| + state.is = :notfound + } + return + else + super end end end diff --git a/lib/puppet/util.rb b/lib/puppet/util.rb index d1878f592..87e80b143 100644 --- a/lib/puppet/util.rb +++ b/lib/puppet/util.rb @@ -19,8 +19,8 @@ module Util if group.is_a?(Integer) gid = group else - unless obj = Puppet::Type::Group[group] - obj = Puppet::Type::Group.create( + unless obj = Puppet.type(:group)[group] + obj = Puppet.type(:group).create( :name => group, :check => [:gid] ) @@ -47,8 +47,8 @@ module Util if user.is_a?(Integer) uid = user else - unless obj = Puppet::Type::User[user] - obj = Puppet::Type::User.create( + unless obj = Puppet.type(:user)[user] + obj = Puppet.type(:user).create( :name => user, :check => [:uid, :gid] ) @@ -114,12 +114,12 @@ module Util if useself Puppet::Log.create( :level => level, + :source => self, :message => args ) else Puppet::Log.create( :level => level, - :source => self, :message => args ) end @@ -149,6 +149,15 @@ module Util return true end end + + def self.symbolize(value) + case value + when String: value = value.intern + when Symbol: # nothing + else + raise ArgumentError, "'%s' must be a string or symbol" % value + end + end end end |