diff options
-rwxr-xr-x | lib/puppet/type/mount.rb | 19 | ||||
-rw-r--r-- | lib/puppet/type/state.rb | 166 | ||||
-rwxr-xr-x | test/types/state.rb | 142 |
3 files changed, 242 insertions, 85 deletions
diff --git a/lib/puppet/type/mount.rb b/lib/puppet/type/mount.rb index 259d2f894..518f21884 100755 --- a/lib/puppet/type/mount.rb +++ b/lib/puppet/type/mount.rb @@ -1,10 +1,10 @@ require 'puppet/type/parsedtype' module Puppet - newtype(:mount, Puppet::Type::ParsedType) do + newtype(:mount) do # Use the normal parent class, because we actually want to # call code when sync() is called. - newstate(:ensure, :parent => Puppet::State) do + newstate(:ensure) do desc "Control what to do with this mount. If the value is ``present``, the mount is entered into the mount table, but not mounted, if it is ``absent``, the entry is removed @@ -12,19 +12,22 @@ module Puppet currently mounted, if it is ``mounted``, the filesystem is entered into the mount table and mounted." - newvalue(:present, :event => :mount_created) do - # The parsedtype nature of the provider automatically - # creates the mount in the file, and we're not mounting, - # so we don't do anything here. + newvalue(:present, :call => :after) do + if provider.mounted? + provider.unmount + return :mount_unmounted + else + return :mount_created + end end - newvalue(:absent, :event => :mount_deleted) do + newvalue(:absent, :event => :mount_deleted, :call => :after) do if provider.mounted? provider.unmount end end - newvalue(:mounted, :event => :mount_mounted) do + newvalue(:mounted, :event => :mount_mounted, :call => :after) do # We have to flush any changes to disk. @parent.flush diff --git a/lib/puppet/type/state.rb b/lib/puppet/type/state.rb index a85ea801f..c639b671b 100644 --- a/lib/puppet/type/state.rb +++ b/lib/puppet/type/state.rb @@ -36,13 +36,25 @@ class State < Puppet::Parameter end end - # Only retrieve the event, don't autogenerate one. - def self.event(value) - if value.is_a?(String) - value = symbolize(value) + # Look up a value's name, so we can find options and such. + def self.value_name(value) + name = symbolize(value) + if @parametervalues[name] + return name + elsif ary = self.match?(value) + return ary[0] + else + return nil + end + end + + # Retrieve an option set when a value was defined. + def self.value_option(name, option) + if option.is_a?(String) + option = symbolize(option) end - if hash = @parameteroptions[value] - hash[:event] + if hash = @parameteroptions[name] + hash[option] else nil end @@ -62,6 +74,11 @@ class State < Puppet::Parameter # The first argument to the method is either the value itself or a regex. # The second argument is an option hash; valid options are: # * <tt>:event</tt>: The event that should be returned when this value is set. + # * <tt>:call</tt>: 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) name = name.intern if name.is_a? String @@ -73,6 +90,12 @@ class State < Puppet::Parameter paramopts[symbolize(opt)] = symbolize(val) end + # By default, call the block instead of the provider. + if block_given? + paramopts[:call] ||= :instead + else + paramopts[:call] ||= :none + end # If there was no block given, we still want to store the information # for validation, but we won't be defining a method block ||= true @@ -88,16 +111,63 @@ class State < Puppet::Parameter method = "set_" + name.to_s settor = paramopts[:settor] || (self.name.to_s + "=") define_method(method, &block) + paramopts[:method] = method end when Regexp - # The regexes are handled in parameter.rb + # The regexes are handled in parameter.rb. This value is used + # for validation. @parameterregexes[name] = block + + # This is used for looking up the block for execution. + if block_given? + paramopts[:block] = block + end else raise ArgumentError, "Invalid value %s of type %s" % [name, name.class] end 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.is] + + 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], @parent.line, @parent.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. + # If the regex was defined with no associated block, then just pass + # through and the correct event will be passed back. + event = self.instance_eval(&block) + end + return event, name + end + # How should a state change be printed as a string? def change_to_s begin @@ -121,9 +191,9 @@ class State < Puppet::Parameter # Figure out which event to return. # not specified. - def event(value, event = nil) - if setevent = self.class.event(value) - return setevent + def event(name, event = nil) + if value_event = self.class.value_option(name, :event) + return value_event else if event and event.is_a?(Symbol) if event == :nochange @@ -273,63 +343,30 @@ class State < Puppet::Parameter @is = provider.send(self.class.name) end - # Call the method associated with a given value. - def set - if self.insync? - self.log "already in sync" - return nil - end - - value = self.should - if value.nil? - self.devfail "Got a nil value for should" - 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 = symbolize(value) - method = "set_" + value.to_s - event = nil - if self.respond_to?(method) - self.debug "setting %s (currently %s)" % [value, self.is] + name = self.class.value_name(value) - 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], @parent.line, @parent.file) - error.set_backtrace detail.backtrace - raise error - end - elsif ary = self.class.match?(value) and ary[1].is_a?(Proc) - # FIXME It'd be better here to define a method, so that - # the blocks could return values. - # If the regex was defined with no associated block, then just pass - # through and the correct event will be passed back. - if ary[1].is_a?(Proc) - event = self.instance_eval(&ary[1]) - end - else - # This will get set if the regex matches but has no proc - if ary - name = ary[0] - end + call = self.class.value_option(name, :call) + + # If we're supposed to call the block first or instead, call it now + if call == :before or call == :instead + event, tmp = call_valuemethod(name, value) + end + unless call == :instead if @parent.provider - begin - provider.send(self.class.name.to_s + "=", self.should) - rescue NoMethodError - self.fail "The %s provider can not handle attribute %s" % - [provider.class.name, self.class.name] - end + 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, self.should.inspect] + [self.class.name, value.inspect] end end + if call == :after + event, tmp = call_valuemethod(name, value) + end return event(name, event) end @@ -383,16 +420,17 @@ class State < Puppet::Parameter if self.insync? self.info "already in sync" return nil - #else - #self.info "%s vs %s" % [self.is.inspect, self.should.inspect] end unless self.class.values self.devfail "No values defined for %s" % self.class.name end - # Set ourselves to whatever our should value is. - self.set + if value = self.should + set(value) + else + self.devfail "Got a nil value for should" + end end # The states need to return tags so that logs correctly collect them. diff --git a/test/types/state.rb b/test/types/state.rb index e079c5c41..a429ecd2c 100755 --- a/test/types/state.rb +++ b/test/types/state.rb @@ -30,6 +30,65 @@ class TestState < Test::Unit::TestCase } end + def newmodel(name) + # Create an object that responds to mystate as an attr + provklass = Class.new { attr_accessor name } + prov = provklass.new + + klass = Class.new { attr_accessor :provider, :path } + klassinst = klass.new + klassinst.path = "instpath" + klassinst.provider = prov + + return prov, klassinst + end + + # Make sure we correctly look up names. + def test_value_name + state = newstate() + + state.newvalue(:one) + state.newvalue(/\d+/) + + name = nil + ["one", :one].each do |value| + assert_nothing_raised do + name = state.value_name(value) + end + assert_equal(:one, name) + end + ["42"].each do |value| + assert_nothing_raised do + name = state.value_name(value) + end + assert_equal(/\d+/, name) + end + ["two", :three].each do |value| + assert_nothing_raised do + name = state.value_name(value) + end + assert_nil(name) + end + end + + # Test that we correctly look up options for values. + def test_value_option + state = newstate() + + options = { + :one => {:event => :yay, :call => :before}, + /\d+/ => {:event => :fun, :call => :instead} + } + state.newvalue(:one, options[:one]) + state.newvalue(/\d+/, options[/\d+/]) + + options.each do |name, opts| + opts.each do |param, value| + assert_equal(value, state.value_option(name, param)) + end + end + end + def test_newvalue state = newstate() @@ -46,6 +105,10 @@ class TestState < Test::Unit::TestCase end } + # Make sure we default to using the block instead + assert_equal(:instead, state.value_option(:one, :call), + ":call was not set to :instead when a block was provided") + inst = newinst(state) assert_nothing_raised { @@ -141,14 +204,10 @@ class TestState < Test::Unit::TestCase state.newvalue(/^\d+$/, :event => :matched_number) } - # Create an object that responds to mystate as an attr - provklass = Class.new { attr_accessor :mystate } - prov = provklass.new + assert_equal(:none, state.value_option(:value, :call), + ":call was not set to none when no block is provided") - klass = Class.new { attr_accessor :provider, :path } - klassinst = klass.new - klassinst.path = "instpath" - klassinst.provider = prov + prov, klassinst = newmodel(:mystate) inst = newinst(state, klassinst) @@ -199,18 +258,75 @@ class TestState < Test::Unit::TestCase p = s.new(1, "yay", "rah") mystate = Class.new(Puppet::State) mystate.initvars + + mystate.newvalue :mkfailure do + raise "It's all broken" + end state = mystate.new(:parent => p) - class << state - def set_mkfailure - raise "It's all broken" + assert_raise(Puppet::Error) do + state.set(:mkfailure) + end + end + + # Make sure 'set' behaves correctly WRT to call order. This tests that the + # :call value is handled correctly in all cases. + def test_set + state = newstate(:mystate) + + $setting = [] + + newval = proc do |name, call| + options = {} + if call + options[:call] = name + block = proc { $setting << name } end + assert_nothing_raised("Could not create %s value" % name) { + if block + state.newvalue(name, options, &block) + else + state.newvalue(name, options) + end + } end - state.should = :mkfailure + newval.call(:none, false) - assert_raise(Puppet::Error) do - state.set + # Create a value with no block; it should default to :none + newval.call(:before, true) + + # One with a block but after + newval.call(:after, true) + + # One with an explicit instead + newval.call(:instead, true) + + # And one with an implicit instead + assert_nothing_raised do + state.newvalue(:implicit) do + $setting << :implicit + end + end + + # Now create a provider + prov, model = newmodel(:mystate) + inst = newinst(state, model) + + # Mark when we're called + prov.meta_def(:mystate=) do |value| $setting << :provider end + + # Now run through the list and make sure everything is correct + {:before => [:before, :provider], + :after => [:provider, :after], + :instead => [:instead], + :none => [:provider], + :implicit => [:implicit] + }.each do |name, result| + inst.set(name) + + assert_equal(result, $setting, "%s was not handled right" % name) + $setting.clear end end end |