# The virtual base class for properties, which are the self-contained building # blocks for actually doing work on the system. require 'puppet' require 'puppet/parameter' class Puppet::Property < Puppet::Parameter # Because 'should' uses an array, we have a special method for handling # it. We also want to keep copies of the original values, so that # they can be retrieved and compared later when merging. attr_reader :shouldorig attr_writer :noop class << self attr_accessor :unmanaged attr_reader :name # Return array matching info, defaulting to just matching # the first value. def array_matching unless defined?(@array_matching) @array_matching = :first end @array_matching end # Set whether properties should match all values or just the first one. def array_matching=(value) value = value.intern if value.is_a?(String) unless [:first, :all].include?(value) raise ArgumentError, "Supported values for Property#array_matching are 'first' and 'all'" end @array_matching = value end def checkable @checkable = true end def uncheckable @checkable = false end def checkable? if defined? @checkable return @checkable else return true end end end # Look up a value's name, so we can find options and such. def self.value_name(name) if value = value_collection.match?(name) value.name end end # Retrieve an option set when a value was defined. def self.value_option(name, option) if value = value_collection.value(name) value.send(option) end end # Define a new valid value for a property. You must provide the value itself, # usually as a symbol, or a regex to match the value. # # The first argument to the method is either the value itself or a regex. # The second argument is an option hash; valid options are: # * :method: The name of the method to define. Defaults to 'set_'. # * :required_features: A list of features this value requires. # * :event: The event that should be returned when this value is set. # * :call: When to call any associated block. The default value # is ``instead``, which means to call the value instead of calling the # provider. You can also specify ``before`` or ``after``, which will # call both the block and the provider, according to the order you specify # (the ``first`` refers to when the block is called, not the provider). def self.newvalue(name, options = {}, &block) value = value_collection.newvalue(name, options, &block) if value.method and value.block define_method(value.method, &value.block) end value end # Call the provider method. def call_provider(value) begin provider.send(self.class.name.to_s + "=", value) rescue NoMethodError self.fail "The %s provider can not handle attribute %s" % [provider.class.name, self.class.name] end end # Call the dynamically-created method associated with our value, if # there is one. def call_valuemethod(name, value) event = nil if method = self.class.value_option(name, :method) and self.respond_to?(method) #self.debug "setting %s (currently %s)" % [value, self.retrieve] begin event = self.send(method) rescue Puppet::Error raise rescue => detail if Puppet[:trace] puts detail.backtrace end error = Puppet::Error.new("Could not set %s on %s: %s" % [value, self.class.name, detail], @resource.line, @resource.file) error.set_backtrace detail.backtrace raise error end elsif block = self.class.value_option(name, :block) # FIXME It'd be better here to define a method, so that # the blocks could return values. event = self.instance_eval(&block) else devfail "Could not find method for value '%s'" % name end return event, name end # How should a property change be printed as a string? def change_to_s(currentvalue, newvalue) begin if currentvalue == :absent return "defined '%s' as '%s'" % [self.name, self.should_to_s(newvalue)] elsif newvalue == :absent or newvalue == [:absent] return "undefined %s from '%s'" % [self.name, self.is_to_s(currentvalue)] else return "%s changed '%s' to '%s'" % [self.name, self.is_to_s(currentvalue), self.should_to_s(newvalue)] end rescue Puppet::Error, Puppet::DevError raise rescue => detail puts detail.backtrace if Puppet[:trace] raise Puppet::DevError, "Could not convert change %s to string: %s" % [self.name, detail] end end # Figure out which event to return. def event(name, event = nil) if value_event = self.class.value_option(name, :event) return value_event end if event and event.is_a?(Symbol) if event == :nochange return nil else return event end end if self.class.name == :ensure event = case self.should when :present; (@resource.class.name.to_s + "_created").intern when :absent; (@resource.class.name.to_s + "_removed").intern else (@resource.class.name.to_s + "_changed").intern end else event = (@resource.class.name.to_s + "_changed").intern end return event end attr_reader :shadow # initialize our property def initialize(hash = {}) super if ! self.metaparam? and klass = Puppet::Type.metaparamclass(self.class.name) setup_shadow(klass) end end # Determine whether the property 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 property to be in-sync # since we cannot fix it. Otherwise, we expect our should value # to be an array, and if @is matches any of those values, then # we consider it to be in-sync. def insync?(is) #debug "%s value is '%s', should be '%s'" % # [self,self.is.inspect,self.should.inspect] unless defined? @should and @should return true end unless @should.is_a?(Array) self.devfail "%s's should is not array" % self.class.name end # an empty array is analogous to no should values if @should.empty? return true end # Look for a matching value if match_all? return (is == @should or is == @should.collect { |v| v.to_s }) else @should.each { |val| if is == val or is == val.to_s return true end } end # otherwise, return false return false end # because the @should and @is vars might be in weird formats, # we need to set up a mechanism for pretty printing of the values # default to just the values, but this way individual properties can # override these methods def is_to_s(currentvalue) currentvalue end # Send a log message. def log(msg) unless @resource[:loglevel] self.devfail "Parent %s has no loglevel" % @resource.name end Puppet::Util::Log.create( :level => @resource[:loglevel], :message => msg, :source => self ) end # Should we match all values, or just the first? def match_all? self.class.array_matching == :all end # Execute our shadow's munge code, too, if we have one. def munge(value) self.shadow.munge(value) if self.shadow super end # each property class must define the name() method, and property instances # do not change that name # this implicitly means that a given object can only have one property # instance of a given property class def name return self.class.name end # for testing whether we should actually do anything def noop # This is only here to make testing easier. if @resource.respond_to?(:noop?) @resource.noop? else if defined?(@noop) @noop else Puppet[:noop] end end end # By default, call the method associated with the property name on our # provider. In other words, if the property name is 'gid', we'll call # 'provider.gid' to retrieve the current value. def retrieve provider.send(self.class.name) end # Set our value, using the provider, an associated block, or both. def set(value) # Set a name for looking up associated options like the event. name = self.class.value_name(value) call = self.class.value_option(name, :call) || :none if call == :instead event, tmp = call_valuemethod(name, value) elsif call == :none if @resource.provider call_provider(value) else # They haven't provided a block, and our parent does not have # a provider, so we have no idea how to handle this. self.fail "%s cannot handle values of type %s" % [self.class.name, value.inspect] end else # LAK:NOTE 20081031 This is a change in behaviour -- you could # previously specify :call => [;before|:after], which would call # the setter *in addition to* the block. I'm convinced this # was never used, and it makes things unecessarily complicated. # If you want to specify a block and still call the setter, then # do so in the block. devfail "Cannot use obsolete :call value '%s' for property '%s'" % [call, self.class.name] end return event(name, event) end # If there's a shadowing metaparam, instantiate it now. # This allows us to create a property or parameter with the # same name as a metaparameter, and the metaparam will only be # stored as a shadow. def setup_shadow(klass) @shadow = klass.new(:resource => self.resource) end # Only return the first value def should if defined? @should unless @should.is_a?(Array) self.devfail "should for %s on %s is not an array" % [self.class.name, @resource.name] end if match_all? return @should.collect { |val| self.unmunge(val) } else return self.unmunge(@should[0]) end else return nil end end # Set the should value. def should=(values) unless values.is_a?(Array) values = [values] end @shouldorig = values values.each { |val| validate(val) } @should = values.collect { |val| self.munge(val) } end def should_to_s(newvalue) [newvalue].flatten.join(" ") end def sync devfail "Got a nil value for should" unless should set(should) end def to_s return "%s(%s)" % [@resource.name,self.name] end # Verify that the passed value is valid. # If the developer uses a 'validate' hook, this method will get overridden. def unsafe_validate(value) super validate_features_per_value(value) end # Make sure that we've got all of the required features for a given value. def validate_features_per_value(value) if features = self.class.value_option(self.class.value_name(value), :required_features) raise ArgumentError, "Provider must have features '%s' to set '%s' to '%s'" % [[features].flatten.join(", "), self.class.name, value] unless provider.satisfies?(features) end end # Just return any should value we might have. def value self.should end # Match the Parameter interface, but we really just use 'should' internally. # Note that the should= method does all of the validation and such. def value=(value) self.should = value end # This property will get automatically added to any type that responds # to the methods 'exists?', 'create', and 'destroy'. class Ensure < Puppet::Property @name = :ensure def self.defaultvalues newvalue(:present) do if @resource.provider and @resource.provider.respond_to?(:create) @resource.provider.create else @resource.create end nil # return nil so the event is autogenerated end newvalue(:absent) do if @resource.provider and @resource.provider.respond_to?(:destroy) @resource.provider.destroy else @resource.destroy end nil # return nil so the event is autogenerated end defaultto do if @resource.managed? :present else nil end end # This doc will probably get overridden @doc ||= "The basic property that the resource should be in." end def self.inherited(sub) # Add in the two properties that everyone will have. sub.class_eval do end end def change_to_s(currentvalue, newvalue) begin if currentvalue == :absent or currentvalue.nil? return "created" elsif newvalue == :absent return "removed" else return "%s changed '%s' to '%s'" % [self.name, self.is_to_s(currentvalue), self.should_to_s(newvalue)] end rescue Puppet::Error, Puppet::DevError raise rescue => detail raise Puppet::DevError, "Could not convert change %s to string: %s" % [self.name, detail] end end def retrieve # XXX This is a problem -- whether the object exists or not often # depends on the results of other properties, yet we're the first property # to get checked, which means that those other properties do not have # @is values set. This seems to be the source of quite a few bugs, # although they're mostly logging bugs, not functional ones. if prov = @resource.provider and prov.respond_to?(:exists?) result = prov.exists? elsif @resource.respond_to?(:exists?) result = @resource.exists? else raise Puppet::DevError, "No ability to determine if %s exists" % @resource.class.name end if result return :present else return :absent end end # If they're talking about the thing at all, they generally want to # say it should exist. #defaultto :present defaultto do if @resource.managed? :present else nil end end end end