diff --git a/lib/puppet/file_collection.rb b/lib/puppet/file_collection.rb
index 451b496a1..69f59ffdf 100644
--- a/lib/puppet/file_collection.rb
+++ b/lib/puppet/file_collection.rb
@@ -1,20 +1,28 @@
# A simple way to turn file names into singletons,
# so we don't have tons of copies of each file path around.
class Puppet::FileCollection
+ require 'puppet/file_collection/lookup'
+
+ def self.collection
+ @collection
+ end
+
def initialize
@paths = []
end
def index(path)
if @paths.include?(path)
return @paths.index(path)
else
@paths << path
return @paths.length - 1
end
end
def path(index)
@paths[index]
end
+
+ @collection = self.new
end
diff --git a/lib/puppet/file_collection/lookup.rb b/lib/puppet/file_collection/lookup.rb
index 8f69c6681..ddb0c8431 100644
--- a/lib/puppet/file_collection/lookup.rb
+++ b/lib/puppet/file_collection/lookup.rb
@@ -1,16 +1,20 @@
require 'puppet/file_collection'
# A simple module for looking up file paths and indexes
# in a file collection.
module Puppet::FileCollection::Lookup
attr_accessor :line, :file_index
+ def file_collection
+ Puppet::FileCollection.collection
+ end
+
def file=(path)
@file_index = file_collection.index(path)
end
def file
return nil unless file_index
file_collection.path(file_index)
end
end
diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb
index 303d75bac..ab23dd1f8 100644
--- a/lib/puppet/parser/ast.rb
+++ b/lib/puppet/parser/ast.rb
@@ -1,118 +1,119 @@
# the parent class for all of our syntactical objects
require 'puppet'
require 'puppet/util/autoload'
+require 'puppet/file_collection/lookup'
# The base class for all of the objects that make up the parse trees.
# Handles things like file name, line #, and also does the initialization
# for all of the parameters of all of the child objects.
class Puppet::Parser::AST
# Do this so I don't have to type the full path in all of the subclasses
AST = Puppet::Parser::AST
+ include Puppet::FileCollection::Lookup
+
include Puppet::Util::Errors
include Puppet::Util::MethodHelper
include Puppet::Util::Docs
- attr_accessor :line, :file, :parent, :scope
+ attr_accessor :parent, :scope
# don't fetch lexer comment by default
def use_docs
self.class.use_docs
end
# allow our subclass to specify they want documentation
class << self
attr_accessor :use_docs
def associates_doc
self.use_docs = true
end
end
# Does this ast object set something? If so, it gets evaluated first.
def self.settor?
if defined? @settor
@settor
else
false
end
end
# Evaluate the current object. Just a stub method, since the subclass
# should override this method.
# of the contained children and evaluates them in turn, returning a
# list of all of the collected values, rejecting nil values
def evaluate(*options)
raise Puppet::DevError, "Did not override #evaluate in %s" % self.class
end
# Throw a parse error.
def parsefail(message)
self.fail(Puppet::ParseError, message)
end
# Wrap a statemp in a reusable way so we always throw a parse error.
def parsewrap
exceptwrap :type => Puppet::ParseError do
yield
end
end
# The version of the evaluate method that should be called, because it
# correctly handles errors. It is critical to use this method because
# it can enable you to catch the error where it happens, rather than
# much higher up the stack.
def safeevaluate(*options)
# We duplicate code here, rather than using exceptwrap, because this
# is called so many times during parsing.
begin
return self.evaluate(*options)
rescue Puppet::Error => detail
raise adderrorcontext(detail)
rescue => detail
error = Puppet::Error.new(detail.to_s)
# We can't use self.fail here because it always expects strings,
# not exceptions.
raise adderrorcontext(error, detail)
end
end
# Initialize the object. Requires a hash as the argument, and
# takes each of the parameters of the hash and calls the settor
# method for them. This is probably pretty inefficient and should
# likely be changed at some point.
def initialize(args)
- @file = nil
- @line = nil
set_options(args)
end
end
# And include all of the AST subclasses.
require 'puppet/parser/ast/arithmetic_operator'
require 'puppet/parser/ast/astarray'
require 'puppet/parser/ast/branch'
require 'puppet/parser/ast/boolean_operator'
require 'puppet/parser/ast/caseopt'
require 'puppet/parser/ast/casestatement'
require 'puppet/parser/ast/collection'
require 'puppet/parser/ast/collexpr'
require 'puppet/parser/ast/comparison_operator'
require 'puppet/parser/ast/definition'
require 'puppet/parser/ast/else'
require 'puppet/parser/ast/function'
require 'puppet/parser/ast/hostclass'
require 'puppet/parser/ast/ifstatement'
require 'puppet/parser/ast/leaf'
require 'puppet/parser/ast/minus'
require 'puppet/parser/ast/node'
require 'puppet/parser/ast/nop'
require 'puppet/parser/ast/not'
require 'puppet/parser/ast/resource'
require 'puppet/parser/ast/resource_defaults'
require 'puppet/parser/ast/resource_override'
require 'puppet/parser/ast/resource_reference'
require 'puppet/parser/ast/resourceparam'
require 'puppet/parser/ast/selector'
require 'puppet/parser/ast/tag'
require 'puppet/parser/ast/vardef'
diff --git a/lib/puppet/parser/ast/leaf.rb b/lib/puppet/parser/ast/leaf.rb
index c545c1e47..dba9812a7 100644
--- a/lib/puppet/parser/ast/leaf.rb
+++ b/lib/puppet/parser/ast/leaf.rb
@@ -1,90 +1,90 @@
class Puppet::Parser::AST
# The base class for all of the leaves of the parse trees. These
# basically just have types and values. Both of these parameters
# are simple values, not AST objects.
class Leaf < AST
attr_accessor :value, :type
# Return our value.
def evaluate(scope)
return @value
end
def to_s
return @value
end
end
# The boolean class. True or false. Converts the string it receives
# to a Ruby boolean.
class Boolean < AST::Leaf
# Use the parent method, but then convert to a real boolean.
def initialize(hash)
super
unless @value == true or @value == false
raise Puppet::DevError,
"'%s' is not a boolean" % @value
end
@value
end
end
# The base string class.
class String < AST::Leaf
# Interpolate the string looking for variables, and then return
# the result.
def evaluate(scope)
- return scope.strinterp(@value, @file, @line)
+ return scope.strinterp(@value, file, line)
end
end
# An uninterpreted string.
class FlatString < AST::Leaf
def evaluate(scope)
return @value
end
end
# The 'default' option on case statements and selectors.
class Default < AST::Leaf; end
# Capitalized words; used mostly for type-defaults, but also
# get returned by the lexer any other time an unquoted capitalized
# word is found.
class Type < AST::Leaf; end
# Lower-case words.
class Name < AST::Leaf; end
# double-colon separated class names
class ClassName < AST::Leaf; end
# undef values; equiv to nil
class Undef < AST::Leaf; end
# Host names, either fully qualified or just the short name
class HostName < AST::Leaf
def initialize(hash)
super
unless @value =~ %r{^[0-9a-zA-Z\-]+(\.[0-9a-zA-Z\-]+)*$}
raise Puppet::DevError,
"'%s' is not a valid hostname" % @value
end
end
end
# A simple variable. This object is only used during interpolation;
# the VarDef class is used for assignment.
class Variable < Name
# Looks up the value of the object in the scope tree (does
# not include syntactical constructs, like '$' and '{}').
def evaluate(scope)
parsewrap do
return scope.lookupvar(@value)
end
end
end
end
diff --git a/lib/puppet/parser/ast/resource_override.rb b/lib/puppet/parser/ast/resource_override.rb
index 5c4a2410f..f8cf3a81e 100644
--- a/lib/puppet/parser/ast/resource_override.rb
+++ b/lib/puppet/parser/ast/resource_override.rb
@@ -1,68 +1,68 @@
require 'puppet/parser/ast/resource'
class Puppet::Parser::AST
# Set a parameter on a resource specification created somewhere else in the
# configuration. The object is responsible for verifying that this is allowed.
class ResourceOverride < Resource
associates_doc
attr_accessor :object
attr_reader :params
# Iterate across all of our children.
def each
[@object,@params].flatten.each { |param|
#Puppet.debug("yielding param %s" % param)
yield param
}
end
# Does not actually return an object; instead sets an object
# in the current scope.
def evaluate(scope)
# Get our object reference.
resource = @object.safeevaluate(scope)
hash = {}
# Evaluate all of the specified params.
params = @params.collect { |param|
param.safeevaluate(scope)
}
# Now we just create a normal resource, but we call a very different
# method on the scope.
resource = [resource] unless resource.is_a?(Array)
resource = resource.collect do |r|
res = Puppet::Parser::Resource.new(
:type => r.type,
:title => r.title,
:params => params,
- :file => @file,
- :line => @line,
+ :file => file,
+ :line => line,
:source => scope.source,
:scope => scope
)
# Now we tell the scope that it's an override, and it behaves as
# necessary.
scope.compiler.add_override(res)
res
end
# decapsulate array in case of only one item
return resource.pop if resource.length == 1
return resource
end
# Create our ResourceDef. Handles type checking for us.
def initialize(hash)
@checked = false
super
#self.typecheck(@type.value)
end
end
end
diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb
index 2fdd78ddf..7f0e33359 100644
--- a/lib/puppet/parser/resource.rb
+++ b/lib/puppet/parser/resource.rb
@@ -1,453 +1,457 @@
# A resource that we're managing. This handles making sure that only subclasses
# can set parameters.
class Puppet::Parser::Resource
require 'puppet/parser/resource/param'
require 'puppet/parser/resource/reference'
require 'puppet/util/tagging'
+ require 'puppet/file_collection/lookup'
+
+ include Puppet::FileCollection::Lookup
+
include Puppet::Util
include Puppet::Util::MethodHelper
include Puppet::Util::Errors
include Puppet::Util::Logging
include Puppet::Util::Tagging
- attr_accessor :source, :line, :file, :scope, :rails_id
+ attr_accessor :source, :scope, :rails_id
attr_accessor :virtual, :override, :translated
attr_reader :exported, :evaluated, :params
# Determine whether the provided parameter name is a relationship parameter.
def self.relationship_parameter?(name)
unless defined?(@relationship_names)
@relationship_names = Puppet::Type.relationship_params.collect { |p| p.name }
end
@relationship_names.include?(name)
end
# Proxy a few methods to our @ref object.
[:builtin?, :type, :title].each do |method|
define_method(method) do
@ref.send(method)
end
end
# Set up some boolean test methods
[:exported, :translated, :override, :virtual, :evaluated].each do |method|
newmeth = (method.to_s + "?").intern
define_method(newmeth) do
self.send(method)
end
end
def [](param)
param = symbolize(param)
if param == :title
return self.title
end
if @params.has_key?(param)
@params[param].value
else
nil
end
end
def builtin=(bool)
@ref.builtin = bool
end
# Retrieve the associated definition and evaluate it.
def evaluate
if klass = @ref.definedtype
finish()
return klass.evaluate_code(self)
elsif builtin?
devfail "Cannot evaluate a builtin type"
else
self.fail "Cannot find definition %s" % self.type
end
ensure
@evaluated = true
end
# Mark this resource as both exported and virtual,
# or remove the exported mark.
def exported=(value)
if value
@virtual = true
@exported = value
else
@exported = value
end
end
# Do any finishing work on this object, called before evaluation or
# before storage/translation.
def finish
return if finished?
@finished = true
add_defaults()
add_metaparams()
add_scope_tags()
validate()
end
# Has this resource already been finished?
def finished?
defined?(@finished) and @finished
end
def initialize(options)
# Set all of the options we can.
options.each do |option, value|
if respond_to?(option.to_s + "=")
send(option.to_s + "=", value)
options.delete(option)
end
end
unless self.scope
raise ArgumentError, "Resources require a scope"
end
@source ||= scope.source
options = symbolize_options(options)
# Set up our reference.
if type = options[:type] and title = options[:title]
options.delete(:type)
options.delete(:title)
else
raise ArgumentError, "Resources require a type and title"
end
@ref = Reference.new(:type => type, :title => title, :scope => self.scope)
@params = {}
# Define all of the parameters
if params = options[:params]
options.delete(:params)
params.each do |param|
set_parameter(param)
end
end
# Throw an exception if we've got any arguments left to set.
unless options.empty?
raise ArgumentError, "Resources do not accept %s" % options.keys.collect { |k| k.to_s }.join(", ")
end
tag(@ref.type)
tag(@ref.title) if valid_tag?(@ref.title.to_s)
end
# Is this resource modeling an isomorphic resource type?
def isomorphic?
if builtin?
return @ref.builtintype.isomorphic?
else
return true
end
end
# Merge an override resource in. This will throw exceptions if
# any overrides aren't allowed.
def merge(resource)
# Test the resource scope, to make sure the resource is even allowed
# to override.
unless self.source.object_id == resource.source.object_id || resource.source.child_of?(self.source)
raise Puppet::ParseError.new("Only subclasses can override parameters", resource.line, resource.file)
end
# Some of these might fail, but they'll fail in the way we want.
resource.params.each do |name, param|
override_parameter(param)
end
end
# Modify this resource in the Rails database. Poor design, yo.
def modify_rails(db_resource)
args = rails_args
args.each do |param, value|
db_resource[param] = value unless db_resource[param] == value
end
# Handle file specially
if (self.file and
(!db_resource.file or db_resource.file != self.file))
db_resource.file = self.file
end
updated_params = @params.inject({}) do |hash, ary|
hash[ary[0].to_s] = ary[1]
hash
end
db_resource.ar_hash_merge(db_resource.get_params_hash(), updated_params,
:create => Proc.new { |name, parameter|
parameter.to_rails(db_resource)
}, :delete => Proc.new { |values|
values.each { |value| Puppet::Rails::ParamValue.delete(value['id']) }
}, :modify => Proc.new { |db, mem|
mem.modify_rails_values(db)
})
updated_tags = tags.inject({}) { |hash, tag|
hash[tag] = tag
hash
}
db_resource.ar_hash_merge(db_resource.get_tag_hash(),
updated_tags,
:create => Proc.new { |name, tag|
db_resource.add_resource_tag(name)
}, :delete => Proc.new { |tag|
Puppet::Rails::ResourceTag.delete(tag['id'])
}, :modify => Proc.new { |db, mem|
# nothing here
})
end
# Return the resource name, or the title if no name
# was specified.
def name
unless defined? @name
@name = self[:name] || self.title
end
@name
end
# This *significantly* reduces the number of calls to Puppet.[].
def paramcheck?
unless defined? @@paramcheck
@@paramcheck = Puppet[:paramcheck]
end
@@paramcheck
end
# A temporary occasion, until I get paths in the scopes figured out.
def path
to_s
end
# Return the short version of our name.
def ref
@ref.to_s
end
# Define a parameter in our resource.
def set_parameter(param, value = nil)
if value
param = Puppet::Parser::Resource::Param.new(
:name => param, :value => value, :source => self.source
)
elsif ! param.is_a?(Puppet::Parser::Resource::Param)
raise ArgumentError, "Must pass a parameter or all necessary values"
end
# And store it in our parameter hash.
@params[param.name] = param
end
def to_hash
@params.inject({}) do |hash, ary|
param = ary[1]
# Skip "undef" values.
if param.value != :undef
hash[param.name] = param.value
end
hash
end
end
# Turn our parser resource into a Rails resource.
def to_rails(host)
args = rails_args
db_resource = host.resources.build(args)
# Handle file specially
db_resource.file = self.file
db_resource.save
@params.each { |name, param|
param.to_rails(db_resource)
}
tags.each { |tag| db_resource.add_resource_tag(tag) }
return db_resource
end
def to_s
self.ref
end
# Translate our object to a transportable object.
def to_trans
return nil if virtual?
if builtin?
to_transobject
else
to_transbucket
end
end
def to_transbucket
bucket = Puppet::TransBucket.new([])
bucket.type = self.type
bucket.name = self.title
# TransBuckets don't support parameters, which is why they're being deprecated.
return bucket
end
# Convert this resource to a RAL resource. We hackishly go via the
# transportable stuff.
def to_type
to_trans.to_type
end
def to_transobject
# Now convert to a transobject
obj = Puppet::TransObject.new(@ref.title, @ref.type)
to_hash.each do |p, v|
if v.is_a?(Reference)
v = v.to_ref
elsif v.is_a?(Array)
v = v.collect { |av|
if av.is_a?(Reference)
av = av.to_ref
end
av
}
end
# If the value is an array with only one value, then
# convert it to a single value. This is largely so that
# the database interaction doesn't have to worry about
# whether it returns an array or a string.
obj[p.to_s] = if v.is_a?(Array) and v.length == 1
v[0]
else
v
end
end
obj.file = self.file
obj.line = self.line
obj.tags = self.tags
return obj
end
private
# Add default values from our definition.
def add_defaults
scope.lookupdefaults(self.type).each do |name, param|
unless @params.include?(name)
self.debug "Adding default for %s" % name
@params[name] = param.dup
end
end
end
# Add any metaparams defined in our scope. This actually adds any metaparams
# from any parent scope, and there's currently no way to turn that off.
def add_metaparams
Puppet::Type.eachmetaparam do |name|
# Skip metaparams that we already have defined, unless they're relationship metaparams.
# LAK:NOTE Relationship metaparams get treated specially -- we stack them, instead of
# overriding.
next if @params[name] and not self.class.relationship_parameter?(name)
# Skip metaparams for which we get no value.
next unless val = scope.lookupvar(name.to_s, false) and val != :undefined
# The default case: just set the value
set_parameter(name, val) and next unless @params[name]
# For relationship params, though, join the values (a la #446).
@params[name].value = [@params[name].value, val].flatten
end
end
def add_scope_tags
if scope_resource = scope.resource
tag(*scope_resource.tags)
end
end
# Accept a parameter from an override.
def override_parameter(param)
# This can happen if the override is defining a new parameter, rather
# than replacing an existing one.
(@params[param.name] = param and return) unless current = @params[param.name]
# The parameter is already set. Fail if they're not allowed to override it.
unless param.source.child_of?(current.source)
puts caller if Puppet[:trace]
msg = "Parameter '%s' is already set on %s" % [param.name, self.to_s]
if current.source.to_s != ""
msg += " by %s" % current.source
end
if current.file or current.line
fields = []
fields << current.file if current.file
fields << current.line.to_s if current.line
msg += " at %s" % fields.join(":")
end
msg += "; cannot redefine"
raise Puppet::ParseError.new(msg, param.line, param.file)
end
# If we've gotten this far, we're allowed to override.
# Merge with previous value, if the parameter was generated with the +> syntax.
# It's important that we use the new param instance here, not the old one,
# so that the source is registered correctly for later overrides.
param.value = [current.value, param.value].flatten if param.add
@params[param.name] = param
end
# Verify that all passed parameters are valid. This throws an error if
# there's a problem, so we don't have to worry about the return value.
def paramcheck(param)
param = param.to_s
# Now make sure it's a valid argument to our class. These checks
# are organized in order of commonhood -- most types, it's a valid
# argument and paramcheck is enabled.
if @ref.typeclass.validattr?(param)
true
elsif %w{name title}.include?(param) # always allow these
true
elsif paramcheck?
self.fail Puppet::ParseError, "Invalid parameter '%s' for type '%s'" %
[param, @ref.type]
end
end
def rails_args
return [:type, :title, :line, :exported].inject({}) do |hash, param|
# 'type' isn't a valid column name, so we have to use another name.
to = (param == :type) ? :restype : param
if value = self.send(param)
hash[to] = value
end
hash
end
end
# Make sure the resource's parameters are all valid for the type.
def validate
@params.each do |name, param|
# Make sure it's a valid parameter.
paramcheck(name)
end
end
end
diff --git a/lib/puppet/parser/resource/param.rb b/lib/puppet/parser/resource/param.rb
index 1a5cfe8fb..7ce58f4c4 100644
--- a/lib/puppet/parser/resource/param.rb
+++ b/lib/puppet/parser/resource/param.rb
@@ -1,97 +1,101 @@
+require 'puppet/file_collection/lookup'
+
# The parameters we stick in Resources.
class Puppet::Parser::Resource::Param
- attr_accessor :name, :value, :source, :line, :file, :add
+ attr_accessor :name, :value, :source, :add
include Puppet::Util
include Puppet::Util::Errors
include Puppet::Util::MethodHelper
+ include Puppet::FileCollection::Lookup
+
def initialize(hash)
set_options(hash)
requiredopts(:name, :value, :source)
@name = symbolize(@name)
end
def inspect
"#<#{self.class} @name => #{name}, @value => #{value}, @source => #{source.name}, @line => #{line}>"
end
def line_to_i
return line ? Integer(line) : nil
end
# Make sure an array (or possibly not an array) of values is correctly
# set up for Rails. The main thing is that Resource::Reference objects
# should stay objects, so they just get serialized.
def munge_for_rails(values)
values = value.is_a?(Array) ? value : [value]
values.map do |v|
if v.is_a?(Puppet::Parser::Resource::Reference)
v
else
v.to_s
end
end
end
# Store a new parameter in a Rails db.
def to_rails(db_resource)
values = munge_for_rails(value)
param_name = Puppet::Rails::ParamName.find_or_create_by_name(self.name.to_s)
line_number = line_to_i()
return values.collect do |v|
db_resource.param_values.create(:value => v,
:line => line_number,
:param_name => param_name)
end
end
def modify_rails_values(db_values)
#dev_warn if db_values.nil? || db_values.empty?
values_to_remove(db_values).each { |remove_me|
Puppet::Rails::ParamValue.delete(remove_me['id'])
}
line_number = line_to_i()
db_param_name = db_values[0]['param_name_id']
values_to_add(db_values).each { |add_me|
Puppet::Rails::ParamValue.create(:value => add_me,
:line => line_number,
:param_name_id => db_param_name,
:resource_id => db_values[0]['resource_id'] )
}
end
def to_s
"%s => %s" % [self.name, self.value]
end
def compare(v,db_value)
if (v.is_a?(Puppet::Parser::Resource::Reference))
return v.to_s == db_value.to_s
else
return v == db_value
end
end
def values_to_remove(db_values)
values = munge_for_rails(value)
line_number = line_to_i()
db_values.collect do |db|
db unless (db['line'] == line_number &&
values.find { |v|
compare(v,db['value'])
} )
end.compact
end
def values_to_add(db_values)
values = munge_for_rails(value)
line_number = line_to_i()
values.collect do |v|
v unless db_values.find { |db| (compare(v,db['value']) &&
line_number == db['line']) }
end.compact
end
end
diff --git a/lib/puppet/parser/resource/reference.rb b/lib/puppet/parser/resource/reference.rb
index cb505d606..bffc03788 100644
--- a/lib/puppet/parser/resource/reference.rb
+++ b/lib/puppet/parser/resource/reference.rb
@@ -1,90 +1,92 @@
require 'puppet/resource_reference'
+require 'puppet/file_collection/lookup'
# A reference to a resource. Mostly just the type and title.
class Puppet::Parser::Resource::Reference < Puppet::ResourceReference
+ include Puppet::FileCollection::Lookup
include Puppet::Util::MethodHelper
include Puppet::Util::Errors
attr_accessor :builtin, :file, :line, :scope
# Are we a builtin type?
def builtin?
unless defined? @builtin
if builtintype()
@builtin = true
else
@builtin = false
end
end
@builtin
end
def builtintype
if t = Puppet::Type.type(self.type.downcase) and t.name != :component
t
else
nil
end
end
# Return the defined type for our obj. This can return classes,
# definitions or nodes.
def definedtype
unless defined? @definedtype
case self.type
when "Class": # look for host classes
if self.title == :main
tmp = @scope.findclass("")
else
unless tmp = @scope.parser.classes[self.title]
fail Puppet::ParseError, "Could not find class '%s'" % self.title
end
end
when "Node": # look for node definitions
unless tmp = @scope.parser.nodes[self.title]
fail Puppet::ParseError, "Could not find node '%s'" % self.title
end
else # normal definitions
# The resource type is capitalized, so we have to downcase. Really,
# we should have a better interface for finding these, but eh.
tmp = @scope.parser.definitions[self.type.downcase]
end
if tmp
@definedtype = tmp
else
fail Puppet::ParseError, "Could not find resource type '%s'" % self.type
end
end
@definedtype
end
def initialize(hash)
set_options(hash)
requiredopts(:type, :title)
end
def to_ref
# We have to return different cases to provide backward compatibility
# from 0.24.x to 0.23.x.
if builtin?
return [type.to_s.downcase, title.to_s]
else
return [type.to_s, title.to_s]
end
end
def typeclass
unless defined? @typeclass
if tmp = builtintype || definedtype
@typeclass = tmp
else
fail Puppet::ParseError, "Could not find type %s" % self.type
end
end
@typeclass
end
end
diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb
index b57c74b95..5cb4ce13d 100644
--- a/lib/puppet/type.rb
+++ b/lib/puppet/type.rb
@@ -1,2512 +1,2512 @@
require 'puppet'
require 'puppet/util/log'
require 'puppet/util/metric'
require 'puppet/property'
require 'puppet/parameter'
require 'puppet/util'
require 'puppet/util/autoload'
require 'puppet/metatype/manager'
require 'puppet/util/errors'
require 'puppet/util/log_paths'
require 'puppet/util/logging'
require 'puppet/resource_reference'
+require 'puppet/file_collection/lookup'
# see the bottom of the file for the rest of the inclusions
module Puppet
class Type
include Puppet::Util
include Puppet::Util::Errors
include Puppet::Util::LogPaths
include Puppet::Util::Logging
+ include Puppet::FileCollection::Lookup
###############################
# Code related to resource type attributes.
class << self
include Puppet::Util::ClassGen
include Puppet::Util::Warnings
attr_reader :properties
end
def self.states
warnonce "The states method is deprecated; use properties"
properties()
end
# All parameters, in the appropriate order. The namevar comes first,
# then the properties, 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
# Cache this, since it gets called so many times
namevar = self.namevar
order = [namevar]
if self.parameters.include?(:provider)
order << :provider
end
order << [self.properties.collect { |property| property.name },
self.parameters - [:provider],
self.metaparams].flatten.reject { |param|
# we don't want our namevar in there multiple times
param == namevar
}
order.flatten!
return order
end
# Retrieve an attribute alias, if there is one.
def self.attr_alias(param)
@attr_aliases[symbolize(param)]
end
# Create an alias to an existing attribute. This will cause the aliased
# attribute to be valid when setting and retrieving values on the instance.
def self.set_attr_alias(hash)
hash.each do |new, old|
@attr_aliases[symbolize(new)] = symbolize(old)
end
end
# Find the class associated with any given attribute.
def self.attrclass(name)
@attrclasses ||= {}
# We cache the value, since this method gets called such a huge number
# of times (as in, hundreds of thousands in a given run).
unless @attrclasses.include?(name)
@attrclasses[name] = case self.attrtype(name)
when :property: @validproperties[name]
when :meta: @@metaparamhash[name]
when :param: @paramhash[name]
end
end
@attrclasses[name]
end
# What type of parameter are we dealing with? Cache the results, because
# this method gets called so many times.
def self.attrtype(attr)
@attrtypes ||= {}
unless @attrtypes.include?(attr)
@attrtypes[attr] = case
when @validproperties.include?(attr): :property
when @paramhash.include?(attr): :param
when @@metaparamhash.include?(attr): :meta
else
raise Puppet::DevError,
"Invalid attribute '%s' for class '%s'" %
[attr, self.name]
end
end
@attrtypes[attr]
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 << param
@parameters.each { |p| @paramhash[name] = p }
if param.isnamevar?
@namevar = param.name
end
end
# A similar function but one that yields the class and type.
# This is mainly so that setdefaults doesn't call quite so many functions.
def self.eachattr(*ary)
if ary.empty?
ary = nil
end
# We have to do this in a specific order, so that defaults are
# created in that order (e.g., providers should be set up before
# anything else).
allattrs.each do |name|
next unless ary.nil? or ary.include?(name)
if obj = @properties.find { |p| p.name == name }
yield obj, :property
elsif obj = @parameters.find { |p| p.name == name }
yield obj, :param
elsif obj = @@metaparams.find { |p| p.name == name }
yield obj, :meta
else
raise Puppet::DevError, "Could not find parameter %s" % name
end
end
end
def self.eachmetaparam
@@metaparams.each { |p| yield p.name }
end
# Create the 'ensure' class. This is a separate method so other types
# can easily call it and create their own 'ensure' values.
def self.ensurable(&block)
if block_given?
self.newproperty(:ensure, :parent => Puppet::Property::Ensure, &block)
else
self.newproperty(:ensure, :parent => Puppet::Property::Ensure) do
self.defaultvalues
end
end
end
# Should we add the 'ensure' property to this class?
def self.ensurable?
# If the class has all three of these methods defined, then it's
# ensurable.
ens = [:exists?, :create, :destroy].inject { |set, method|
set &&= self.public_method_defined?(method)
}
return ens
end
# Deal with any options passed into parameters.
def self.handle_param_options(name, options)
# If it's a boolean parameter, create a method to test the value easily
if options[:boolean]
define_method(name.to_s + "?") do
val = self[name]
if val == :true or val == true
return true
end
end
end
# If this param handles relationships, store that information
end
# Is the parameter in question a meta-parameter?
def self.metaparam?(param)
@@metaparamhash.include?(symbolize(param))
end
# Find the metaparameter class associated with a given metaparameter name.
def self.metaparamclass(name)
@@metaparamhash[symbolize(name)]
end
def self.metaparams
@@metaparams.collect { |param| param.name }
end
def self.metaparamdoc(metaparam)
@@metaparamhash[metaparam].doc
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, options = {}, &block)
@@metaparams ||= []
@@metaparamhash ||= {}
name = symbolize(name)
param = genclass(name,
:parent => options[:parent] || Puppet::Parameter,
:prefix => "MetaParam",
:hash => @@metaparamhash,
:array => @@metaparams,
:attributes => options[:attributes],
&block
)
# Grr.
if options[:required_features]
param.required_features = options[:required_features]
end
handle_param_options(name, options)
param.metaparam = true
return param
end
# Find the namevar
def self.namevar
unless defined? @namevar
params = @parameters.find_all { |param|
param.isnamevar? or param.name == :name
}
if params.length > 1
raise Puppet::DevError, "Found multiple namevars for %s" % self.name
elsif params.length == 1
@namevar = params[0].name
else
raise Puppet::DevError, "No namevar for %s" % self.name
end
end
@namevar
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, options = {}, &block)
options[:attributes] ||= {}
param = genclass(name,
:parent => options[:parent] || Puppet::Parameter,
:attributes => options[:attributes],
:block => block,
:prefix => "Parameter",
:array => @parameters,
:hash => @paramhash
)
handle_param_options(name, options)
# Grr.
if options[:required_features]
param.required_features = options[:required_features]
end
param.isnamevar if options[:namevar]
# These might be enabled later.
# define_method(name) do
# @parameters[name].value
# end
#
# define_method(name.to_s + "=") do |value|
# newparam(param, value)
# end
if param.isnamevar?
@namevar = param.name
end
return param
end
def self.newstate(name, options = {}, &block)
Puppet.warning "newstate() has been deprecrated; use newproperty(%s)" %
name
newproperty(name, options, &block)
end
# Create a new property. The first parameter must be the name of the property;
# this is how users will refer to the property when creating new instances.
# The second parameter is a hash of options; the options are:
# * :parent: The parent class for the property. Defaults to Puppet::Property.
# * :retrieve: The method to call on the provider or @parent object (if
# the provider is not set) to retrieve the current value.
def self.newproperty(name, options = {}, &block)
name = symbolize(name)
# This is here for types that might still have the old method of defining
# a parent class.
unless options.is_a? Hash
raise Puppet::DevError,
"Options must be a hash, not %s" % options.inspect
end
if @validproperties.include?(name)
raise Puppet::DevError, "Class %s already has a property named %s" %
[self.name, name]
end
if parent = options[:parent]
options.delete(:parent)
else
parent = Puppet::Property
end
# We have to create our own, new block here because we want to define
# an initial :retrieve method, if told to, and then eval the passed
# block if available.
prop = genclass(name, :parent => parent, :hash => @validproperties, :attributes => options) do
# If they've passed a retrieve method, then override the retrieve
# method on the class.
if options[:retrieve]
define_method(:retrieve) do
provider.send(options[:retrieve])
end
end
if block
class_eval(&block)
end
end
# If it's the 'ensure' property, always put it first.
if name == :ensure
@properties.unshift prop
else
@properties << prop
end
# define_method(name) do
# @parameters[name].should
# end
#
# define_method(name.to_s + "=") do |value|
# newproperty(name, :should => value)
# end
return prop
end
def self.paramdoc(param)
@paramhash[param].doc
end
# Return the parameter names
def self.parameters
return [] unless defined? @parameters
@parameters.collect { |klass| klass.name }
end
# Find the parameter class associated with a given parameter name.
def self.paramclass(name)
@paramhash[name]
end
# Return the property class associated with a name
def self.propertybyname(name)
@validproperties[name]
end
def self.validattr?(name)
name = symbolize(name)
return true if name == :name
@validattrs ||= {}
unless @validattrs.include?(name)
if self.validproperty?(name) or self.validparameter?(name) or self.metaparam?(name)
@validattrs[name] = true
else
@validattrs[name] = false
end
end
@validattrs[name]
end
# does the name reflect a valid property?
def self.validproperty?(name)
name = symbolize(name)
if @validproperties.include?(name)
return @validproperties[name]
else
return false
end
end
# Return the list of validproperties
def self.validproperties
return {} unless defined? @parameters
return @validproperties.keys
end
# does the name reflect a valid parameter?
def self.validparameter?(name)
unless defined? @parameters
raise Puppet::DevError, "Class %s has not defined parameters" % self
end
if @paramhash.include?(name) or @@metaparamhash.include?(name)
return true
else
return false
end
end
# fix any namevar => param translations
def argclean(oldhash)
# This duplication is here because it might be a transobject.
hash = oldhash.dup.to_hash
if hash.include?(:resource)
hash.delete(:resource)
end
namevar = self.class.namevar
# Do a simple translation for those cases where they've passed :name
# but that's not our namevar
if hash.include? :name and namevar != :name
if hash.include? namevar
raise ArgumentError, "Cannot provide both name and %s" % namevar
end
hash[namevar] = hash[:name]
hash.delete(:name)
end
# Make sure we have a name, one way or another
unless hash.include? namevar
if defined? @title and @title
hash[namevar] = @title
else
raise Puppet::Error, "Was not passed a namevar or title"
end
end
return hash
end
# Return either the attribute alias or the attribute.
def attr_alias(name)
name = symbolize(name)
if synonym = self.class.attr_alias(name)
return synonym
else
return name
end
end
# Are we deleting this resource?
def deleting?
obj = @parameters[:ensure] and obj.should == :absent
end
# Create a new property if it is valid but doesn't exist
# Returns: true if a new parameter was added, false otherwise
def add_property_parameter(prop_name)
if self.class.validproperty?(prop_name) && !@parameters[prop_name]
self.newattr(prop_name)
return true
end
return false
end
# abstract accessing parameters and properties, and normalize
# access to always be symbols, not strings
# This returns a value, not an object. It returns the 'is'
# value, but you can also specifically return 'is' and 'should'
# values using 'object.is(:property)' or 'object.should(:property)'.
def [](name)
name = attr_alias(name)
unless self.class.validattr?(name)
raise TypeError.new("Invalid parameter %s(%s)" % [name, name.inspect])
end
if name == :name
name = self.class.namevar
end
if obj = @parameters[name]
# Note that if this is a property, then the value is the "should" value,
# not the current value.
obj.value
else
return nil
end
end
# Abstract setting parameters and properties, and normalize
# access to always be symbols, not strings. This sets the 'should'
# value on properties, and otherwise just sets the appropriate parameter.
def []=(name,value)
name = attr_alias(name)
unless self.class.validattr?(name)
raise TypeError.new("Invalid parameter %s" % [name])
end
if name == :name
name = self.class.namevar
end
if value.nil?
raise Puppet::Error.new("Got nil value for %s" % name)
end
if obj = @parameters[name]
obj.value = value
return nil
else
self.newattr(name, :value => value)
end
nil
end
# remove a property from the object; useful in testing or in cleanup
# when an error has been encountered
def delete(attr)
attr = symbolize(attr)
if @parameters.has_key?(attr)
@parameters.delete(attr)
else
raise Puppet::DevError.new("Undefined attribute '#{attr}' in #{self}")
end
end
# iterate across the existing properties
def eachproperty
# properties() is a private method
properties().each { |property|
yield property
}
end
# retrieve the 'should' value for a specified property
def should(name)
name = attr_alias(name)
if prop = @parameters[name] and prop.is_a?(Puppet::Property)
return prop.should
else
return nil
end
end
# Create the actual attribute instance. Requires either the attribute
# name or class as the first argument, then an optional hash of
# attributes to set during initialization.
def newattr(name, options = {})
if name.is_a?(Class)
klass = name
name = klass.name
end
unless klass = self.class.attrclass(name)
raise Puppet::Error, "Resource type %s does not support parameter %s" % [self.class.name, name]
end
if @parameters.include?(name)
raise Puppet::Error, "Parameter '%s' is already defined in %s" %
[name, self.ref]
end
if provider and ! provider.class.supports_parameter?(klass)
missing = klass.required_features.find_all { |f| ! provider.class.feature?(f) }
info "Provider %s does not support features %s; not managing attribute %s" % [provider.class.name, missing.join(", "), name]
return nil
end
# Add resource information at creation time, so it's available
# during validation.
options[:resource] = self
begin
# make sure the parameter doesn't have any errors
return @parameters[name] = klass.new(options)
rescue => detail
error = Puppet::Error.new("Parameter %s failed: %s" %
[name, detail])
error.set_backtrace(detail.backtrace)
raise error
end
end
# return the value of a parameter
def parameter(name)
unless name.is_a? Symbol
name = name.intern
end
return @parameters[name].value
end
# Is the named property defined?
def propertydefined?(name)
unless name.is_a? Symbol
name = name.intern
end
return @parameters.include?(name)
end
# return an actual type by name; to return the value, use 'inst[name]'
# FIXME this method should go away
def property(name)
if obj = @parameters[symbolize(name)] and obj.is_a?(Puppet::Property)
return obj
else
return nil
end
end
# def set(name, value)
# send(name.to_s + "=", value)
# end
#
# def get(name)
# send(name)
# end
# For any parameters or properties that have defaults and have not yet been
# set, set them now. This method can be handed a list of attributes,
# and if so it will only set defaults for those attributes.
def setdefaults(*ary)
#self.class.eachattr(*ary) { |klass, type|
self.class.eachattr(*ary) { |klass, type|
# not many attributes will have defaults defined, so we short-circuit
# those away
next unless klass.method_defined?(:default)
next if @parameters[klass.name]
next unless obj = self.newattr(klass)
# We have to check for nil values, not "truth", so we allow defaults
# to false.
value = obj.default and ! value.nil?
if ! value.nil?
obj.value = value
else
@parameters.delete(obj.name)
end
}
end
# Convert our object to a hash. This just includes properties.
def to_hash
rethash = {}
@parameters.each do |name, obj|
rethash[name] = obj.value
end
rethash
end
# Return a specific value for an attribute.
def value(name)
name = attr_alias(name)
if obj = @parameters[name] and obj.respond_to?(:value)
return obj.value
else
return nil
end
end
# Meta-parameter methods: These methods deal with the results
# of specifying metaparameters
private
# Return all of the property objects, in the order specified in the
# class.
def properties
#debug "%s has %s properties" % [self,@parameters.length]
props = self.class.properties.collect { |prop|
@parameters[prop.name]
}.find_all { |p|
! p.nil?
}.each do |prop|
unless prop.is_a?(Puppet::Property)
raise Puppet::DevError, "got a non-property %s(%s)" %
[prop.class, prop.class.name]
end
end
props
end
public
###############################
# Code related to the closure-like behaviour of the resource classes.
attr_writer :implicit
# Is this type's name isomorphic with the object? That is, if the
# name conflicts, does it necessarily mean that the objects conflict?
# Defaults to true.
def self.isomorphic?
if defined? @isomorphic
return @isomorphic
else
return true
end
end
def implicit?
if defined? @implicit and @implicit
return true
else
return false
end
end
def isomorphic?
self.class.isomorphic?
end
# is the instance a managed instance? A 'yes' here means that
# the instance was created from the language, vs. being created
# in order resolve other questions, such as finding a package
# in a list
def managed?
# Once an object is managed, it always stays managed; but an object
# that is listed as unmanaged might become managed later in the process,
# so we have to check that every time
if defined? @managed and @managed
return @managed
else
@managed = false
properties.each { |property|
s = property.should
if s and ! property.class.unmanaged
@managed = true
break
end
}
return @managed
end
end
###############################
# Code related to the container behaviour.
def self.depthfirst?
if defined? @depthfirst
return @depthfirst
else
return false
end
end
def depthfirst?
self.class.depthfirst?
end
# Add a hook for testing for recursion.
def parentof?(child)
if (self == child)
debug "parent is equal to child"
return true
elsif defined? @parent and @parent.parentof?(child)
debug "My parent is parent of child"
return true
else
return false
end
end
# Remove an object. The argument determines whether the object's
# subscriptions get eliminated, too.
def remove(rmdeps = true)
# This is hackish (mmm, cut and paste), but it works for now, and it's
# better than warnings.
@parameters.each do |name, obj|
obj.remove
end
@parameters.clear
self.class.delete(self)
@parent = nil
# Remove the reference to the provider.
if self.provider
@provider.clear
@provider = nil
end
end
###############################
# Code related to evaluating the resources.
# This method is responsible for collecting property changes we always
# descend into the children before we evaluate our current properties.
# This returns any changes resulting from testing, thus 'collect' rather
# than 'each'.
def evaluate
if self.provider.is_a?(Puppet::Provider)
unless provider.class.suitable?
raise Puppet::Error, "Provider %s is not functional on this platform" % provider.class.name
end
end
#Puppet.err "Evaluating %s" % self.path.join(":")
unless defined? @evalcount
self.err "No evalcount defined on '%s' of type '%s'" %
[self.title,self.class]
@evalcount = 0
end
@evalcount += 1
# this only operates on properties, not properties + children
# it's important that we call retrieve() on the type instance,
# not directly on the property, because it allows the type to override
# the method, like pfile does
currentvalues = self.retrieve
changes = propertychanges(currentvalues).flatten
# now record how many changes we've resulted in
if changes.length > 0
self.debug "%s change(s)" %
[changes.length]
end
# If we're in noop mode, we don't want to store the checked time,
# because it will result in the resource not getting scheduled if
# someone were to apply the catalog in non-noop mode.
# We're going to go ahead and record that we checked if there were
# no changes, since it's unlikely it will affect the scheduling.
noop = noop?
if ! noop or (noop && changes.length == 0)
self.cache(:checked, Time.now)
end
return changes.flatten
end
# Flush the provider, if it supports it. This is called by the
# transaction.
def flush
if self.provider and self.provider.respond_to?(:flush)
self.provider.flush
end
end
# if all contained objects are in sync, then we're in sync
# FIXME I don't think this is used on the type instances any more,
# it's really only used for testing
def insync?(is)
insync = true
if property = @parameters[:ensure]
unless is.include? property
raise Puppet::DevError,
"The is value is not in the is array for '%s'" %
[property.name]
end
ensureis = is[property]
if property.insync?(ensureis) and property.should == :absent
return true
end
end
properties.each { |property|
unless is.include? property
raise Puppet::DevError,
"The is value is not in the is array for '%s'" %
[property.name]
end
propis = is[property]
unless property.insync?(propis)
property.debug("Not in sync: %s vs %s" %
[propis.inspect, property.should.inspect])
insync = false
#else
# property.debug("In sync")
end
}
#self.debug("%s sync status is %s" % [self,insync])
return insync
end
# retrieve the current value of all contained properties
def retrieve
return currentpropvalues
end
# Get a hash of the current properties. Returns a hash with
# the actual property instance as the key and the current value
# as the, um, value.
def currentpropvalues
# It's important to use the 'properties' method here, as it follows the order
# in which they're defined in the class. It also guarantees that 'ensure'
# is the first property, which is important for skipping 'retrieve' on
# all the properties if the resource is absent.
ensure_state = false
return properties().inject({}) do | prophash, property|
if property.name == :ensure
ensure_state = property.retrieve
prophash[property] = ensure_state
else
if ensure_state == :absent
prophash[property] = :absent
else
prophash[property] = property.retrieve
end
end
prophash
end
end
# Are we running in noop mode?
def noop?
if defined?(@noop)
@noop
else
Puppet[:noop]
end
end
def noop
noop?
end
# Retrieve the changes associated with all of the properties.
def propertychanges(currentvalues)
# If we are changing the existence of the object, then none of
# the other properties matter.
changes = []
ensureparam = @parameters[:ensure]
# This allows resource types to have 'ensure' be a parameter, which allows them to
# just pass the parameter on to other generated resources.
ensureparam = nil unless ensureparam.is_a?(Puppet::Property)
if ensureparam && !currentvalues.include?(ensureparam)
raise Puppet::DevError, "Parameter ensure defined but missing from current values"
end
if ensureparam and ! ensureparam.insync?(currentvalues[ensureparam])
changes << Puppet::Transaction::Change.new(ensureparam, currentvalues[ensureparam])
# Else, if the 'ensure' property is correctly absent, then do
# nothing
elsif ensureparam and currentvalues[ensureparam] == :absent
return []
else
changes = properties().find_all { |property|
currentvalues[property] ||= :absent
! property.insync?(currentvalues[property])
}.collect { |property|
Puppet::Transaction::Change.new(property, currentvalues[property])
}
end
if Puppet[:debug] and changes.length > 0
self.debug("Changing " + changes.collect { |ch| ch.property.name }.join(","))
end
changes
end
###############################
# Code related to managing resource instances.
require 'puppet/transportable'
# Make 'new' private, so people have to use create instead.
class << self
private :new
end
# retrieve a named instance of the current type
def self.[](name)
@objects[name] || @aliases[name]
end
# add an instance by name to the class list of instances
def self.[]=(name,object)
newobj = nil
if object.is_a?(Puppet::Type)
newobj = object
else
raise Puppet::DevError, "must pass a Puppet::Type object"
end
if exobj = @objects[name] and self.isomorphic?
msg = "Object '%s[%s]' already exists" %
[newobj.class.name, name]
if exobj.file and exobj.line
msg += ("in file %s at line %s" %
[object.file, object.line])
end
if object.file and object.line
msg += ("and cannot be redefined in file %s at line %s" %
[object.file, object.line])
end
error = Puppet::Error.new(msg)
raise error
else
#Puppet.info("adding %s of type %s to class list" %
# [name,object.class])
@objects[name] = newobj
end
end
# Create an alias. We keep these in a separate hash so that we don't encounter
# the objects multiple times when iterating over them.
def self.alias(name, obj)
if @objects.include?(name)
unless @objects[name] == obj
raise Puppet::Error.new(
"Cannot create alias %s: object already exists" %
[name]
)
end
end
if @aliases.include?(name)
unless @aliases[name] == obj
raise Puppet::Error.new(
"Object %s already has alias %s" %
[@aliases[name].name, name]
)
end
end
@aliases[name] = obj
end
# remove all of the instances of a single type
def self.clear
if defined? @objects
@objects.each do |name, obj|
obj.remove(true)
end
@objects.clear
end
if defined? @aliases
@aliases.clear
end
end
# Force users to call this, so that we can merge objects if
# necessary.
def self.create(args)
# Don't modify the original hash; instead, create a duplicate and modify it.
# We have to dup and use the ! so that it stays a TransObject if it is
# one.
hash = args.dup
symbolizehash!(hash)
# If we're the base class, then pass the info on appropriately
if self == Puppet::Type
type = nil
if hash.is_a? Puppet::TransObject
type = hash.type
else
# If we're using the type to determine object type, then delete it
if type = hash[:type]
hash.delete(:type)
end
end
# If they've specified a type and called on the base, then
# delegate to the subclass.
if type
if typeklass = self.type(type)
return typeklass.create(hash)
else
raise Puppet::Error, "Unknown type %s" % type
end
else
raise Puppet::Error, "No type found for %s" % hash.inspect
end
end
# Handle this new object being implicit
implicit = hash[:implicit] || false
if hash.include?(:implicit)
hash.delete(:implicit)
end
name = nil
unless hash.is_a? Puppet::TransObject
hash = self.hash2trans(hash)
end
# XXX This will have to change when transobjects change to using titles
title = hash.name
# if the object already exists
if self.isomorphic? and retobj = self[title]
# if only one of our objects is implicit, then it's easy to see
# who wins -- the non-implicit one.
if retobj.implicit? and ! implicit
Puppet.notice "Removing implicit %s" % retobj.title
# Remove all of the objects, but do not remove their subscriptions.
retobj.remove(false)
# now pass through and create the new object
elsif implicit
Puppet.debug "Ignoring implicit %s[%s]" % [self.name, title]
return nil
else
raise Puppet::Error, "%s is already being managed" % retobj.ref
end
end
# create it anew
# if there's a failure, destroy the object if it got that far, but raise
# the error.
begin
obj = new(hash)
rescue => detail
Puppet.err "Could not create %s: %s" % [title, detail.to_s]
if obj
obj.remove(true)
elsif obj = self[title]
obj.remove(true)
end
raise
end
if implicit
obj.implicit = true
end
# Store the object by title
self[obj.title] = obj
return obj
end
# remove a specified object
def self.delete(resource)
return unless defined? @objects
if @objects.include?(resource.title)
@objects.delete(resource.title)
end
if @aliases.include?(resource.title)
@aliases.delete(resource.title)
end
if @aliases.has_value?(resource)
names = []
@aliases.each do |name, otherres|
if otherres == resource
names << name
end
end
names.each { |name| @aliases.delete(name) }
end
end
# iterate across each of the type's instances
def self.each
return unless defined? @objects
@objects.each { |name,instance|
yield instance
}
end
# does the type have an object with the given name?
def self.has_key?(name)
return @objects.has_key?(name)
end
# Convert a hash to a TransObject.
def self.hash2trans(hash)
title = nil
if hash.include? :title
title = hash[:title]
hash.delete(:title)
elsif hash.include? self.namevar
title = hash[self.namevar]
hash.delete(self.namevar)
if hash.include? :name
raise ArgumentError, "Cannot provide both name and %s to %s" %
[self.namevar, self.name]
end
elsif hash[:name]
title = hash[:name]
hash.delete :name
end
if catalog = hash[:catalog]
hash.delete(:catalog)
end
raise(Puppet::Error, "You must specify a title for objects of type %s" % self.to_s) unless title
if hash.include? :type
unless self.validattr? :type
hash.delete :type
end
end
# okay, now make a transobject out of hash
begin
trans = Puppet::TransObject.new(title, self.name.to_s)
trans.catalog = catalog if catalog
hash.each { |param, value|
trans[param] = value
}
rescue => detail
raise Puppet::Error, "Could not create %s: %s" %
[name, detail]
end
return trans
end
# Retrieve all known instances. Either requires providers or must be overridden.
def self.instances
unless defined?(@providers) and ! @providers.empty?
raise Puppet::DevError, "%s has no providers and has not overridden 'instances'" % self.name
end
# Put the default provider first, then the rest of the suitable providers.
provider_instances = {}
providers_by_source.collect do |provider|
provider.instances.collect do |instance|
# First try to get the resource if it already exists
# Skip instances that map to a managed resource with a different provider
next if resource = self[instance.name] and resource.provider.class != instance.class
# We always want to use the "first" provider instance we find, unless the resource
# is already managed and has a different provider set
if other = provider_instances[instance.name]
Puppet.warning "%s %s found in both %s and %s; skipping the %s version" %
[self.name.to_s.capitalize, instance.name, other.class.name, instance.class.name, instance.class.name]
next
end
provider_instances[instance.name] = instance
if resource
resource.provider = instance
resource
else
create(:name => instance.name, :provider => instance, :check => :all)
end
end
end.flatten.compact
end
# Return a list of one suitable provider per source, with the default provider first.
def self.providers_by_source
# Put the default provider first, then the rest of the suitable providers.
sources = []
[defaultprovider, suitableprovider].flatten.uniq.collect do |provider|
next if sources.include?(provider.source)
sources << provider.source
provider
end.compact
end
# Create the path for logging and such.
def pathbuilder
if p = parent
[p.pathbuilder, self.ref].flatten
else
[self.ref]
end
end
###############################
# Add all of the meta parameters.
newmetaparam(:noop) do
desc "Boolean flag indicating whether work should actually
be done."
newvalues(:true, :false)
munge do |value|
case value
when true, :true, "true": @resource.noop = true
when false, :false, "false": @resource.noop = false
end
end
end
newmetaparam(:schedule) do
desc "On what schedule the object should be managed. You must create a
schedule object, and then reference the name of that object to use
that for your schedule::
schedule { daily:
period => daily,
range => \"2-4\"
}
exec { \"/usr/bin/apt-get update\":
schedule => daily
}
The creation of the schedule object does not need to appear in the
configuration before objects that use it."
end
newmetaparam(:check) do
desc "Propertys 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, so that you
could specify that you wanted to check the install state of all
packages, and then query the Puppet client daemon to get reports
on all packages."
munge do |args|
# If they've specified all, collect all known properties
if args == :all
args = @resource.class.properties.find_all do |property|
# Only get properties supported by our provider
if @resource.provider
@resource.provider.class.supports_parameter?(property)
else
true
end
end.collect do |property|
property.name
end
end
unless args.is_a?(Array)
args = [args]
end
unless defined? @resource
self.devfail "No parent for %s, %s?" %
[self.class, self.name]
end
args.each { |property|
unless property.is_a?(Symbol)
property = property.intern
end
next if @resource.propertydefined?(property)
unless propertyklass = @resource.class.validproperty?(property)
if @resource.class.validattr?(property)
next
else
raise Puppet::Error, "%s is not a valid attribute for %s" %
[property, self.class.name]
end
end
next unless propertyklass.checkable?
@resource.newattr(property)
}
end
end
# We've got four relationship metaparameters, so this method is used
# to reduce code duplication between them.
def munge_relationship(param, values)
# We need to support values passed in as an array or as a
# resource reference.
result = []
# 'values' could be an array or a reference. If it's an array,
# it could be an array of references or an array of arrays.
if values.is_a?(Puppet::Type)
result << [values.class.name, values.title]
else
unless values.is_a?(Array)
devfail "Relationships must be resource references"
end
if values[0].is_a?(String) or values[0].is_a?(Symbol)
# we're a type/title array reference
values[0] = symbolize(values[0])
result << values
else
# we're an array of stuff
values.each do |value|
if value.is_a?(Puppet::Type)
result << [value.class.name, value.title]
elsif value.is_a?(Array)
value[0] = symbolize(value[0])
result << value
else
devfail "Invalid relationship %s" % value.inspect
end
end
end
end
if existing = self[param]
result = existing + result
end
result
end
newmetaparam(:loglevel) do
desc "Sets the level that information will be logged.
The log levels have the biggest impact when logs are sent to
syslog (which is currently the default)."
defaultto :notice
newvalues(*Puppet::Util::Log.levels)
newvalues(:verbose)
munge do |loglevel|
val = super(loglevel)
if val == :verbose
val = :info
end
val
end
end
newmetaparam(:alias) do
desc "Creates an alias for the object. Puppet uses this internally when you
provide a symbolic name::
file { sshdconfig:
path => $operatingsystem ? {
solaris => \"/usr/local/etc/ssh/sshd_config\",
default => \"/etc/ssh/sshd_config\"
},
source => \"...\"
}
service { sshd:
subscribe => file[sshdconfig]
}
When you use this feature, the parser sets ``sshdconfig`` as the name,
and the library sets that as an alias for the file so the dependency
lookup for ``sshd`` works. You can use this parameter yourself,
but note that only the library can use these aliases; for instance,
the following code will not work::
file { \"/etc/ssh/sshd_config\":
owner => root,
group => root,
alias => sshdconfig
}
file { sshdconfig:
mode => 644
}
There's no way here for the Puppet parser to know that these two stanzas
should be affecting the same file.
See the `LanguageTutorial language tutorial`:trac: for more information.
"
munge do |aliases|
unless aliases.is_a?(Array)
aliases = [aliases]
end
raise(ArgumentError, "Cannot add aliases without a catalog") unless @resource.catalog
aliases.each do |other|
if obj = @resource.catalog.resource(@resource.class.name, other)
unless obj.object_id == @resource.object_id
self.fail("%s can not create alias %s: object already exists" % [@resource.title, other])
end
next
end
# LAK:FIXME Old-school, add the alias to the class.
@resource.class.alias(other, @resource)
# Newschool, add it to the catalog.
@resource.catalog.alias(@resource, other)
end
end
end
newmetaparam(:tag) do
desc "Add the specified tags to the associated resource. While all resources
are automatically tagged with as much information as possible
(e.g., each class and definition containing the resource), it can
be useful to add your own tags to a given resource.
Tags are currently useful for things like applying a subset of a
host's configuration::
puppetd --test --tags mytag
This way, when you're testing a configuration you can run just the
portion you're testing."
munge do |tags|
tags = [tags] unless tags.is_a? Array
tags.each do |tag|
@resource.tag(tag)
end
end
end
class RelationshipMetaparam < Puppet::Parameter
class << self
attr_accessor :direction, :events, :callback, :subclasses
end
@subclasses = []
def self.inherited(sub)
@subclasses << sub
end
def munge(rels)
@resource.munge_relationship(self.class.name, rels)
end
def validate_relationship
@value.each do |value|
unless @resource.catalog.resource(*value)
description = self.class.direction == :in ? "dependency" : "dependent"
fail Puppet::Error, "Could not find %s %s[%s] for %s" %
[description, value[0].to_s.capitalize, value[1], resource.ref]
end
end
end
# Create edges from each of our relationships. :in
# relationships are specified by the event-receivers, and :out
# relationships are specified by the event generator. This
# way 'source' and 'target' are consistent terms in both edges
# and events -- that is, an event targets edges whose source matches
# the event's source. The direction of the relationship determines
# which resource is applied first and which resource is considered
# to be the event generator.
def to_edges
@value.collect do |value|
# we just have a name and a type, and we need to convert it
# to an object...
tname, name = value
reference = Puppet::ResourceReference.new(tname, name)
# Either of the two retrieval attempts could have returned
# nil.
unless object = reference.resolve
self.fail "Could not retrieve dependency '%s' of %s" % [reference, @resource.ref]
end
# Are we requiring them, or vice versa? See the method docs
# for futher info on this.
if self.class.direction == :in
source = object
target = @resource
else
source = @resource
target = object
end
if method = self.class.callback
subargs = {
:event => self.class.events,
:callback => method
}
self.debug("subscribes to %s" % [object.ref])
else
# If there's no callback, there's no point in even adding
# a label.
subargs = nil
self.debug("requires %s" % [object.ref])
end
rel = Puppet::Relationship.new(source, target, subargs)
end
end
end
def self.relationship_params
RelationshipMetaparam.subclasses
end
# Note that the order in which the relationships params is defined
# matters. The labelled params (notify and subcribe) must be later,
# so that if both params are used, those ones win. It's a hackish
# solution, but it works.
newmetaparam(:require, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :NONE}) 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. For instance::
# Create the destination directory before you copy things down
file { \"/usr/local/scripts\":
ensure => directory
}
file { \"/usr/local/scripts/myscript\":
source => \"puppet://server/module/myscript\",
mode => 755,
require => File[\"/usr/local/scripts\"]
}
Multiple dependencies can be specified by providing a comma-seperated list
of resources, enclosed in square brackets::
require => [ File[\"/usr/local\"], File[\"/usr/local/scripts\"] ]
Note that Puppet will autorequire everything that it can, and
there are hooks in place so that it's easy for resources to add new
ways to autorequire objects, so if you think Puppet could be
smarter here, let us know.
In fact, the above code was redundant -- Puppet will autorequire
any parent directories that are being managed; it will
automatically realize that the parent directory should be created
before the script is pulled down.
Currently, exec resources will autorequire their CWD (if it is
specified) plus any fully qualified paths that appear in the
command. For instance, if you had an ``exec`` command that ran
the ``myscript`` mentioned above, the above code that pulls the
file down would be automatically listed as a requirement to the
``exec`` code, so that you would always be running againts the
most recent version.
"
end
newmetaparam(:subscribe, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :ALL_EVENTS, :callback => :refresh}) 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). For instance::
class nagios {
file { \"/etc/nagios/nagios.conf\":
source => \"puppet://server/module/nagios.conf\",
alias => nagconf # just to make things easier for me
}
service { nagios:
running => true,
subscribe => File[nagconf]
}
}
Currently the ``exec``, ``mount`` and ``service`` type support
refreshing.
"
end
newmetaparam(:before, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :NONE}) do
desc %{This parameter is the opposite of **require** -- it guarantees
that the specified object is applied later than the specifying
object::
file { "/var/nagios/configuration":
source => "...",
recurse => true,
before => Exec["nagios-rebuid"]
}
exec { "nagios-rebuild":
command => "/usr/bin/make",
cwd => "/var/nagios/configuration"
}
This will make sure all of the files are up to date before the
make command is run.}
end
newmetaparam(:notify, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :ALL_EVENTS, :callback => :refresh}) do
desc %{This parameter is the opposite of **subscribe** -- it sends events
to the specified object::
file { "/etc/sshd_config":
source => "....",
notify => Service[sshd]
}
service { sshd:
ensure => running
}
This will restart the sshd service if the sshd config file changes.}
end
###############################
# All of the provider plumbing for the resource types.
require 'puppet/provider'
require 'puppet/util/provider_features'
# Add the feature handling module.
extend Puppet::Util::ProviderFeatures
attr_reader :provider
# the Type class attribute accessors
class << self
attr_accessor :providerloader
attr_writer :defaultprovider
end
# Find the default provider.
def self.defaultprovider
unless defined? @defaultprovider and @defaultprovider
suitable = suitableprovider()
# Find which providers are a default for this system.
defaults = suitable.find_all { |provider| provider.default? }
# If we don't have any default we use suitable providers
defaults = suitable if defaults.empty?
max = defaults.collect { |provider| provider.defaultnum }.max
defaults = defaults.find_all { |provider| provider.defaultnum == max }
retval = nil
if defaults.length > 1
Puppet.warning(
"Found multiple default providers for %s: %s; using %s" %
[self.name, defaults.collect { |i| i.name.to_s }.join(", "),
defaults[0].name]
)
retval = defaults.shift
elsif defaults.length == 1
retval = defaults.shift
else
raise Puppet::DevError, "Could not find a default provider for %s" %
self.name
end
@defaultprovider = retval
end
return @defaultprovider
end
# Convert a hash, as provided by, um, a provider, into an instance of self.
def self.hash2obj(hash)
obj = nil
namevar = self.namevar
unless hash.include?(namevar) and hash[namevar]
raise Puppet::DevError, "Hash was not passed with namevar"
end
# if the obj already exists with that name...
if obj = self[hash[namevar]]
# We're assuming here that objects with the same name
# are the same object, which *should* be the case, assuming
# we've set up our naming stuff correctly everywhere.
# Mark found objects as present
hash.each { |param, value|
if property = obj.property(param)
elsif val = obj[param]
obj[param] = val
else
# There is a value on disk, but it should go away
obj[param] = :absent
end
}
else
# create a new obj, since no existing one seems to
# match
obj = self.create(namevar => hash[namevar])
# We can't just pass the hash in at object creation time,
# because it sets the should value, not the is value.
hash.delete(namevar)
hash.each { |param, value|
obj[param] = value unless obj.add_property_parameter(param)
}
end
return obj
end
# Retrieve a provider by name.
def self.provider(name)
name = Puppet::Util.symbolize(name)
# If we don't have it yet, try loading it.
unless @providers.has_key?(name)
@providerloader.load(name)
end
return @providers[name]
end
# Just list all of the providers.
def self.providers
@providers.keys
end
def self.validprovider?(name)
name = Puppet::Util.symbolize(name)
return (@providers.has_key?(name) && @providers[name].suitable?)
end
# Create a new provider of a type. This method must be called
# directly on the type that it's implementing.
def self.provide(name, options = {}, &block)
name = Puppet::Util.symbolize(name)
if obj = @providers[name]
Puppet.debug "Reloading %s %s provider" % [name, self.name]
unprovide(name)
end
parent = if pname = options[:parent]
options.delete(:parent)
if pname.is_a? Class
pname
else
if provider = self.provider(pname)
provider
else
raise Puppet::DevError,
"Could not find parent provider %s of %s" %
[pname, name]
end
end
else
Puppet::Provider
end
options[:resource_type] ||= self
self.providify
provider = genclass(name,
:parent => parent,
:hash => @providers,
:prefix => "Provider",
:block => block,
:include => feature_module,
:extend => feature_module,
:attributes => options
)
return provider
end
# Make sure we have a :provider parameter defined. Only gets called if there
# are providers.
def self.providify
return if @paramhash.has_key? :provider
newparam(:provider) do
desc "The specific backend for #{self.name.to_s} to use. You will
seldom need to specify this -- Puppet will usually discover the
appropriate provider for your platform."
# This is so we can refer back to the type to get a list of
# providers for documentation.
class << self
attr_accessor :parenttype
end
# We need to add documentation for each provider.
def self.doc
@doc + " Available providers are:\n\n" + parenttype().providers.sort { |a,b|
a.to_s <=> b.to_s
}.collect { |i|
"* **%s**: %s" % [i, parenttype().provider(i).doc]
}.join("\n")
end
defaultto {
@resource.class.defaultprovider.name
}
validate do |provider_class|
provider_class = provider_class[0] if provider_class.is_a? Array
if provider_class.is_a?(Puppet::Provider)
provider_class = provider_class.class.name
end
unless provider = @resource.class.provider(provider_class)
raise ArgumentError, "Invalid %s provider '%s'" % [@resource.class.name, provider_class]
end
end
munge do |provider|
provider = provider[0] if provider.is_a? Array
if provider.is_a? String
provider = provider.intern
end
@resource.provider = provider
if provider.is_a?(Puppet::Provider)
provider.class.name
else
provider
end
end
end.parenttype = self
end
def self.unprovide(name)
if @providers.has_key? name
rmclass(name,
:hash => @providers,
:prefix => "Provider"
)
if @defaultprovider and @defaultprovider.name == name
@defaultprovider = nil
end
end
end
# Return an array of all of the suitable providers.
def self.suitableprovider
if @providers.empty?
providerloader.loadall
end
@providers.find_all { |name, provider|
provider.suitable?
}.collect { |name, provider|
provider
}.reject { |p| p.name == :fake } # For testing
end
def provider=(name)
if name.is_a?(Puppet::Provider)
@provider = name
@provider.resource = self
elsif klass = self.class.provider(name)
@provider = klass.new(self)
else
raise ArgumentError, "Could not find %s provider of %s" %
[name, self.class.name]
end
end
###############################
# All of the relationship code.
# Specify a block for generating a list of objects to autorequire. This
# makes it so that you don't have to manually specify things that you clearly
# require.
def self.autorequire(name, &block)
@autorequires ||= {}
@autorequires[name] = block
end
# Yield each of those autorequires in turn, yo.
def self.eachautorequire
@autorequires ||= {}
@autorequires.each { |type, block|
yield(type, block)
}
end
# Figure out of there are any objects we can automatically add as
# dependencies.
def autorequire
reqs = []
self.class.eachautorequire { |type, block|
# Ignore any types we can't find, although that would be a bit odd.
next unless typeobj = Puppet.type(type)
# Retrieve the list of names from the block.
next unless list = self.instance_eval(&block)
unless list.is_a?(Array)
list = [list]
end
# Collect the current prereqs
list.each { |dep|
obj = nil
# Support them passing objects directly, to save some effort.
unless dep.is_a? Puppet::Type
# Skip autorequires that we aren't managing
unless dep = typeobj[dep]
next
end
end
reqs << Puppet::Relationship.new(dep, self)
}
}
return reqs
end
# Build the dependencies associated with an individual object.
def builddepends
# Handle the requires
self.class.relationship_params.collect do |klass|
if param = @parameters[klass.name]
param.to_edges
end
end.flatten.reject { |r| r.nil? }
end
# Does this resource have a relationship with the other? We have to
# check each object for both directions of relationship.
def requires?(other)
them = [other.class.name, other.title]
me = [self.class.name, self.title]
self.class.relationship_params.each do |param|
case param.direction
when :in: return true if v = self[param.name] and v.include?(them)
when :out: return true if v = other[param.name] and v.include?(me)
end
end
return false
end
# we've received an event
# we only support local events right now, so we can pass actual
# objects around, including the transaction object
# the assumption here is that container objects will pass received
# methods on to contained objects
# i.e., we don't trigger our children, our refresh() method calls
# refresh() on our children
def trigger(event, source)
trans = event.transaction
if @callbacks.include?(source)
[:ALL_EVENTS, event.event].each { |eventname|
if method = @callbacks[source][eventname]
if trans.triggered?(self, method) > 0
next
end
if self.respond_to?(method)
self.send(method)
end
trans.triggered(self, method)
end
}
end
end
# Unsubscribe from a given object, possibly with a specific event.
def unsubscribe(object, event = nil)
# First look through our own relationship params
[:require, :subscribe].each do |param|
if values = self[param]
newvals = values.reject { |d|
d == [object.class.name, object.title]
}
if newvals.length != values.length
self.delete(param)
self[param] = newvals
end
end
end
end
###############################
# All of the scheduling code.
# Look up the schedule and set it appropriately. This is done after
# the instantiation phase, so that the schedule can be anywhere in the
# file.
def schedule
unless defined? @schedule
if name = self[:schedule]
if sched = Puppet.type(:schedule)[name]
@schedule = sched
else
self.fail "Could not find schedule %s" % name
end
else
@schedule = nil
end
end
@schedule
end
# Check whether we are scheduled to run right now or not.
def scheduled?
return true if Puppet[:ignoreschedules]
return true unless schedule = self.schedule
# We use 'checked' here instead of 'synced' because otherwise we'll
# end up checking most resources most times, because they will generally
# have been synced a long time ago (e.g., a file only gets updated
# once a month on the server and its schedule is daily; the last sync time
# will have been a month ago, so we'd end up checking every run).
return schedule.match?(self.cached(:checked).to_i)
end
###############################
# All of the tagging code.
attr_reader :tags
# Add a new tag.
def tag(tag)
tag = tag.intern if tag.is_a? String
unless @tags.include? tag
@tags << tag
end
end
# Define the initial list of tags.
def tags=(list)
list = [list] unless list.is_a? Array
@tags = list.collect do |t|
case t
when String: t.intern
when Symbol: t
else
self.warning "Ignoring tag %s of type %s" % [tag.inspect, tag.class]
end
end
@tags << self.class.name unless @tags.include?(self.class.name)
end
# Figure out of any of the specified tags apply to this object. This is an
# OR operation.
def tagged?(tags)
tags = [tags] unless tags.is_a? Array
tags = tags.collect { |t| t.intern }
return tags.find { |tag| @tags.include? tag }
end
# Types (which map to resources 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 properties.
# 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 :file, :line
-
attr_writer :title
attr_writer :noop
include Enumerable
# class methods dealing with Type management
public
# the Type class attribute accessors
class << self
attr_reader :name
attr_accessor :self_refresh
include Enumerable, Puppet::Util::ClassGen
include Puppet::MetaType::Manager
include Puppet::Util
include Puppet::Util::Logging
end
# all of the variables that must be initialized for each subclass
def self.initvars
# all of the instances of this class
@objects = Hash.new
@aliases = Hash.new
@providers = Hash.new
@defaults = {}
unless defined? @parameters
@parameters = []
end
@validproperties = {}
@properties = []
@parameters = []
@paramhash = {}
@attr_aliases = {}
@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
}
unless defined? @doc
@doc = ""
end
end
def self.to_s
if defined? @name
"Puppet::Type::" + @name.to_s.capitalize
else
super
end
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
# The catalog that this resource is stored in.
attr_accessor :catalog
# create a log at specified level
def log(msg)
Puppet::Util::Log.create(
:level => @parameters[:loglevel].value,
:message => msg,
:source => self
)
end
# instance methods related to instance intrinsics
# e.g., initialize() and name()
public
def initvars
@evalcount = 0
@tags = []
# callbacks are per object and event
@callbacks = Hash.new { |chash, key|
chash[key] = {}
}
# properties and parameters are treated equivalently from the outside:
# as name-value pairs (using [] and []=)
# internally, however, parameters are merely a hash, while properties
# point to Property objects
# further, the lists of valid properties and parameters are defined
# at the class level
unless defined? @parameters
@parameters = {}
end
# 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 such as failed changes
# it also doesn't distinguish between changes from the current transaction
# vs. changes over the process lifetime
@totalchanges = 0
@syncedchanges = 0
@failedchanges = 0
@inited = true
end
# initialize the type instance
def initialize(hash)
unless defined? @inited
self.initvars
end
namevar = self.class.namevar
orighash = hash
# If we got passed a transportable object, we just pull a bunch of info
# directly from it. This is the main object instantiation mechanism.
if hash.is_a?(Puppet::TransObject)
# XXX This will need to change when transobjects change to titles.
self.title = hash.name
#self[:name] = hash[:name]
[:file, :line, :tags, :catalog].each { |getter|
if hash.respond_to?(getter)
setter = getter.to_s + "="
if val = hash.send(getter)
self.send(setter, val)
end
end
}
hash = hash.to_hash
else
if hash[:title]
@title = hash[:title]
hash.delete(:title)
end
end
# Before anything else, set our parent if it was included
if hash.include?(:parent)
@parent = hash[:parent]
hash.delete(:parent)
end
# Munge up the namevar stuff so we only have one value.
hash = self.argclean(hash)
# Let's do the name first, because some things need to happen once
# we have the name but before anything else
attrs = self.class.allattrs
if hash.include?(namevar)
#self.send(namevar.to_s + "=", hash[namevar])
self[namevar] = hash[namevar]
hash.delete(namevar)
if attrs.include?(namevar)
attrs.delete(namevar)
else
self.devfail "My namevar isn't a valid attribute...?"
end
else
self.devfail "I was not passed a namevar"
end
# If the name and title differ, set up an alias
if self.name != self.title
if obj = self.class[self.name]
if self.class.isomorphic?
raise Puppet::Error, "%s already exists with name %s" %
[obj.title, self.name]
end
else
self.class.alias(self.name, self)
end
end
if hash.include?(:provider)
self[:provider] = hash[:provider]
hash.delete(:provider)
else
setdefaults(:provider)
end
# This is all of our attributes except the namevar.
attrs.each { |attr|
if hash.include?(attr)
begin
self[attr] = hash[attr]
rescue ArgumentError, Puppet::Error, TypeError
raise
rescue => detail
error = Puppet::DevError.new( "Could not set %s on %s: %s" % [attr, self.class.name, detail])
error.set_backtrace(detail.backtrace)
raise error
end
hash.delete attr
end
}
# Set all default values.
self.setdefaults
if hash.length > 0
self.debug hash.inspect
self.fail("Class %s does not accept argument(s) %s" %
[self.class.name, hash.keys.join(" ")])
end
if self.respond_to?(:validate)
self.validate
end
end
# Set up all of our autorequires.
def finish
# Scheduling has to be done when the whole config is instantiated, so
# that file order doesn't matter in finding them.
self.schedule
# Make sure all of our relationships are valid. Again, must be done
# when the entire catalog is instantiated.
self.class.relationship_params.collect do |klass|
if param = @parameters[klass.name]
param.validate_relationship
end
end.flatten.reject { |r| r.nil? }
end
# Return a cached value
def cached(name)
Puppet::Util::Storage.cache(self)[name]
#@cache[name] ||= nil
end
# Cache a value
def cache(name, value)
Puppet::Util::Storage.cache(self)[name] = value
#@cache[name] = value
end
# def set(name, value)
# send(name.to_s + "=", value)
# end
#
# def get(name)
# send(name)
# end
# For now, leave the 'name' method functioning like it used to. Once 'title'
# works everywhere, I'll switch it.
def name
return self[:name]
end
# Look up our parent in the catalog, if we have one.
def parent
return nil unless catalog
unless defined?(@parent)
# This is kinda weird.
if implicit?
parents = catalog.relationship_graph.adjacent(self, :direction => :in)
else
parents = catalog.adjacent(self, :direction => :in)
end
if parents
# We should never have more than one parent, so let's just ignore
# it if we happen to.
@parent = parents.shift
else
@parent = nil
end
end
@parent
end
# Return the "type[name]" style reference.
def ref
"%s[%s]" % [self.class.name.to_s.capitalize, self.title]
end
def self_refresh?
self.class.self_refresh
end
# Mark that we're purging.
def purging
@purging = true
end
# Is this resource being purged? Used by transactions to forbid
# deletion when there are dependencies.
def purging?
if defined? @purging
@purging
else
false
end
end
# Retrieve the title of an object. If no title was set separately,
# then use the object's name.
def title
unless defined? @title and @title
namevar = self.class.namevar
if self.class.validparameter?(namevar)
@title = self[:name]
elsif self.class.validproperty?(namevar)
@title = self.should(namevar)
else
self.devfail "Could not find namevar %s for %s" %
[namevar, self.class.name]
end
end
return @title
end
# convert to a string
def to_s
self.ref
end
# Convert to a transportable object
def to_trans(ret = true)
trans = TransObject.new(self.title, self.class.name)
values = retrieve()
values.each do |name, value|
trans[name.name] = value
end
@parameters.each do |name, param|
# Avoid adding each instance name as both the name and the namevar
next if param.class.isnamevar? and param.value == self.title
# We've already got property values
next if param.is_a?(Puppet::Property)
trans[name] = param.value
end
trans.tags = self.tags
# FIXME I'm currently ignoring 'parent' and 'path'
return trans
end
end # Puppet::Type
end
require 'puppet/provider'
# Always load these types.
require 'puppet/type/component'
diff --git a/spec/unit/file_collection.rb b/spec/unit/file_collection.rb
index e9acc8dd2..81bcc2d2d 100755
--- a/spec/unit/file_collection.rb
+++ b/spec/unit/file_collection.rb
@@ -1,45 +1,53 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../spec_helper'
require 'puppet/file_collection'
describe Puppet::FileCollection do
before do
@collection = Puppet::FileCollection.new
end
it "should be able to convert a file name into an index" do
@collection.index("/my/file").should be_instance_of(Fixnum)
end
it "should be able to convert an index into a file name" do
index = @collection.index("/path/to/file")
@collection.path(index).should == "/path/to/file"
end
it "should always give the same file name for a given index" do
index = @collection.index("/path/to/file")
@collection.path(index).should == @collection.path(index)
end
it "should always give the same index for a given file name" do
@collection.index("/my/file").should == @collection.index("/my/file")
end
it "should always correctly relate a file name and its index even when multiple files are in the collection" do
indexes = %w{a b c d e f}.inject({}) do |hash, letter|
hash[letter] = @collection.index("/path/to/file/%s" % letter)
hash
end
indexes.each do |letter, index|
@collection.index("/path/to/file/%s" % letter).should == indexes[letter]
@collection.path(index).should == @collection.path(index)
end
end
it "should return nil as the file name when an unknown index is provided" do
@collection.path(50).should be_nil
end
+
+ it "should provide a global collection" do
+ Puppet::FileCollection.collection.should be_instance_of(Puppet::FileCollection)
+ end
+
+ it "should reuse the global collection" do
+ Puppet::FileCollection.collection.should equal(Puppet::FileCollection.collection)
+ end
end
diff --git a/spec/unit/file_collection/lookup.rb b/spec/unit/file_collection/lookup.rb
index 9ae7ae582..81cc61872 100755
--- a/spec/unit/file_collection/lookup.rb
+++ b/spec/unit/file_collection/lookup.rb
@@ -1,41 +1,46 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/file_collection/lookup'
class LookupTester
include Puppet::FileCollection::Lookup
end
describe Puppet::FileCollection::Lookup do
before do
@tester = LookupTester.new
@file_collection = mock 'file_collection'
- @tester.stubs(:file_collection).returns @file_collection
+ Puppet::FileCollection.stubs(:collection).returns @file_collection
end
it "should use the file collection to determine the index of the file name" do
@file_collection.expects(:index).with("/my/file").returns 50
@tester.file = "/my/file"
@tester.file_index.should == 50
end
it "should return nil as the file name if no index is set" do
@tester.file.should be_nil
end
it "should use the file collection to convert the index to a file name" do
@file_collection.expects(:path).with(25).returns "/path/to/file"
@tester.file_index = 25
@tester.file.should == "/path/to/file"
end
it "should support a line attribute" do
@tester.line = 50
@tester.line.should == 50
end
+
+ it "should default to the global file collection" do
+ Puppet::FileCollection.expects(:collection).returns "collection"
+ @tester.file_collection.should == "collection"
+ end
end
diff --git a/spec/unit/parser/ast.rb b/spec/unit/parser/ast.rb
index 513943725..35e2b1eff 100644
--- a/spec/unit/parser/ast.rb
+++ b/spec/unit/parser/ast.rb
@@ -1,37 +1,41 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/parser/ast'
describe Puppet::Parser::AST do
+
+ it "should use the file lookup module" do
+ Puppet::Parser::AST.ancestors.should be_include(Puppet::FileCollection::Lookup)
+ end
it "should have a doc accessor" do
ast = Puppet::Parser::AST.new({})
ast.should respond_to(:doc)
end
it "should have a use_docs accessor to indicate it wants documentation" do
ast = Puppet::Parser::AST.new({})
ast.should respond_to(:use_docs)
end
[ Puppet::Parser::AST::Collection, Puppet::Parser::AST::Definition, Puppet::Parser::AST::Else,
Puppet::Parser::AST::Function, Puppet::Parser::AST::HostClass, Puppet::Parser::AST::IfStatement,
Puppet::Parser::AST::Node, Puppet::Parser::AST::Resource, Puppet::Parser::AST::ResourceDefaults,
Puppet::Parser::AST::ResourceOverride, Puppet::Parser::AST::VarDef
].each do |k|
it "#{k}.use_docs should return true" do
ast = k.new({})
ast.use_docs.should be_true
end
end
describe "when initializing" do
it "should store the doc argument if passed" do
ast = Puppet::Parser::AST.new(:doc => "documentation")
ast.doc.should == "documentation"
end
end
-end
\ No newline at end of file
+end
diff --git a/spec/unit/parser/resource.rb b/spec/unit/parser/resource.rb
index 63cfbc2ed..2666f6461 100755
--- a/spec/unit/parser/resource.rb
+++ b/spec/unit/parser/resource.rb
@@ -1,336 +1,340 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
# LAK: FIXME This is just new tests for resources; I have
# not moved all tests over yet.
describe Puppet::Parser::Resource do
before do
@parser = Puppet::Parser::Parser.new :Code => ""
@source = @parser.newclass ""
@node = Puppet::Node.new("yaynode")
@compiler = Puppet::Parser::Compiler.new(@node, @parser)
@scope = @compiler.topscope
end
def mkresource(args = {})
args[:source] ||= "source"
args[:scope] ||= stub('scope', :source => mock('source'))
{:type => "resource", :title => "testing", :source => "source", :scope => "scope"}.each do |param, value|
args[param] ||= value
end
params = args[:params] || {:one => "yay", :three => "rah"}
if args[:params] == :none
args.delete(:params)
else
args[:params] = paramify(args[:source], params)
end
Puppet::Parser::Resource.new(args)
end
def param(name, value, source)
Puppet::Parser::Resource::Param.new(:name => name, :value => value, :source => source)
end
def paramify(source, hash)
hash.collect do |name, value|
Puppet::Parser::Resource::Param.new(
:name => name, :value => value, :source => source
)
end
end
+ it "should use the file lookup module" do
+ Puppet::Parser::Resource.ancestors.should be_include(Puppet::FileCollection::Lookup)
+ end
+
it "should be isomorphic if it is builtin and models an isomorphic type" do
Puppet::Type.type(:file).expects(:isomorphic?).returns(true)
@resource = Puppet::Parser::Resource.new(:type => "file", :title => "whatever", :scope => @scope, :source => @source).isomorphic?.should be_true
end
it "should not be isomorphic if it is builtin and models a non-isomorphic type" do
Puppet::Type.type(:file).expects(:isomorphic?).returns(false)
@resource = Puppet::Parser::Resource.new(:type => "file", :title => "whatever", :scope => @scope, :source => @source).isomorphic?.should be_false
end
it "should be isomorphic if it is not builtin" do
@parser.newdefine "whatever"
@resource = Puppet::Parser::Resource.new(:type => "whatever", :title => "whatever", :scope => @scope, :source => @source).isomorphic?.should be_true
end
it "should have a array-indexing method for retrieving parameter values" do
@resource = mkresource
@resource[:one].should == "yay"
end
it "should have a method for converting to a ral resource" do
trans = mock 'trans', :to_type => "yay"
@resource = mkresource
@resource.expects(:to_trans).returns trans
@resource.to_type.should == "yay"
end
describe "when initializing" do
before do
@arguments = {:type => "resource", :title => "testing", :scope => stub('scope', :source => mock('source'))}
end
[:type, :title, :scope].each do |name|
it "should fail unless #{name.to_s} is specified" do
try = @arguments.dup
try.delete(name)
lambda { Puppet::Parser::Resource.new(try) }.should raise_error(ArgumentError)
end
end
it "should set the reference correctly" do
res = Puppet::Parser::Resource.new(@arguments)
res.ref.should == "Resource[testing]"
end
end
describe "when evaluating" do
before do
@type = Puppet::Parser::Resource
@definition = @parser.newdefine "mydefine"
@class = @parser.newclass "myclass"
@nodedef = @parser.newnode("mynode")[0]
end
it "should evaluate the associated AST definition" do
res = @type.new(:type => "mydefine", :title => "whatever", :scope => @scope, :source => @source)
@definition.expects(:evaluate_code).with(res)
res.evaluate
end
it "should evaluate the associated AST class" do
res = @type.new(:type => "class", :title => "myclass", :scope => @scope, :source => @source)
@class.expects(:evaluate_code).with(res)
res.evaluate
end
it "should evaluate the associated AST node" do
res = @type.new(:type => "node", :title => "mynode", :scope => @scope, :source => @source)
@nodedef.expects(:evaluate_code).with(res)
res.evaluate
end
end
describe "when finishing" do
before do
@class = @parser.newclass "myclass"
@nodedef = @parser.newnode("mynode")[0]
@resource = Puppet::Parser::Resource.new(:type => "file", :title => "whatever", :scope => @scope, :source => @source)
end
it "should do nothing if it has already been finished" do
@resource.finish
@resource.expects(:add_metaparams).never
@resource.finish
end
it "should add all defaults available from the scope" do
@resource.scope.expects(:lookupdefaults).with(@resource.type).returns(:owner => param(:owner, "default", @resource.source))
@resource.finish
@resource[:owner].should == "default"
end
it "should not replace existing parameters with defaults" do
@resource.set_parameter :owner, "oldvalue"
@resource.scope.expects(:lookupdefaults).with(@resource.type).returns(:owner => :replaced)
@resource.finish
@resource[:owner].should == "oldvalue"
end
it "should add a copy of each default, rather than the actual default parameter instance" do
newparam = param(:owner, "default", @resource.source)
other = newparam.dup
other.value = "other"
newparam.expects(:dup).returns(other)
@resource.scope.expects(:lookupdefaults).with(@resource.type).returns(:owner => newparam)
@resource.finish
@resource[:owner].should == "other"
end
it "should copy metaparams from its scope" do
@scope.setvar("noop", "true")
@resource.class.publicize_methods(:add_metaparams) { @resource.add_metaparams }
@resource["noop"].should == "true"
end
it "should not copy metaparams that it already has" do
@resource.set_parameter("noop", "false")
@scope.setvar("noop", "true")
@resource.class.publicize_methods(:add_metaparams) { @resource.add_metaparams }
@resource["noop"].should == "false"
end
it "should copy all metaparams that it finds" do
@scope.setvar("require", "container")
@scope.setvar("notify", "container")
@resource.class.publicize_methods(:add_metaparams) { @resource.add_metaparams }
@resource["require"].should == "container"
@resource["notify"].should == "container"
end
it "should stack relationship metaparams from its container if it already has them" do
@resource.set_parameter("require", "resource")
@scope.setvar("require", "container")
@resource.class.publicize_methods(:add_metaparams) { @resource.add_metaparams }
@resource["require"].sort.should == %w{container resource}
end
it "should flatten the array resulting from stacking relationship metaparams" do
@resource.set_parameter("require", ["resource1", "resource2"])
@scope.setvar("require", %w{container1 container2})
@resource.class.publicize_methods(:add_metaparams) { @resource.add_metaparams }
@resource["require"].sort.should == %w{container1 container2 resource1 resource2}
end
it "should add any tags from the scope resource" do
scope_resource = stub 'scope_resource', :tags => %w{one two}
@scope.stubs(:resource).returns(scope_resource)
@resource.class.publicize_methods(:add_scope_tags) { @resource.add_scope_tags }
@resource.tags.should be_include("one")
@resource.tags.should be_include("two")
end
end
describe "when being tagged" do
before do
@scope_resource = stub 'scope_resource', :tags => %w{srone srtwo}
@scope = stub 'scope', :resource => @scope_resource
@resource = Puppet::Parser::Resource.new(:type => "file", :title => "yay", :scope => @scope, :source => mock('source'))
end
it "should get tagged with the resource type" do
@resource.tags.should be_include("file")
end
it "should get tagged with the title" do
@resource.tags.should be_include("yay")
end
it "should get tagged with each name in the title if the title is a qualified class name" do
resource = Puppet::Parser::Resource.new(:type => "file", :title => "one::two", :scope => @scope, :source => mock('source'))
resource.tags.should be_include("one")
resource.tags.should be_include("two")
end
it "should get tagged with each name in the type if the type is a qualified class name" do
resource = Puppet::Parser::Resource.new(:type => "one::two", :title => "whatever", :scope => @scope, :source => mock('source'))
resource.tags.should be_include("one")
resource.tags.should be_include("two")
end
it "should not get tagged with non-alphanumeric titles" do
resource = Puppet::Parser::Resource.new(:type => "file", :title => "this is a test", :scope => @scope, :source => mock('source'))
resource.tags.should_not be_include("this is a test")
end
it "should fail on tags containing '*' characters" do
lambda { @resource.tag("bad*tag") }.should raise_error(Puppet::ParseError)
end
it "should fail on tags starting with '-' characters" do
lambda { @resource.tag("-badtag") }.should raise_error(Puppet::ParseError)
end
it "should fail on tags containing ' ' characters" do
lambda { @resource.tag("bad tag") }.should raise_error(Puppet::ParseError)
end
it "should allow alpha tags" do
lambda { @resource.tag("good_tag") }.should_not raise_error(Puppet::ParseError)
end
end
describe "when merging overrides" do
before do
@source = "source1"
@resource = mkresource :source => @source
@override = mkresource :source => @source
end
it "should fail when the override was not created by a parent class" do
@override.source = "source2"
@override.source.expects(:child_of?).with("source1").returns(false)
lambda { @resource.merge(@override) }.should raise_error(Puppet::ParseError)
end
it "should succeed when the override was created in the current scope" do
@resource.source = "source3"
@override.source = @resource.source
@override.source.expects(:child_of?).with("source3").never
params = {:a => :b, :c => :d}
@override.expects(:params).returns(params)
@resource.expects(:override_parameter).with(:b)
@resource.expects(:override_parameter).with(:d)
@resource.merge(@override)
end
it "should succeed when a parent class created the override" do
@resource.source = "source3"
@override.source = "source4"
@override.source.expects(:child_of?).with("source3").returns(true)
params = {:a => :b, :c => :d}
@override.expects(:params).returns(params)
@resource.expects(:override_parameter).with(:b)
@resource.expects(:override_parameter).with(:d)
@resource.merge(@override)
end
it "should add new parameters when the parameter is not set" do
@source.stubs(:child_of?).returns true
@override.set_parameter(:testing, "value")
@resource.merge(@override)
@resource[:testing].should == "value"
end
it "should replace existing parameter values" do
@source.stubs(:child_of?).returns true
@resource.set_parameter(:testing, "old")
@override.set_parameter(:testing, "value")
@resource.merge(@override)
@resource[:testing].should == "value"
end
it "should add values to the parameter when the override was created with the '+>' syntax" do
@source.stubs(:child_of?).returns true
param = Puppet::Parser::Resource::Param.new(:name => :testing, :value => "testing", :source => @resource.source)
param.add = true
@override.set_parameter(param)
@resource.set_parameter(:testing, "other")
@resource.merge(@override)
@resource[:testing].should == %w{other testing}
end
end
end
diff --git a/spec/unit/parser/resource/reference.rb b/spec/unit/parser/resource/reference.rb
index bb1452692..6284e67cc 100755
--- a/spec/unit/parser/resource/reference.rb
+++ b/spec/unit/parser/resource/reference.rb
@@ -1,95 +1,99 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../spec_helper'
describe Puppet::Parser::Resource::Reference do
before do
@type = Puppet::Parser::Resource::Reference
end
+ it "should use the file lookup module" do
+ Puppet::Parser::Resource::Reference.ancestors.should be_include(Puppet::FileCollection::Lookup)
+ end
+
it "should require a type" do
proc { @type.new(:title => "yay") }.should raise_error(Puppet::DevError)
end
it "should require a title" do
proc { @type.new(:type => "file") }.should raise_error(Puppet::DevError)
end
it "should know when it refers to a builtin type" do
ref = @type.new(:type => "file", :title => "/tmp/yay")
ref.builtin?.should be_true
ref.builtintype.should equal(Puppet::Type.type(:file))
end
it "should return a downcased relationship-style resource reference for defined types" do
ref = @type.new(:type => "file", :title => "/tmp/yay")
ref.to_ref.should == ["file", "/tmp/yay"]
end
it "should return a capitalized relationship-style resource reference for defined types" do
ref = @type.new(:type => "whatever", :title => "/tmp/yay")
ref.to_ref.should == ["Whatever", "/tmp/yay"]
end
it "should return a resource reference string when asked" do
ref = @type.new(:type => "file", :title => "/tmp/yay")
ref.to_s.should == "File[/tmp/yay]"
end
it "should canonize resource references" do
ref = @type.new(:type => "foo::bar", :title => "/tmp/yay")
ref.to_s.should == "Foo::Bar[/tmp/yay]"
end
end
describe Puppet::Parser::Resource::Reference, " when modeling defined types" do
before do
@type = Puppet::Parser::Resource::Reference
@parser = Puppet::Parser::Parser.new :Code => ""
@definition = @parser.newdefine "mydefine"
@class = @parser.newclass "myclass"
@nodedef = @parser.newnode("mynode")[0]
@node = Puppet::Node.new("yaynode")
@compiler = Puppet::Parser::Compiler.new(@node, @parser)
end
it "should be able to find defined types" do
ref = @type.new(:type => "mydefine", :title => "/tmp/yay", :scope => @compiler.topscope)
ref.builtin?.should be_false
ref.definedtype.should equal(@definition)
end
it "should be able to find classes" do
ref = @type.new(:type => "class", :title => "myclass", :scope => @compiler.topscope)
ref.builtin?.should be_false
ref.definedtype.should equal(@class)
end
it "should be able to find nodes" do
ref = @type.new(:type => "node", :title => "mynode", :scope => @compiler.topscope)
ref.builtin?.should be_false
ref.definedtype.object_id.should == @nodedef.object_id
end
it "should only look for fully qualified classes" do
top = @parser.newclass "top"
sub = @parser.newclass "other::top"
scope = @compiler.topscope.class.new(:parent => @compiler.topscope, :namespace => "other", :parser => @parser)
ref = @type.new(:type => "class", :title => "top", :scope => scope)
ref.definedtype.classname.should equal(top.classname)
end
it "should only look for fully qualified definitions" do
top = @parser.newdefine "top"
sub = @parser.newdefine "other::top"
scope = @compiler.topscope.class.new(:parent => @compiler.topscope, :namespace => "other", :parser => @parser)
ref = @type.new(:type => "top", :title => "foo", :scope => scope)
ref.definedtype.classname.should equal(top.classname)
end
end