Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tag: 2.7.6
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 340 lines (282 sloc) 10.948 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
# 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
  require 'puppet/property/ensure'

  # 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
      @array_matching ||= :first
    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)
      raise ArgumentError, "Supported values for Property#array_matching are 'first' and 'all'" unless [:first, :all].include?(value)
      @array_matching = value
    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:
  # * <tt>:method</tt>: The name of the method to define. Defaults to 'set_<value>'.
  # * <tt>:required_features</tt>: A list of features this value requires.
  # * <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)
    value = value_collection.newvalue(name, options, &block)

    define_method(value.method, &value.block) if value.method and value.block
    value
  end

  # Call the provider method.
  def call_provider(value)
      provider.send(self.class.name.to_s + "=", value)
  rescue NoMethodError
      self.fail "The #{provider.class.name} provider can not handle attribute #{self.class.name}"
  end

  # Call the dynamically-created method associated with our value, if
  # there is one.
  def call_valuemethod(name, value)
    if method = self.class.value_option(name, :method) and self.respond_to?(method)
      begin
        event = self.send(method)
      rescue Puppet::Error
        raise
      rescue => detail
        puts detail.backtrace if Puppet[:trace]
        error = Puppet::Error.new("Could not set '#{value} on #{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.
      self.instance_eval(&block)
    else
      devfail "Could not find method for value '#{name}'"
    end
  end

  # How should a property change be printed as a string?
  def change_to_s(current_value, newvalue)
    begin
      if current_value == :absent
        return "defined '#{name}' as '#{should_to_s(newvalue)}'"
      elsif newvalue == :absent or newvalue == [:absent]
        return "undefined '#{name}' from '#{is_to_s(current_value)}'"
      else
        return "#{name} changed '#{is_to_s(current_value)}' to '#{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 '#{name}' to string: #{detail}"
    end
  end

  # Figure out which event to return.
  def event_name
    value = self.should

    event_name = self.class.value_option(value, :event) and return event_name

    name == :ensure or return (name.to_s + "_changed").to_sym

    return (resource.type.to_s + case value
    when :present; "_created"
    when :absent; "_removed"
    else
      "_changed"
    end).to_sym
  end

  # Return a modified form of the resource event.
  def event
    resource.event :name => event_name, :desired_value => should, :property => self, :source_description => path
  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.
  #
  # Don't override this method.
  def safe_insync?(is)
    # If there is no @should value, consider the property to be in sync.
    return true unless @should

    # Otherwise delegate to the (possibly derived) insync? method.
    insync?(is)
  end

  def self.method_added(sym)
    raise "Puppet::Property#safe_insync? shouldn't be overridden; please override insync? instead" if sym == :safe_insync?
  end

  # This method should be overridden by derived classes if necessary
  # to provide extra logic to determine whether the property is in
  # sync.
  def insync?(is)
    self.devfail "#{self.class.name}'s should is not array" unless @should.is_a?(Array)

    # an empty array is analogous to no should values
    return true if @should.empty?

    # Look for a matching value
    return (is == @should or is == @should.collect { |v| v.to_s }) if match_all?

    @should.each { |val| return true if is == val or is == val.to_s }

    # otherwise, return false
    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)

          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
    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
      call_valuemethod(name, value)
    elsif call == :none
      # 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 "#{self.class.name} cannot handle values of type #{value.inspect}" unless @resource.provider
      call_provider(value)
    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 '#{call}' for property '#{self.class.name}'"
    end
  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
    return nil unless defined?(@should)

    self.devfail "should for #{self.class.name} on #{resource.name} is not an array" unless @should.is_a?(Array)

    if match_all?
      return @should.collect { |val| self.unmunge(val) }
    else
      return self.unmunge(@should[0])
    end
  end

  # Set the should value.
  def should=(values)
    values = [values] unless values.is_a?(Array)

    @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

  # 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)
      features = Array(features)
      needed_features = features.collect { |f| f.to_s }.join(", ")
      raise ArgumentError, "Provider must have features '#{needed_features}' to set '#{self.class.name}' to '#{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
end
Something went wrong with that request. Please try again.