diff --git a/lib/puppet.rb b/lib/puppet.rb
index 2e531ad75..09ae531aa 100644
--- a/lib/puppet.rb
+++ b/lib/puppet.rb
@@ -1,167 +1,166 @@
# Try to load rubygems. Hey rubygems, I hate you.
begin
require 'rubygems'
rescue LoadError
end
# see the bottom of the file for further inclusions
require 'singleton'
require 'facter'
require 'puppet/error'
require 'puppet/util'
require 'puppet/util/autoload'
require 'puppet/util/settings'
require 'puppet/util/feature'
require 'puppet/util/suidmanager'
#------------------------------------------------------------
# the top-level module
#
# all this really does is dictate how the whole system behaves, through
# preferences for things like debugging
#
# it's also a place to find top-level commands like 'debug'
module Puppet
PUPPETVERSION = '0.25.4'
def Puppet.version
return PUPPETVERSION
end
class << self
include Puppet::Util
attr_reader :features
attr_writer :name
end
# the hash that determines how our system behaves
@@settings = Puppet::Util::Settings.new
# The services running in this process.
@services ||= []
require 'puppet/util/logging'
extend Puppet::Util::Logging
# The feature collection
@features = Puppet::Util::Feature.new('puppet/feature')
# Load the base features.
require 'puppet/feature/base'
# Store a new default value.
def self.setdefaults(section, hash)
@@settings.setdefaults(section, hash)
end
# configuration parameter access and stuff
def self.[](param)
case param
when :debug
if Puppet::Util::Log.level == :debug
return true
else
return false
end
else
return @@settings[param]
end
end
# configuration parameter access and stuff
def self.[]=(param,value)
@@settings[param] = value
end
def self.clear
@@settings.clear
end
def self.debug=(value)
if value
Puppet::Util::Log.level=(:debug)
else
Puppet::Util::Log.level=(:notice)
end
end
def self.settings
@@settings
end
# Load all of the configuration parameters.
require 'puppet/defaults'
def self.genmanifest
if Puppet[:genmanifest]
puts Puppet.settings.to_manifest
exit(0)
end
end
# Parse the config file for this process.
def self.parse_config
Puppet.settings.parse
end
# XXX this should all be done using puppet objects, not using
# normal mkdir
def self.recmkdir(dir,mode = 0755)
if FileTest.exist?(dir)
return false
else
tmp = dir.sub(/^\//,'')
path = [File::SEPARATOR]
tmp.split(File::SEPARATOR).each { |dir|
path.push dir
if ! FileTest.exist?(File.join(path))
begin
Dir.mkdir(File.join(path), mode)
rescue Errno::EACCES => detail
Puppet.err detail.to_s
return false
rescue => detail
Puppet.err "Could not create %s: %s" % [path, detail.to_s]
return false
end
elsif FileTest.directory?(File.join(path))
next
else FileTest.exist?(File.join(path))
raise Puppet::Error, "Cannot create %s: basedir %s is a file" %
[dir, File.join(path)]
end
}
return true
end
end
# Create a new type. Just proxy to the Type class.
def self.newtype(name, options = {}, &block)
Puppet::Type.newtype(name, options, &block)
end
# Retrieve a type by name. Just proxy to the Type class.
def self.type(name)
# LAK:DEP Deprecation notice added 12/17/2008
Puppet.warning "Puppet.type is deprecated; use Puppet::Type.type"
Puppet::Type.type(name)
end
end
require 'puppet/type'
require 'puppet/parser'
require 'puppet/resource'
-require 'puppet/resource/reference'
require 'puppet/network'
require 'puppet/ssl'
require 'puppet/module'
require 'puppet/util/storage'
require 'puppet/status'
require 'puppet/file_bucket/file'
if Puppet[:storeconfigs]
require 'puppet/rails'
end
diff --git a/lib/puppet/parser/ast/resource.rb b/lib/puppet/parser/ast/resource.rb
index d222893b3..5da40b32f 100644
--- a/lib/puppet/parser/ast/resource.rb
+++ b/lib/puppet/parser/ast/resource.rb
@@ -1,76 +1,72 @@
require 'puppet/parser/ast/resource_reference'
# Any normal puppet resource declaration. Can point to a definition or a
# builtin type.
class Puppet::Parser::AST
class Resource < AST::ResourceReference
associates_doc
attr_accessor :title, :type, :exported, :virtual
attr_reader :params
# Does not actually return an object; instead sets an object
# in the current scope.
def evaluate(scope)
# Evaluate all of the specified params.
- paramobjects = @params.collect { |param|
+ paramobjects = params.collect { |param|
param.safeevaluate(scope)
}
resource_titles = @title.safeevaluate(scope)
# it's easier to always use an array, even for only one name
unless resource_titles.is_a?(Array)
resource_titles = [resource_titles]
end
- resource_type = qualified_type(scope)
-
# We want virtual to be true if exported is true. We can't
# just set :virtual => self.virtual in the initialization,
# because sometimes the :virtual attribute is set *after*
# :exported, in which case it clobbers :exported if :exported
# is true. Argh, this was a very tough one to track down.
virt = self.virtual || self.exported
# This is where our implicit iteration takes place; if someone
# passed an array as the name, then we act just like the called us
# many times.
resource_titles.flatten.collect { |resource_title|
exceptwrap :type => Puppet::ParseError do
- resource = Puppet::Parser::Resource.new(
- :type => resource_type,
- :title => resource_title,
+ resource = Puppet::Parser::Resource.new(type, resource_title,
:params => paramobjects,
:file => self.file,
:line => self.line,
:exported => self.exported,
:virtual => virt,
:source => scope.source,
:scope => scope
)
# And then store the resource in the compiler.
# At some point, we need to switch all of this to return
# resources instead of storing them like this.
scope.compiler.add_resource(scope, resource)
resource
end
}.reject { |resource| resource.nil? }
end
# Set the parameters for our object.
def params=(params)
if params.is_a?(AST::ASTArray)
@params = params
else
@params = AST::ASTArray.new(
:line => params.line,
:file => params.file,
:children => [params]
)
end
end
end
end
diff --git a/lib/puppet/parser/ast/resource_defaults.rb b/lib/puppet/parser/ast/resource_defaults.rb
index 3fde7ade2..f0746ecf1 100644
--- a/lib/puppet/parser/ast/resource_defaults.rb
+++ b/lib/puppet/parser/ast/resource_defaults.rb
@@ -1,24 +1,24 @@
require 'puppet/parser/ast/branch'
class Puppet::Parser::AST
# A statement syntactically similar to an ResourceDef, but uses a
# capitalized object type and cannot have a name.
class ResourceDefaults < AST::Branch
attr_accessor :type, :params
associates_doc
# As opposed to ResourceDef, this stores each default for the given
# object type.
def evaluate(scope)
# Use a resource reference to canonize the type
- ref = Puppet::Resource::Reference.new(@type, "whatever")
+ ref = Puppet::Resource.new(@type, "whatever")
type = ref.type
params = @params.safeevaluate(scope)
parsewrap do
scope.setdefaults(type, params)
end
end
end
end
diff --git a/lib/puppet/parser/ast/resource_override.rb b/lib/puppet/parser/ast/resource_override.rb
index 5eac50982..2d4f7a871 100644
--- a/lib/puppet/parser/ast/resource_override.rb
+++ b/lib/puppet/parser/ast/resource_override.rb
@@ -1,68 +1,66 @@
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,
+ res = Puppet::Parser::Resource.new(r.type, r.title,
:params => params,
: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/ast/resource_reference.rb b/lib/puppet/parser/ast/resource_reference.rb
index 794e505b8..8e09a8e3b 100644
--- a/lib/puppet/parser/ast/resource_reference.rb
+++ b/lib/puppet/parser/ast/resource_reference.rb
@@ -1,76 +1,23 @@
+require 'puppet/parser/ast'
require 'puppet/parser/ast/branch'
+require 'puppet/resource'
-class Puppet::Parser::AST
- # A reference to an object. Only valid as an rvalue.
- class ResourceReference < AST::Branch
- attr_accessor :title, :type
- # Is the type a builtin type?
- def builtintype?(type)
- if typeklass = Puppet::Type.type(type)
- return typeklass
- else
- return false
- end
- end
-
- def each
- [@type,@title].flatten.each { |param|
- #Puppet.debug("yielding param %s" % param)
- yield param
- }
- end
+class Puppet::Parser::AST::ResourceReference < Puppet::Parser::AST::Branch
+ attr_accessor :title, :type
- # Evaluate our object, but just return a simple array of the type
- # and name.
- def evaluate(scope)
- title = @title.safeevaluate(scope)
- title = [title] unless title.is_a?(Array)
-
- if @type.to_s.downcase == "class"
- resource_type = "class"
- title = title.collect { |t| qualified_class(scope, t) }
- else
- resource_type = qualified_type(scope)
- end
-
- title = title.collect { |t| Puppet::Parser::Resource::Reference.new(
- :type => resource_type, :title => t
- ) }
- return title.pop if title.length == 1
- return title
- end
-
- # Look up a fully qualified class name.
- def qualified_class(scope, title)
- # Look up the full path to the class
- if classobj = scope.find_hostclass(title)
- title = classobj.name
- else
- raise Puppet::ParseError, "Could not find class %s" % title
- end
- end
-
- # Look up a fully-qualified type. This method is
- # also used in AST::Resource.
- def qualified_type(scope, title = nil)
- # We want a lower-case type. For some reason.
- objtype = @type.downcase
- unless builtintype?(objtype)
- if dtype = scope.find_definition(objtype)
- objtype = dtype.name
- else
- raise Puppet::ParseError, "Could not find resource type %s" % objtype
- end
- end
- return objtype
- end
+ # Evaluate our object, but just return a simple array of the type
+ # and name.
+ def evaluate(scope)
+ titles = Array(title.safeevaluate(scope)).collect { |t| Puppet::Resource.new(type, t, :namespaces => scope.namespaces) }
+ return titles.pop if titles.length == 1
+ return titles
+ end
- def to_s
- if title.is_a?(ASTArray)
- "#{type.to_s.capitalize}#{title}"
- else
- "#{type.to_s.capitalize}[#{title}]"
- end
+ def to_s
+ if title.is_a?(Puppet::Parser::AST::ASTArray)
+ "#{type.to_s.capitalize}#{title}"
+ else
+ "#{type.to_s.capitalize}[#{title}]"
end
end
end
diff --git a/lib/puppet/parser/collector.rb b/lib/puppet/parser/collector.rb
index a6763c419..044518793 100644
--- a/lib/puppet/parser/collector.rb
+++ b/lib/puppet/parser/collector.rb
@@ -1,231 +1,231 @@
# An object that collects stored objects from the central cache and returns
# them to the current host, yo.
class Puppet::Parser::Collector
attr_accessor :type, :scope, :vquery, :equery, :form, :resources, :overrides, :collected
# Call the collection method, mark all of the returned objects as non-virtual,
# optionally applying parameter overrides. The collector can also delete himself
# from the compiler if there is no more resources to collect (valid only for resource fixed-set collector
# which get their resources from +collect_resources+ and not from the catalog)
def evaluate
# Shortcut if we're not using storeconfigs and they're trying to collect
# exported resources.
if form == :exported and Puppet[:storeconfigs] != true
Puppet.warning "Not collecting exported resources without storeconfigs"
return false
end
if self.resources
unless objects = collect_resources and ! objects.empty?
return false
end
else
method = "collect_#{@form.to_s}"
objects = send(method).each do |obj|
obj.virtual = false
end
if objects.empty?
return false
end
end
# we have an override for the collected resources
if @overrides and !objects.empty?
# force the resource to be always child of any other resource
overrides[:source].meta_def(:child_of?) do
true
end
# tell the compiler we have some override for him unless we already
# overrided those resources
objects.each do |res|
unless @collected.include?(res.ref)
res = Puppet::Parser::Resource.new(
:type => res.type,
:title => res.title,
:params => overrides[:params],
:file => overrides[:file],
:line => overrides[:line],
:source => overrides[:source],
:scope => overrides[:scope]
)
scope.compiler.add_override(res)
end
end
end
# filter out object that already have been collected by ourself
objects.reject! { |o| @collected.include?(o.ref) }
return false if objects.empty?
# keep an eye on the resources we have collected
objects.inject(@collected) { |c,o| c[o.ref]=o; c }
# return our newly collected resources
objects
end
def initialize(scope, type, equery, vquery, form)
@scope = scope
# initialisation
@collected = {}
# Canonize the type
- @type = Puppet::Resource::Reference.new(type, "whatever").type
+ @type = Puppet::Resource.new(type, "whatever").type
@equery = equery
@vquery = vquery
raise(ArgumentError, "Invalid query form %s" % form) unless [:exported, :virtual].include?(form)
@form = form
end
# add a resource override to the soon to be exported/realized resources
def add_override(hash)
unless hash[:params]
raise ArgumentError, "Exported resource try to override without parameters"
end
# schedule an override for an upcoming collection
@overrides = hash
end
private
# Create our active record query.
def build_active_record_query
Puppet::Rails.init unless ActiveRecord::Base.connected?
raise Puppet::DevError, "Cannot collect resources for a nil host" unless @scope.host
host = Puppet::Rails::Host.find_by_name(@scope.host)
search = "(exported=? AND restype=?)"
values = [true, @type]
search += " AND (%s)" % @equery if @equery
# note:
# we're not eagerly including any relations here because
# it can creates so much objects we'll throw out later.
# We used to eagerly include param_names/values but the way
# the search filter is built ruined those efforts and we
# were eagerly loading only the searched parameter and not
# the other ones.
query = {}
case search
when /puppet_tags/
query = {:joins => {:resource_tags => :puppet_tag}}
when /param_name/
query = {:joins => {:param_values => :param_name}}
end
# We're going to collect objects from rails, but we don't want any
# objects from this host.
search = ("host_id != ? AND %s" % search) and values.unshift(host.id) if host
query[:conditions] = [search, *values]
return query
end
# Collect exported objects.
def collect_exported
# First get everything from the export table. Just reuse our
# collect_virtual method but tell it to use 'exported? for the test.
resources = collect_virtual(true).reject { |r| ! r.virtual? }
count = resources.length
query = build_active_record_query
# Now look them up in the rails db. When we support attribute comparison
# and such, we'll need to vary the conditions, but this works with no
# attributes, anyway.
time = Puppet::Util.thinmark do
Puppet::Rails::Resource.find(:all, query).each do |obj|
if resource = exported_resource(obj)
count += 1
resources << resource
end
end
end
scope.debug("Collected %s %s resource%s in %.2f seconds" %
[count, @type, count == 1 ? "" : "s", time])
return resources
end
def collect_resources
unless @resources.is_a?(Array)
@resources = [@resources]
end
method = "collect_#{form.to_s}_resources"
send(method)
end
def collect_exported_resources
raise Puppet::ParseError, "realize() is not yet implemented for exported resources"
end
# Collect resources directly; this is the result of using 'realize',
# which specifies resources, rather than using a normal collection.
def collect_virtual_resources
return [] unless defined?(@resources) and ! @resources.empty?
result = @resources.dup.collect do |ref|
if res = @scope.findresource(ref.to_s)
@resources.delete(ref)
res
end
end.reject { |r| r.nil? }.each do |res|
res.virtual = false
end
# If there are no more resources to find, delete this from the list
# of collections.
if @resources.empty?
@scope.compiler.delete_collection(self)
end
return result
end
# Collect just virtual objects, from our local compiler.
def collect_virtual(exported = false)
scope.compiler.resources.find_all do |resource|
resource.type == @type and (exported ? resource.exported? : true) and match?(resource)
end
end
# Seek a specific exported resource.
def exported_resource(obj)
if existing = @scope.findresource(obj.restype, obj.title)
# Next see if we've already collected this resource
return nil if existing.rails_id == obj.id
# This is the one we've already collected
raise Puppet::ParseError, "Exported resource %s cannot override local resource" % [obj.ref]
end
resource = obj.to_resource(self.scope)
resource.exported = false
scope.compiler.add_resource(scope, resource)
return resource
end
# Does the resource match our tests? We don't yet support tests,
# so it's always true at the moment.
def match?(resource)
if self.vquery
return self.vquery.call(resource)
else
return true
end
end
end
diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/compiler.rb
index c669076bb..8e84f5a5b 100644
--- a/lib/puppet/parser/compiler.rb
+++ b/lib/puppet/parser/compiler.rb
@@ -1,410 +1,410 @@
# Created by Luke A. Kanies on 2007-08-13.
# Copyright (c) 2007. All rights reserved.
require 'puppet/node'
require 'puppet/resource/catalog'
require 'puppet/util/errors'
require 'puppet/resource/type_collection_helper'
# Maintain a graph of scopes, along with a bunch of data
# about the individual catalog we're compiling.
class Puppet::Parser::Compiler
include Puppet::Util
include Puppet::Util::Errors
include Puppet::Resource::TypeCollectionHelper
def self.compile(node)
new(node).compile.to_resource
rescue => detail
puts detail.backtrace if Puppet[:trace]
raise Puppet::Error, "#{detail} on node #{node.name}"
end
attr_reader :node, :facts, :collections, :catalog, :node_scope, :resources
# Add a collection to the global list.
def add_collection(coll)
@collections << coll
end
# Store a resource override.
def add_override(override)
# If possible, merge the override in immediately.
if resource = @catalog.resource(override.ref)
resource.merge(override)
else
# Otherwise, store the override for later; these
# get evaluated in Resource#finish.
@resource_overrides[override.ref] << override
end
end
# Store a resource in our resource table.
def add_resource(scope, resource)
@resources << resource
# Note that this will fail if the resource is not unique.
@catalog.add_resource(resource)
# And in the resource graph. At some point, this might supercede
# the global resource table, but the table is a lot faster
# so it makes sense to maintain for now.
if resource.type.to_s.downcase == "class" and main = @catalog.resource(:class, :main)
@catalog.add_edge(main, resource)
else
@catalog.add_edge(scope.resource, resource)
end
end
# Do we use nodes found in the code, vs. the external node sources?
def ast_nodes?
known_resource_types.nodes?
end
# Store the fact that we've evaluated a class
def add_class(name)
@catalog.add_class(name) unless name == ""
end
# Return a list of all of the defined classes.
def classlist
return @catalog.classes
end
# Compiler our catalog. This mostly revolves around finding and evaluating classes.
# This is the main entry into our catalog.
def compile
# Set the client's parameters into the top scope.
set_node_parameters()
evaluate_main()
evaluate_ast_node()
evaluate_node_classes()
evaluate_generators()
finish()
fail_on_unevaluated()
return @catalog
end
# LAK:FIXME There are no tests for this.
def delete_collection(coll)
@collections.delete(coll) if @collections.include?(coll)
end
# Return the node's environment.
def environment
unless defined? @environment
if node.environment and node.environment != ""
@environment = node.environment
else
@environment = nil
end
end
@environment
end
# Evaluate all of the classes specified by the node.
def evaluate_node_classes
evaluate_classes(@node.classes, topscope)
end
# Evaluate each specified class in turn. If there are any classes we can't
# find, just tag the catalog and move on. This method really just
# creates resource objects that point back to the classes, and then the
# resources are themselves evaluated later in the process.
def evaluate_classes(classes, scope, lazy_evaluate = true)
unless scope.source
raise Puppet::DevError, "No source for scope passed to evaluate_classes"
end
found = []
classes.each do |name|
# If we can find the class, then make a resource that will evaluate it.
if klass = scope.find_hostclass(name)
found << name and next if scope.class_scope(klass)
resource = klass.mk_plain_resource(scope)
# If they've disabled lazy evaluation (which the :include function does),
# then evaluate our resource immediately.
resource.evaluate unless lazy_evaluate
found << name
else
Puppet.info "Could not find class %s for %s" % [name, node.name]
@catalog.tag(name)
end
end
found
end
# Return a resource by either its ref or its type and title.
def findresource(*args)
@catalog.resource(*args)
end
def initialize(node, options = {})
@node = node
options.each do |param, value|
begin
send(param.to_s + "=", value)
rescue NoMethodError
raise ArgumentError, "Compiler objects do not accept %s" % param
end
end
initvars()
init_main()
end
# Create a new scope, with either a specified parent scope or
# using the top scope.
def newscope(parent, options = {})
parent ||= topscope
options[:compiler] = self
scope = Puppet::Parser::Scope.new(options)
scope.parent = parent
scope
end
# Return any overrides for the given resource.
def resource_overrides(resource)
@resource_overrides[resource.ref]
end
# The top scope is usually the top-level scope, but if we're using AST nodes,
# then it is instead the node's scope.
def topscope
node_scope || @topscope
end
private
# If ast nodes are enabled, then see if we can find and evaluate one.
def evaluate_ast_node
return unless ast_nodes?
# Now see if we can find the node.
astnode = nil
@node.names.each do |name|
break if astnode = known_resource_types.node(name.to_s.downcase)
end
unless (astnode ||= known_resource_types.node("default"))
raise Puppet::ParseError, "Could not find default node or by name with '%s'" % node.names.join(", ")
end
# Create a resource to model this node, and then add it to the list
# of resources.
resource = astnode.mk_plain_resource(topscope)
resource.evaluate
# Now set the node scope appropriately, so that :topscope can
# behave differently.
@node_scope = topscope.class_scope(astnode)
end
# Evaluate our collections and return true if anything returned an object.
# The 'true' is used to continue a loop, so it's important.
def evaluate_collections
return false if @collections.empty?
found_something = false
exceptwrap do
# We have to iterate over a dup of the array because
# collections can delete themselves from the list, which
# changes its length and causes some collections to get missed.
@collections.dup.each do |collection|
found_something = true if collection.evaluate
end
end
return found_something
end
# Make sure all of our resources have been evaluated into native resources.
# We return true if any resources have, so that we know to continue the
# evaluate_generators loop.
def evaluate_definitions
exceptwrap do
if ary = unevaluated_resources
evaluated = false
ary.each do |resource|
if not resource.virtual?
resource.evaluate
evaluated = true
end
end
# If we evaluated, let the loop know.
return evaluated
else
return false
end
end
end
# Iterate over collections and resources until we're sure that the whole
# compile is evaluated. This is necessary because both collections
# and defined resources can generate new resources, which themselves could
# be defined resources.
def evaluate_generators
count = 0
loop do
done = true
# Call collections first, then definitions.
done = false if evaluate_collections
done = false if evaluate_definitions
break if done
count += 1
if count > 1000
raise Puppet::ParseError, "Somehow looped more than 1000 times while evaluating host catalog"
end
end
end
# Find and evaluate our main object, if possible.
def evaluate_main
- @main = known_resource_types.find_hostclass("", "") || known_resource_types.add(Puppet::Resource::Type.new(:hostclass, ""))
+ @main = known_resource_types.find_hostclass([""], "") || known_resource_types.add(Puppet::Resource::Type.new(:hostclass, ""))
@topscope.source = @main
- @main_resource = Puppet::Parser::Resource.new(:type => "class", :title => :main, :scope => @topscope, :source => @main)
+ @main_resource = Puppet::Parser::Resource.new("class", :main, :scope => @topscope, :source => @main)
@topscope.resource = @main_resource
@resources << @main_resource
@catalog.add_resource(@main_resource)
@main_resource.evaluate
end
# Make sure the entire catalog is evaluated.
def fail_on_unevaluated
fail_on_unevaluated_overrides
fail_on_unevaluated_resource_collections
end
# If there are any resource overrides remaining, then we could
# not find the resource they were supposed to override, so we
# want to throw an exception.
def fail_on_unevaluated_overrides
remaining = []
@resource_overrides.each do |name, overrides|
remaining += overrides
end
unless remaining.empty?
fail Puppet::ParseError,
"Could not find resource(s) %s for overriding" % remaining.collect { |o|
o.ref
}.join(", ")
end
end
# Make sure we don't have any remaining collections that specifically
# look for resources, because we want to consider those to be
# parse errors.
def fail_on_unevaluated_resource_collections
remaining = []
@collections.each do |coll|
# We're only interested in the 'resource' collections,
# which result from direct calls of 'realize'. Anything
# else is allowed not to return resources.
# Collect all of them, so we have a useful error.
if r = coll.resources
if r.is_a?(Array)
remaining += r
else
remaining << r
end
end
end
unless remaining.empty?
raise Puppet::ParseError, "Failed to realize virtual resources %s" %
remaining.join(', ')
end
end
# Make sure all of our resources and such have done any last work
# necessary.
def finish
resources.each do |resource|
# Add in any resource overrides.
if overrides = resource_overrides(resource)
overrides.each do |over|
resource.merge(over)
end
# Remove the overrides, so that the configuration knows there
# are none left.
overrides.clear
end
resource.finish if resource.respond_to?(:finish)
end
end
# Initialize the top-level scope, class, and resource.
def init_main
# Create our initial scope and a resource that will evaluate main.
@topscope = Puppet::Parser::Scope.new(:compiler => self)
end
# Set up all of our internal variables.
def initvars
# The list of objects that will available for export.
@exported_resources = {}
# The list of overrides. This is used to cache overrides on objects
# that don't exist yet. We store an array of each override.
@resource_overrides = Hash.new do |overs, ref|
overs[ref] = []
end
# The list of collections that have been created. This is a global list,
# but they each refer back to the scope that created them.
@collections = []
# For maintaining the relationship between scopes and their resources.
@catalog = Puppet::Resource::Catalog.new(@node.name)
@catalog.version = known_resource_types.version
# local resource array to maintain resource ordering
@resources = []
# Make sure any external node classes are in our class list
@catalog.add_class(*@node.classes)
end
# Set the node's parameters into the top-scope as variables.
def set_node_parameters
node.parameters.each do |param, value|
@topscope.setvar(param, value)
end
# These might be nil.
catalog.client_version = node.parameters["clientversion"]
catalog.server_version = node.parameters["serverversion"]
end
# Return an array of all of the unevaluated resources. These will be definitions,
# which need to get evaluated into native resources.
def unevaluated_resources
ary = resources.reject { |resource| resource.builtin? or resource.evaluated? }
if ary.empty?
return nil
else
return ary
end
end
end
diff --git a/lib/puppet/parser/functions/defined.rb b/lib/puppet/parser/functions/defined.rb
index 5ad74a79e..4d1d8c6fd 100644
--- a/lib/puppet/parser/functions/defined.rb
+++ b/lib/puppet/parser/functions/defined.rb
@@ -1,27 +1,27 @@
# Test whether a given class or definition is defined
Puppet::Parser::Functions::newfunction(:defined, :type => :rvalue, :doc => "Determine whether a given
type is defined, either as a native type or a defined type, or whether a class is defined.
This is useful for checking whether a class is defined and only including it if it is.
This function can also test whether a resource has been defined, using resource references
(e.g., ``if defined(File['/tmp/myfile']) { ... }``). This function is unfortunately
dependent on the parse order of the configuration when testing whether a resource is defined.") do |vals|
result = false
+ vals = [vals] unless vals.is_a?(Array)
vals.each do |val|
case val
when String
- # For some reason, it doesn't want me to return from here.
if Puppet::Type.type(val) or find_definition(val) or find_hostclass(val)
result = true
break
end
- when Puppet::Parser::Resource::Reference
+ when Puppet::Resource
if findresource(val.to_s)
result = true
break
end
else
- raise ArgumentError, "Invalid argument of type %s to 'defined'" % val.class
+ raise ArgumentError, "Invalid argument of type '#{val.class}' to 'defined'"
end
end
result
end
diff --git a/lib/puppet/parser/functions/require.rb b/lib/puppet/parser/functions/require.rb
index 66d60b9ce..294484a2b 100644
--- a/lib/puppet/parser/functions/require.rb
+++ b/lib/puppet/parser/functions/require.rb
@@ -1,56 +1,56 @@
# Requires the specified classes
Puppet::Parser::Functions::newfunction(:require,
:doc =>"Evaluate one or more classes, adding the required class as a dependency.
The relationship metaparameters work well for specifying relationships
between individual resources, but they can be clumsy for specifying
relationships between classes. This function is a superset of the
'include' function, adding a class relationship so that the requiring
class depends on the required class.
.. Warning::
using require in place of include can lead to unwanted dependency cycles.
For instance the following manifest, with 'require' instead of 'include'
would produce a nasty dependence cycle, because notify imposes a before
between File[/foo] and Service[foo]::
class myservice {
service { foo: ensure => running }
}
class otherstuff {
include myservice
file { '/foo': notify => Service[foo] }
}
Note that this function only works with clients 0.25 and later, and it will
fail if used with earlier clients.
") do |vals|
# Verify that the 'include' function is loaded
method = Puppet::Parser::Functions.function(:include)
send(method, vals)
if resource.metaparam_compatibility_mode?
warning "The 'require' function is only compatible with clients at 0.25 and above; including class but not adding dependency"
else
vals = [vals] unless vals.is_a?(Array)
vals.each do |klass|
# lookup the class in the scopes
if classobj = find_hostclass(klass)
klass = classobj.name
else
raise Puppet::ParseError, "Could not find class %s" % klass
end
# This is a bit hackish, in some ways, but it's the only way
# to configure a dependency that will make it to the client.
# The 'obvious' way is just to add an edge in the catalog,
# but that is considered a containment edge, not a dependency
# edge, so it usually gets lost on the client.
- ref = Puppet::Parser::Resource::Reference.new(:type => :class, :title => klass)
+ ref = Puppet::Resource.new(:class, klass)
resource.set_parameter(:require, [resource[:require]].flatten.compact << ref)
end
end
end
diff --git a/lib/puppet/parser/parser_support.rb b/lib/puppet/parser/parser_support.rb
index e6bc46b86..d43b0d37d 100644
--- a/lib/puppet/parser/parser_support.rb
+++ b/lib/puppet/parser/parser_support.rb
@@ -1,402 +1,391 @@
# I pulled this into a separate file, because I got
# tired of rebuilding the parser.rb file all the time.
class Puppet::Parser::Parser
require 'puppet/parser/functions'
require 'puppet/parser/files'
require 'puppet/resource/type_collection'
require 'puppet/resource/type_collection_helper'
require 'puppet/resource/type'
require 'monitor'
AST = Puppet::Parser::AST
include Puppet::Resource::TypeCollectionHelper
attr_reader :version, :environment
attr_accessor :files
attr_accessor :lexer
# Add context to a message; useful for error messages and such.
def addcontext(message, obj = nil)
obj ||= @lexer
message += " on line %s" % obj.line
if file = obj.file
message += " in file %s" % file
end
return message
end
# Create an AST array out of all of the args
def aryfy(*args)
if args[0].instance_of?(AST::ASTArray)
result = args.shift
args.each { |arg|
result.push arg
}
else
result = ast AST::ASTArray, :children => args
end
return result
end
# Create an AST object, and automatically add the file and line information if
# available.
def ast(klass, hash = {})
klass.new ast_context(klass.use_docs).merge(hash)
end
def ast_context(include_docs = false)
result = {
:line => lexer.line,
:file => lexer.file
}
result[:doc] = lexer.getcomment(result[:line]) if include_docs
result
end
# The fully qualifed name, with the full namespace.
def classname(name)
[@lexer.namespace, name].join("::").sub(/^::/, '')
end
def clear
initvars
end
# Raise a Parse error.
def error(message)
if brace = @lexer.expected
message += "; expected '%s'"
end
except = Puppet::ParseError.new(message)
except.line = @lexer.line
if @lexer.file
except.file = @lexer.file
end
raise except
end
def file
@lexer.file
end
def file=(file)
unless FileTest.exist?(file)
unless file =~ /\.pp$/
file = file + ".pp"
end
unless FileTest.exist?(file)
raise Puppet::Error, "Could not find file %s" % file
end
end
raise Puppet::AlreadyImportedError, "Import loop detected" if known_resource_types.watching_file?(file)
watch_file(file)
@lexer.file = file
end
[:hostclass, :definition, :node, :nodes?].each do |method|
define_method(method) do |*args|
known_resource_types.send(method, *args)
end
end
def find_hostclass(namespace, name)
find_or_load(namespace, name, :hostclass)
end
def find_definition(namespace, name)
find_or_load(namespace, name, :definition)
end
def find_or_load(namespace, name, type)
method = "find_#{type}"
namespace = namespace.downcase
name = name.downcase
fullname = (namespace + "::" + name).sub(/^::/, '')
if name =~ /^::/
names_to_try = [name.sub(/^::/, '')]
else
names_to_try = [fullname]
# Try to load the module init file if we're a qualified name
names_to_try << fullname.split("::")[0] if fullname.include?("::")
# Otherwise try to load the bare name on its own. This
# is appropriate if the class we're looking for is in a
# module that's different from our namespace.
names_to_try << name
names_to_try.compact!
end
until (result = known_resource_types.send(method, namespace, name)) or names_to_try.empty? do
self.load(names_to_try.shift)
end
return result
end
# Import our files.
def import(file)
if Puppet[:ignoreimport]
return AST::ASTArray.new(:children => [])
end
# use a path relative to the file doing the importing
if @lexer.file
dir = @lexer.file.sub(%r{[^/]+$},'').sub(/\/$/, '')
else
dir = "."
end
if dir == ""
dir = "."
end
result = ast AST::ASTArray
# We can't interpolate at this point since we don't have any
# scopes set up. Warn the user if they use a variable reference
raise "Got no file" unless file
pat = file
if pat.index("$")
Puppet.warning(
"The import of #{pat} contains a variable reference;" +
" variables are not interpolated for imports " +
"in file #{@lexer.file} at line #{@lexer.line}"
)
end
files = Puppet::Parser::Files.find_manifests(pat, :cwd => dir, :environment => @environment)
if files.size == 0
raise Puppet::ImportError.new("No file(s) found for import " +
"of '#{pat}'")
end
files.collect { |file|
parser = Puppet::Parser::Parser.new(@environment)
parser.files = self.files
Puppet.debug("importing '%s'" % file)
unless file =~ /^#{File::SEPARATOR}/
file = File.join(dir, file)
end
begin
parser.file = file
rescue Puppet::AlreadyImportedError
# This file has already been imported to just move on
next
end
# This will normally add code to the 'main' class.
parser.parse
}
end
def initialize(env)
# The environment is needed to know how to find the resource type collection.
@environment = env.is_a?(String) ? Puppet::Node::Environment.new(env) : env
initvars()
end
# Initialize or reset all of our variables.
def initvars
@lexer = Puppet::Parser::Lexer.new()
@files = {}
@loaded = []
@loading = {}
@loading.extend(MonitorMixin)
class << @loading
def done_with(item)
synchronize do
delete(item)[:busy].signal if self.has_key?(item) and self[item][:loader] == Thread.current
end
end
def owner_of(item)
synchronize do
if !self.has_key? item
self[item] = { :loader => Thread.current, :busy => self.new_cond}
:nobody
elsif self[item][:loader] == Thread.current
:this_thread
else
flag = self[item][:busy]
flag.wait
flag.signal
:another_thread
end
end
end
end
end
# Utility method factored out of load
def able_to_import?(classname,item,msg)
unless @loaded.include?(item)
begin
case @loading.owner_of(item)
when :this_thread
return
when :another_thread
return able_to_import?(classname,item,msg)
when :nobody
import(item)
Puppet.info "Autoloaded #{msg}"
@loaded << item
end
rescue Puppet::ImportError => detail
# We couldn't load the item
ensure
@loading.done_with(item)
end
end
# We don't know whether we're looking for a class or definition, so we have
# to test for both.
return known_resource_types.hostclass(classname) || known_resource_types.definition(classname)
end
# Try to load a class, since we could not find it.
def load(classname)
return false if classname == ""
filename = classname.gsub("::", File::SEPARATOR)
mod = filename.scan(/^[\w-]+/).shift
# First try to load the top-level module then the individual file
[[mod, "module %s" % mod ],
[filename,"file %s from module %s" % [filename, mod]]
].any? { |item,description| able_to_import?(classname,item,description) }
end
# Split an fq name into a namespace and name
def namesplit(fullname)
ary = fullname.split("::")
n = ary.pop || ""
ns = ary.join("::")
return ns, n
end
# Create a new class, or merge with an existing class.
def newclass(name, options = {})
known_resource_types.add Puppet::Resource::Type.new(:hostclass, name, ast_context(true).merge(options))
end
# Create a new definition.
def newdefine(name, options = {})
known_resource_types.add Puppet::Resource::Type.new(:definition, name, ast_context(true).merge(options))
end
# Create a new node. Nodes are special, because they're stored in a global
# table, not according to namespaces.
def newnode(names, options = {})
names = [names] unless names.instance_of?(Array)
context = ast_context(true)
names.collect do |name|
known_resource_types.add(Puppet::Resource::Type.new(:node, name, context.merge(options)))
end
end
def on_error(token,value,stack)
if token == 0 # denotes end of file
value = 'end of file'
else
value = "'%s'" % value[:value]
end
error = "Syntax error at %s" % [value]
if brace = @lexer.expected
error += "; expected '%s'" % brace
end
except = Puppet::ParseError.new(error)
except.line = @lexer.line
if @lexer.file
except.file = @lexer.file
end
raise except
end
# how should I do error handling here?
def parse(string = nil)
return parse_ruby_file if self.file =~ /\.rb$/
if string
self.string = string
end
begin
@yydebug = false
main = yyparse(@lexer,:scan)
rescue Racc::ParseError => except
error = Puppet::ParseError.new(except)
error.line = @lexer.line
error.file = @lexer.file
error.set_backtrace except.backtrace
raise error
rescue Puppet::ParseError => except
except.line ||= @lexer.line
except.file ||= @lexer.file
raise except
rescue Puppet::Error => except
# and this is a framework error
except.line ||= @lexer.line
except.file ||= @lexer.file
raise except
rescue Puppet::DevError => except
except.line ||= @lexer.line
except.file ||= @lexer.file
raise except
rescue => except
error = Puppet::DevError.new(except.message)
error.line = @lexer.line
error.file = @lexer.file
error.set_backtrace except.backtrace
raise error
end
if main
# Store the results as the top-level class.
newclass("", :code => main)
end
return known_resource_types
ensure
@lexer.clear
end
def parse_ruby_file
require self.file
end
# See if any of the files have changed.
def reparse?
if file = @files.detect { |name, file| file.changed? }
return file[1].stamp
else
return false
end
end
def string=(string)
@lexer.string = string
end
def version
known_resource_types.version
end
# Add a new file to be checked when we're checking to see if we should be
# reparsed. This is basically only used by the TemplateWrapper to let the
# parser know about templates that should be parsed.
def watch_file(filename)
known_resource_types.watch_file(filename)
end
-
- private
-
- def check_and_add_to_watched_files(filename)
- unless @files.include?(filename)
- @files[filename] = Puppet::Util::LoadedFile.new(filename)
- return true
- else
- return false
- end
- end
end
diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb
index 428b9df50..36f1fbe93 100644
--- a/lib/puppet/parser/resource.rb
+++ b/lib/puppet/parser/resource.rb
@@ -1,416 +1,377 @@
-# A resource that we're managing. This handles making sure that only subclasses
-# can set parameters.
-class Puppet::Parser::Resource
+require 'puppet/resource'
+
+# The primary difference between this class and its
+# parent is that this class has rules on who can set
+# parameters
+class Puppet::Parser::Resource < Puppet::Resource
require 'puppet/parser/resource/param'
- require 'puppet/parser/resource/reference'
+ require 'puppet/resource'
require 'puppet/util/tagging'
require 'puppet/file_collection/lookup'
require 'puppet/parser/yaml_trimmer'
include Puppet::FileCollection::Lookup
include Puppet::Util
include Puppet::Util::MethodHelper
include Puppet::Util::Errors
include Puppet::Util::Logging
include Puppet::Util::Tagging
include Puppet::Parser::YamlTrimmer
attr_accessor :source, :scope, :rails_id
attr_accessor :virtual, :override, :translated, :catalog
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|
+ [:translated, :override, :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 []=(param, value)
set_parameter(param, value)
end
- def builtin=(bool)
- @ref.builtin = bool
- end
-
def eachparam
@params.each do |name, param|
yield param
end
end
+ def environment
+ scope.environment
+ end
+
# Retrieve the associated definition and evaluate it.
def evaluate
- if klass = @ref.definedtype
+ if klass = resource_type and ! builtin_type?
finish()
return klass.evaluate_code(self)
elsif builtin?
- devfail "Cannot evaluate a builtin type"
+ devfail "Cannot evaluate a builtin type (#{type})"
else
- self.fail "Cannot find definition %s" % self.type
+ self.fail "Cannot find definition #{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)
+ def initialize(type, title, options)
+ self.type = type
+ self.title = title
+
+ @params = {}
+ # Define all of the parameters
+ if params = options[:params]
+ extract_parameters(params)
+ options.delete(:params)
+ end
+
# 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)
+ else
+ raise ArgumentError, "Resources do not accept #{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|
- # Don't set the same parameter twice
- if @params[param.name]
- self.fail Puppet::ParseError, "Duplicate parameter '%s' for on %s" %
- [param.name, self.to_s]
- end
-
- 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
+ @source ||= scope.source
- tag(@ref.type)
- tag(@ref.title) if valid_tag?(@ref.title.to_s)
+ tag(self.type)
+ tag(self.title) if valid_tag?(self.title.to_s)
end
# Is this resource modeling an isomorphic resource type?
def isomorphic?
- if builtin?
- return @ref.builtintype.isomorphic?
+ if builtin_type?
+ return resource_type.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
# Unless we're running >= 0.25, we're in compat mode.
def metaparam_compatibility_mode?
! (catalog and version = catalog.client_version and version = version.split(".") and version[0] == "0" and version[1].to_i >= 25)
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
+ def namespaces
+ scope.namespaces
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.
# if we ever receive a parameter named 'tag', set
# the resource tags with its value.
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
tag(*param.value) if param.name == :tag
# 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
# Create a Puppet::Resource instance from this parser resource.
# We plan, at some point, on not needing to do this conversion, but
# it's sufficient for now.
def to_resource
result = Puppet::Resource.new(type, title)
to_hash.each do |p, v|
- if v.is_a?(Puppet::Parser::Resource::Reference)
- v = Puppet::Resource::Reference.new(v.type, v.title)
+ if v.is_a?(Puppet::Resource)
+ v = Puppet::Resource.new(v.type, v.title)
elsif v.is_a?(Array)
# flatten resource references arrays
- if v.flatten.find { |av| av.is_a?(Puppet::Parser::Resource::Reference) }
+ if v.flatten.find { |av| av.is_a?(Puppet::Resource) }
v = v.flatten
end
v = v.collect do |av|
- if av.is_a?(Puppet::Parser::Resource::Reference)
- av = Puppet::Resource::Reference.new(av.type, av.title)
+ if av.is_a?(Puppet::Resource)
+ av = Puppet::Resource.new(av.type, av.title)
end
av
end
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.
result[p] = if v.is_a?(Array) and v.length == 1
v[0]
else
v
end
end
result.file = self.file
result.line = self.line
result.exported = self.exported
result.virtual = self.virtual
result.tag(*self.tags)
return result
end
- def to_s
- self.ref
- end
-
# Translate our object to a transportable object.
def to_trans
return nil if virtual?
return to_resource.to_trans
end
# Convert this resource to a RAL resource. We hackishly go via the
# transportable stuff.
def to_ral
to_resource.to_ral
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
def add_backward_compatible_relationship_param(name)
# Skip metaparams for which we get no value.
return unless val = scope.lookupvar(name.to_s, false) and val != :undefined
# The default case: just set the value
set_parameter(name, val) and return unless @params[name]
# For relationship params, though, join the values (a la #446).
@params[name].value = [@params[name].value, val].flatten
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
compat_mode = metaparam_compatibility_mode?
Puppet::Type.eachmetaparam do |name|
if self.class.relationship_parameter?(name)
add_backward_compatible_relationship_param(name) if compat_mode
next
end
next if @params[name]
# Skip metaparams for which we get no value.
next unless val = scope.lookupvar(name.to_s, false) and val != :undefined
set_parameter(name, val)
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.
(set_parameter(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
set_parameter(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.valid_parameter?(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
-
# 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)
+ validate_parameter(name)
+ end
+ rescue => detail
+ fail Puppet::ParseError, detail.to_s
+ end
+
+ private
+
+ def extract_parameters(params)
+ params.each do |param|
+ # Don't set the same parameter twice
+ if @params[param.name]
+ self.fail Puppet::ParseError, "Duplicate parameter '%s' for on %s" %
+ [param.name, self.to_s]
+ end
+
+ set_parameter(param)
end
end
end
diff --git a/lib/puppet/parser/resource/reference.rb b/lib/puppet/parser/resource/reference.rb
deleted file mode 100644
index ac1c79aef..000000000
--- a/lib/puppet/parser/resource/reference.rb
+++ /dev/null
@@ -1,103 +0,0 @@
-# A reference to a resource. Mostly just the type and title.
-require 'puppet/resource/reference'
-require 'puppet/file_collection/lookup'
-require 'puppet/parser/yaml_trimmer'
-
-require 'puppet/resource/type_collection_helper'
-
-# A reference to a resource. Mostly just the type and title.
-class Puppet::Parser::Resource::Reference < Puppet::Resource::Reference
- include Puppet::Parser::YamlTrimmer
- include Puppet::FileCollection::Lookup
- include Puppet::Util::MethodHelper
- include Puppet::Util::Errors
- include Puppet::Resource::TypeCollectionHelper
-
- 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
- name = self.title == :main ? "" : self.title
- unless tmp = known_resource_types.find_hostclass("", name)
- fail Puppet::ParseError, "Could not find '#{title}' class"
- end
- when "Node" # look for node definitions
- unless tmp = known_resource_types.node(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 = known_resource_types.definition(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 environment
- scope.environment
- end
-
- def initialize(hash)
- set_options(hash)
- requiredopts(:type, :title)
- end
-
- def skip_for_yaml
- %w{@typeclass @definedtype}
- 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/rails/param_value.rb b/lib/puppet/rails/param_value.rb
index b298924f3..d0bb8d7d3 100644
--- a/lib/puppet/rails/param_value.rb
+++ b/lib/puppet/rails/param_value.rb
@@ -1,74 +1,74 @@
require 'puppet/util/rails/reference_serializer'
class Puppet::Rails::ParamValue < ActiveRecord::Base
include Puppet::Util::ReferenceSerializer
extend Puppet::Util::ReferenceSerializer
belongs_to :param_name
belongs_to :resource
# Store a new parameter in a Rails db.
def self.from_parser_param(param, values)
values = munge_parser_values(values)
param_name = Puppet::Rails::ParamName.find_or_create_by_name(param.to_s)
return values.collect do |v|
{:value => v, :param_name => param_name}
end
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 self.munge_parser_values(value)
values = value.is_a?(Array) ? value : [value]
values.map do |v|
- if v.is_a?(Puppet::Resource::Reference)
+ if v.is_a?(Puppet::Resource)
v
else
v.to_s
end
end
end
def value
unserialize_value(self[:value])
end
# I could not find a cleaner way to handle making sure that resource references
# were consistently serialized and deserialized.
def value=(val)
self[:value] = serialize_value(val)
end
def to_label
"#{self.param_name.name}"
end
# returns an array of hash containing all the parameters of a given resource
def self.find_all_params_from_resource(db_resource)
params = db_resource.connection.select_all("SELECT v.id, v.value, v.line, v.resource_id, v.param_name_id, n.name FROM param_values v INNER JOIN param_names n ON v.param_name_id=n.id WHERE v.resource_id=%s" % db_resource.id)
params.each do |val|
val['value'] = unserialize_value(val['value'])
val['line'] = val['line'] ? Integer(val['line']) : nil
val['resource_id'] = Integer(val['resource_id'])
end
params
end
# returns an array of hash containing all the parameters of a given host
def self.find_all_params_from_host(db_host)
params = db_host.connection.select_all("SELECT v.id, v.value, v.line, v.resource_id, v.param_name_id, n.name FROM param_values v INNER JOIN resources r ON v.resource_id=r.id INNER JOIN param_names n ON v.param_name_id=n.id WHERE r.host_id=%s" % db_host.id)
params.each do |val|
val['value'] = unserialize_value(val['value'])
val['line'] = val['line'] ? Integer(val['line']) : nil
val['resource_id'] = Integer(val['resource_id'])
end
params
end
def to_s
"%s => %s" % [self.name, self.value]
end
end
diff --git a/lib/puppet/resource.rb b/lib/puppet/resource.rb
index 010cd956e..ec1fd2eae 100644
--- a/lib/puppet/resource.rb
+++ b/lib/puppet/resource.rb
@@ -1,325 +1,372 @@
require 'puppet'
require 'puppet/util/tagging'
require 'puppet/util/pson'
# The simplest resource class. Eventually it will function as the
# base class for all resource-like behaviour.
class Puppet::Resource
- require 'puppet/resource/reference'
include Puppet::Util::Tagging
require 'puppet/resource/type_collection_helper'
include Puppet::Resource::TypeCollectionHelper
extend Puppet::Util::Pson
include Enumerable
- attr_accessor :file, :line, :catalog, :exported, :virtual, :namespace, :validate_parameters
- attr_writer :type, :title, :environment
+ attr_accessor :file, :line, :catalog, :exported, :virtual, :validate_parameters
+ attr_reader :type, :title, :namespaces
require 'puppet/indirector'
extend Puppet::Indirector
indirects :resource, :terminus_class => :ral
ATTRIBUTES = [:file, :line, :exported]
def self.from_pson(pson)
raise ArgumentError, "No resource type provided in pson data" unless type = pson['type']
raise ArgumentError, "No resource title provided in pson data" unless title = pson['title']
resource = new(type, title)
if params = pson['parameters']
params.each { |param, value| resource[param] = value }
end
if tags = pson['tags']
tags.each { |tag| resource.tag(tag) }
end
ATTRIBUTES.each do |a|
if value = pson[a.to_s]
resource.send(a.to_s + "=", value)
end
end
resource.exported ||= false
resource
end
def to_pson_data_hash
data = ([:type, :title, :tags] + ATTRIBUTES).inject({}) do |hash, param|
next hash unless value = self.send(param)
hash[param.to_s] = value
hash
end
data["exported"] ||= false
params = self.to_hash.inject({}) do |hash, ary|
param, value = ary
# Don't duplicate the title as the namevar
next hash if param == namevar and value == title
hash[param] = value
hash
end
data["parameters"] = params unless params.empty?
data
end
def to_pson(*args)
to_pson_data_hash.to_pson(*args)
end
# Proxy these methods to the parameters hash. It's likely they'll
# be overridden at some point, but this works for now.
%w{has_key? keys length delete empty? <<}.each do |method|
define_method(method) do |*args|
@parameters.send(method, *args)
end
end
# Set a given parameter. Converts all passed names
# to lower-case symbols.
def []=(param, value)
validate_parameter(param) if validate_parameters
@parameters[parameter_name(param)] = value
end
# Return a given parameter's value. Converts all passed names
# to lower-case symbols.
def [](param)
@parameters[parameter_name(param)]
end
+ def ==(other)
+ return false unless other.respond_to?(:title) and self.type == other.type and self.title == other.title
+
+ return false unless to_hash == other.to_hash
+ true
+ end
+
# Compatibility method.
def builtin?
builtin_type?
end
# Is this a builtin resource type?
def builtin_type?
- @reference.builtin_type?
+ resource_type.is_a?(Class)
end
# Iterate over each param/value pair, as required for Enumerable.
def each
@parameters.each { |p,v| yield p, v }
end
def include?(parameter)
super || @parameters.keys.include?( parameter_name(parameter) )
end
+ # These two methods are extracted into a Helper
+ # module, but file load order prevents me
+ # from including them in the class, and I had weird
+ # behaviour (i.e., sometimes it didn't work) when
+ # I directly extended each resource with the helper.
+ def environment
+ Puppet::Node::Environment.new(@environment)
+ end
+
+ def environment=(env)
+ if env.is_a?(String) or env.is_a?(Symbol)
+ @environment = env
+ else
+ @environment = env.name
+ end
+ end
+
%w{exported virtual}.each do |m|
define_method(m+"?") do
self.send(m)
end
end
# Create our resource.
- def initialize(type, title, attributes = {})
- # Doing this, instead of including it in the class,
- # is the only way I could get the load order to work
- # here.
- extend Puppet::Node::Environment::Helper
+ def initialize(type, title = nil, attributes = {})
+ self.type, self.title = extract_type_and_title(type, title)
@parameters = {}
- @namespace = ""
+ @namespaces = [""]
(attributes[:parameters] || {}).each do |param, value|
self[param] = value
end
attributes.each do |attr, value|
next if attr == :parameters
send(attr.to_s + "=", value)
end
- @reference = Puppet::Resource::Reference.new(type, title)
-
- tag(@reference.type)
- tag(@reference.title) if valid_tag?(@reference.title)
+ tag(self.type)
+ tag(self.title) if valid_tag?(self.title)
end
- # Provide a reference to our resource in the canonical form.
def ref
- @reference.to_s
+ to_s
+ end
+
+ # Find our resource.
+ def resolve
+ return catalog.resource(to_s) if catalog
+ return nil
+ end
+
+ def title=(value)
+ if @type and klass = Puppet::Type.type(@type.to_s.downcase)
+ value = klass.canonicalize_ref(value)
+ end
+ @title = value
end
def resource_type
case type.to_s.downcase
when "class"; find_hostclass
when "node"; find_node
else
find_builtin_resource_type || find_defined_resource_type
end
end
- # Get our title information from the reference, since it will canonize it for us.
- def title
- @reference.title
- end
-
- # Get our type information from the reference, since it will canonize it for us.
- def type
- @reference.type
- end
-
# Produce a simple hash of our parameters.
def to_hash
result = @parameters.dup
unless result.include?(namevar)
result[namevar] = title
end
result
end
def to_s
- return ref
+ "#{type}[#{title}]"
end
# Convert our resource to Puppet code.
def to_manifest
"%s { '%s':\n%s\n}" % [self.type.to_s.downcase, self.title,
@parameters.collect { |p, v|
if v.is_a? Array
" #{p} => [\'#{v.join("','")}\']"
else
" #{p} => \'#{v}\'"
end
}.join(",\n")
]
end
def to_ref
ref
end
# Convert our resource to a RAL resource instance. Creates component
# instances for resource types that don't exist.
def to_ral
if typeklass = Puppet::Type.type(self.type)
return typeklass.new(self)
else
return Puppet::Type::Component.new(self)
end
end
# Translate our object to a backward-compatible transportable object.
def to_trans
- if @reference.builtin_type?
+ if builtin_type?
result = to_transobject
else
result = to_transbucket
end
result.file = self.file
result.line = self.line
return result
end
+ def to_trans_ref
+ [type.to_s, title.to_s]
+ end
+
# Create an old-style TransObject instance, for builtin resource types.
def to_transobject
# Now convert to a transobject
- result = Puppet::TransObject.new(@reference.title, @reference.type)
+ result = Puppet::TransObject.new(title, type)
to_hash.each do |p, v|
- if v.is_a?(Puppet::Resource::Reference)
+ if v.is_a?(Puppet::Resource)
v = v.to_trans_ref
elsif v.is_a?(Array)
v = v.collect { |av|
- if av.is_a?(Puppet::Resource::Reference)
+ if av.is_a?(Puppet::Resource)
av = av.to_trans_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.
result[p.to_s] = if v.is_a?(Array) and v.length == 1
v[0]
else
v
end
end
result.tags = self.tags
return result
end
def name
# this is potential namespace conflict
# between the notion of an "indirector name"
# and a "resource name"
[ type, title ].join('/')
end
def to_resource
self
end
+ # Canonize the type so we know it's always consistent.
+ def type=(value)
+ if value.nil? or value.to_s.downcase == "component"
+ @type = "Class"
+ else
+ @type = value.to_s.split("::").collect { |s| s.capitalize }.join("::")
+ end
+ end
+
def valid_parameter?(name)
resource_type.valid_parameter?(name)
end
def validate_parameter(name)
raise ArgumentError, "Invalid parameter #{name}" unless valid_parameter?(name)
end
private
def find_node
known_resource_types.node(title)
end
def find_hostclass
name = title == :main ? "" : title
- known_resource_types.find_hostclass(namespace, name)
+ known_resource_types.find_hostclass(namespaces, name)
end
def find_builtin_resource_type
Puppet::Type.type(type.to_s.downcase.to_sym)
end
def find_defined_resource_type
- known_resource_types.find_definition(namespace, type.to_s.downcase)
+ known_resource_types.find_definition(namespaces, type.to_s.downcase)
end
# Produce a canonical method name.
def parameter_name(param)
param = param.to_s.downcase.to_sym
if param == :name and n = namevar()
param = namevar
end
param
end
+ def namespaces=(ns)
+ @namespaces = Array(ns)
+ end
+
# The namevar for our resource type. If the type doesn't exist,
# always use :name.
def namevar
- if t = resource_type
+ if builtin_type? and t = resource_type
t.namevar
else
:name
end
end
# Create an old-style TransBucket instance, for non-builtin resource types.
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
+
+ private
+
+ def extract_type_and_title(argtype, argtitle)
+ if (argtitle || argtype) =~ /^([^\[\]]+)\[(.+)\]$/m then [ $1, $2 ]
+ elsif argtitle then [ argtype, argtitle ]
+ elsif argtype.is_a?(Puppet::Type) then [ argtype.class.name, argtype.title ]
+ else raise ArgumentError, "No title provided and #{argtype.inspect} is not a valid resource reference"
+ end
+ end
end
diff --git a/lib/puppet/resource/catalog.rb b/lib/puppet/resource/catalog.rb
index 6e064a9a1..e63c00c62 100644
--- a/lib/puppet/resource/catalog.rb
+++ b/lib/puppet/resource/catalog.rb
@@ -1,616 +1,616 @@
require 'puppet/resource'
require 'puppet/node'
require 'puppet/indirector'
require 'puppet/simple_graph'
require 'puppet/transaction'
require 'puppet/util/cacher'
require 'puppet/util/pson'
require 'puppet/util/tagging'
# This class models a node catalog. It is the thing
# meant to be passed from server to client, and it contains all
# of the information in the catalog, including the resources
# and the relationships between them.
class Puppet::Resource::Catalog < Puppet::SimpleGraph
class DuplicateResourceError < Puppet::Error; end
extend Puppet::Indirector
indirects :catalog, :terminus_class => :compiler
include Puppet::Util::Tagging
extend Puppet::Util::Pson
include Puppet::Util::Cacher::Expirer
# The host name this is a catalog for.
attr_accessor :name
# The catalog version. Used for testing whether a catalog
# is up to date.
attr_accessor :version
# How long this catalog took to retrieve. Used for reporting stats.
attr_accessor :retrieval_duration
# How we should extract the catalog for sending to the client.
attr_reader :extraction_format
# Whether this is a host catalog, which behaves very differently.
# In particular, reports are sent, graphs are made, and state is
# stored in the state database. If this is set incorrectly, then you often
# end up in infinite loops, because catalogs are used to make things
# that the host catalog needs.
attr_accessor :host_config
# Whether this catalog was retrieved from the cache, which affects
# whether it is written back out again.
attr_accessor :from_cache
# Some metadata to help us compile and generally respond to the current state.
attr_accessor :client_version, :server_version
# Add classes to our class list.
def add_class(*classes)
classes.each do |klass|
@classes << klass
end
# Add the class names as tags, too.
tag(*classes)
end
# Add one or more resources to our graph and to our resource table.
# This is actually a relatively complicated method, because it handles multiple
# aspects of Catalog behaviour:
# * Add the resource to the resource table
# * Add the resource to the resource graph
# * Add the resource to the relationship graph
# * Add any aliases that make sense for the resource (e.g., name != title)
def add_resource(*resources)
resources.each do |resource|
unless resource.respond_to?(:ref)
raise ArgumentError, "Can only add objects that respond to :ref, not instances of %s" % resource.class
end
end.each { |resource| fail_unless_unique(resource) }.each do |resource|
ref = resource.ref
@transient_resources << resource if applying?
@resource_table[ref] = resource
# If the name and title differ, set up an alias
if resource.respond_to?(:name) and resource.respond_to?(:title) and resource.respond_to?(:isomorphic?) and resource.name != resource.title
self.alias(resource, resource.name) if resource.isomorphic?
end
resource.catalog = self if resource.respond_to?(:catalog=)
add_vertex(resource)
if @relationship_graph
@relationship_graph.add_vertex(resource)
end
yield(resource) if block_given?
end
end
# Create an alias for a resource.
def alias(resource, name)
#set $1
resource.ref =~ /^(.+)\[/
newref = "%s[%s]" % [$1 || resource.class.name, name]
# LAK:NOTE It's important that we directly compare the references,
# because sometimes an alias is created before the resource is
# added to the catalog, so comparing inside the below if block
# isn't sufficient.
return if newref == resource.ref
if existing = @resource_table[newref]
return if existing == resource
raise(ArgumentError, "Cannot alias %s to %s; resource %s already exists" % [resource.ref, name, newref])
end
@resource_table[newref] = resource
@aliases[resource.ref] ||= []
@aliases[resource.ref] << newref
end
# Apply our catalog to the local host. Valid options
# are:
# :tags - set the tags that restrict what resources run
# during the transaction
# :ignoreschedules - tell the transaction to ignore schedules
# when determining the resources to run
def apply(options = {})
@applying = true
# Expire all of the resource data -- this ensures that all
# data we're operating against is entirely current.
expire()
Puppet::Util::Storage.load if host_config?
transaction = Puppet::Transaction.new(self)
transaction.tags = options[:tags] if options[:tags]
transaction.ignoreschedules = true if options[:ignoreschedules]
transaction.add_times :config_retrieval => self.retrieval_duration || 0
begin
transaction.evaluate
rescue Puppet::Error => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Could not apply complete catalog: %s" % detail
rescue => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Got an uncaught exception of type %s: %s" % [detail.class, detail]
ensure
# Don't try to store state unless we're a host config
# too recursive.
Puppet::Util::Storage.store if host_config?
end
yield transaction if block_given?
return transaction
ensure
@applying = false
cleanup()
transaction.cleanup if defined? transaction and transaction
end
# Are we in the middle of applying the catalog?
def applying?
@applying
end
def clear(remove_resources = true)
super()
# We have to do this so that the resources clean themselves up.
@resource_table.values.each { |resource| resource.remove } if remove_resources
@resource_table.clear
if defined?(@relationship_graph) and @relationship_graph
@relationship_graph.clear
@relationship_graph = nil
end
end
def classes
@classes.dup
end
# Create a new resource and register it in the catalog.
def create_resource(type, options)
unless klass = Puppet::Type.type(type)
raise ArgumentError, "Unknown resource type %s" % type
end
return unless resource = klass.new(options)
add_resource(resource)
resource
end
def dependent_data_expired?(ts)
if applying?
return super
else
return true
end
end
# Make sure we support the requested extraction format.
def extraction_format=(value)
unless respond_to?("extract_to_%s" % value)
raise ArgumentError, "Invalid extraction format %s" % value
end
@extraction_format = value
end
# Turn our catalog graph into whatever the client is expecting.
def extract
send("extract_to_%s" % extraction_format)
end
# Create the traditional TransBuckets and TransObjects from our catalog
# graph. LAK:NOTE(20081211): This is a pre-0.25 backward compatibility method.
# It can be removed as soon as xmlrpc is killed.
def extract_to_transportable
top = nil
current = nil
buckets = {}
unless main = vertices.find { |res| res.type == "Class" and res.title == :main }
raise Puppet::DevError, "Could not find 'main' class; cannot generate catalog"
end
# Create a proc for examining edges, which we'll use to build our tree
# of TransBuckets and TransObjects.
bucket = nil
walk(main, :out) do |source, target|
# The sources are always non-builtins.
unless tmp = buckets[source.to_s]
if tmp = buckets[source.to_s] = source.to_trans
bucket = tmp
else
# This is because virtual resources return nil. If a virtual
# container resource contains realized resources, we still need to get
# to them. So, we keep a reference to the last valid bucket
# we returned and use that if the container resource is virtual.
end
end
bucket = tmp || bucket
if child = target.to_trans
unless bucket
raise "No bucket created for %s" % source
end
bucket.push child
# It's important that we keep a reference to any TransBuckets we've created, so
# we don't create multiple buckets for children.
unless target.builtin?
buckets[target.to_s] = child
end
end
end
# Retrieve the bucket for the top-level scope and set the appropriate metadata.
unless result = buckets[main.to_s]
# This only happens when the catalog is entirely empty.
result = buckets[main.to_s] = main.to_trans
end
result.classes = classes
# Clear the cache to encourage the GC
buckets.clear
return result
end
# Make sure all of our resources are "finished".
def finalize
make_default_resources
@resource_table.values.each { |resource| resource.finish }
write_graph(:resources)
end
def host_config?
host_config || false
end
def initialize(name = nil)
super()
@name = name if name
@extraction_format ||= :transportable
@classes = []
@resource_table = {}
@transient_resources = []
@applying = false
@relationship_graph = nil
@aliases = {}
if block_given?
yield(self)
finalize()
end
end
# Make the default objects necessary for function.
def make_default_resources
# We have to add the resources to the catalog, or else they won't get cleaned up after
# the transaction.
# First create the default scheduling objects
Puppet::Type.type(:schedule).mkdefaultschedules.each { |res| add_resource(res) unless resource(res.ref) }
# And filebuckets
if bucket = Puppet::Type.type(:filebucket).mkdefaultbucket
add_resource(bucket) unless resource(bucket.ref)
end
end
# Create a graph of all of the relationships in our catalog.
def relationship_graph
unless defined? @relationship_graph and @relationship_graph
# It's important that we assign the graph immediately, because
# the debug messages below use the relationships in the
# relationship graph to determine the path to the resources
# spitting out the messages. If this is not set,
# then we get into an infinite loop.
@relationship_graph = Puppet::SimpleGraph.new
# First create the dependency graph
self.vertices.each do |vertex|
@relationship_graph.add_vertex vertex
vertex.builddepends.each do |edge|
@relationship_graph.add_edge(edge)
end
end
# Lastly, add in any autorequires
@relationship_graph.vertices.each do |vertex|
vertex.autorequire(self).each do |edge|
unless @relationship_graph.edge?(edge.source, edge.target) # don't let automatic relationships conflict with manual ones.
unless @relationship_graph.edge?(edge.target, edge.source)
vertex.debug "Autorequiring %s" % [edge.source]
@relationship_graph.add_edge(edge)
else
vertex.debug "Skipping automatic relationship with %s" % (edge.source == vertex ? edge.target : edge.source)
end
end
end
end
@relationship_graph.write_graph(:relationships) if host_config?
# Then splice in the container information
@relationship_graph.splice!(self, Puppet::Type::Component)
@relationship_graph.write_graph(:expanded_relationships) if host_config?
end
@relationship_graph
end
# Remove the resource from our catalog. Notice that we also call
# 'remove' on the resource, at least until resource classes no longer maintain
# references to the resource instances.
def remove_resource(*resources)
resources.each do |resource|
@resource_table.delete(resource.ref)
if aliases = @aliases[resource.ref]
aliases.each { |res_alias| @resource_table.delete(res_alias) }
@aliases.delete(resource.ref)
end
remove_vertex!(resource) if vertex?(resource)
@relationship_graph.remove_vertex!(resource) if @relationship_graph and @relationship_graph.vertex?(resource)
resource.remove
end
end
# Look a resource up by its reference (e.g., File[/etc/passwd]).
def resource(type, title = nil)
# Always create a resource reference, so that it always canonizes how we
# are referring to them.
if title
- ref = Puppet::Resource::Reference.new(type, title).to_s
+ ref = Puppet::Resource.new(type, title).to_s
else
# If they didn't provide a title, then we expect the first
# argument to be of the form 'Class[name]', which our
# Reference class canonizes for us.
- ref = Puppet::Resource::Reference.new(nil, type).to_s
+ ref = Puppet::Resource.new(nil, type).to_s
end
@resource_table[ref]
end
# Return an array of all resources.
def resources
@resource_table.keys
end
def self.from_pson(data)
result = new(data['name'])
if tags = data['tags']
result.tag(*tags)
end
if version = data['version']
result.version = version
end
if resources = data['resources']
resources = PSON.parse(resources) if resources.is_a?(String)
resources.each do |res|
resource_from_pson(result, res)
end
end
if edges = data['edges']
edges = PSON.parse(edges) if edges.is_a?(String)
edges.each do |edge|
edge_from_pson(result, edge)
end
end
if classes = data['classes']
result.add_class(*classes)
end
result
end
def self.edge_from_pson(result, edge)
# If no type information was presented, we manually find
# the class.
edge = Puppet::Relationship.from_pson(edge) if edge.is_a?(Hash)
unless source = result.resource(edge.source)
raise ArgumentError, "Could not convert from pson: Could not find relationship source #{edge.source.inspect}"
end
edge.source = source
unless target = result.resource(edge.target)
raise ArgumentError, "Could not convert from pson: Could not find relationship target #{edge.target.inspect}"
end
edge.target = target
result.add_edge(edge)
end
def self.resource_from_pson(result, res)
res = Puppet::Resource.from_pson(res) if res.is_a? Hash
result.add_resource(res)
end
PSON.register_document_type('Catalog',self)
def to_pson_data_hash
{
'document_type' => 'Catalog',
'data' => {
'tags' => tags,
'name' => name,
'version' => version,
'resources' => vertices.collect { |v| v.to_pson_data_hash },
'edges' => edges. collect { |e| e.to_pson_data_hash },
'classes' => classes
},
'metadata' => {
'api_version' => 1
}
}
end
def to_pson(*args)
to_pson_data_hash.to_pson(*args)
end
# Convert our catalog into a RAL catalog.
def to_ral
to_catalog :to_ral
end
# Convert our catalog into a catalog of Puppet::Resource instances.
def to_resource
to_catalog :to_resource
end
# filter out the catalog, applying +block+ to each resource.
# If the block result is false, the resource will
# be kept otherwise it will be skipped
def filter(&block)
to_catalog :to_resource, &block
end
# Store the classes in the classfile.
def write_class_file
begin
::File.open(Puppet[:classfile], "w") do |f|
f.puts classes.join("\n")
end
rescue => detail
Puppet.err "Could not create class file %s: %s" % [Puppet[:classfile], detail]
end
end
# Produce the graph files if requested.
def write_graph(name)
# We only want to graph the main host catalog.
return unless host_config?
super
end
private
def cleanup
unless @transient_resources.empty?
remove_resource(*@transient_resources)
@transient_resources.clear
@relationship_graph = nil
end
# Expire any cached data the resources are keeping.
expire()
end
# Verify that the given resource isn't defined elsewhere.
def fail_unless_unique(resource)
# Short-curcuit the common case,
return unless existing_resource = @resource_table[resource.ref]
# If we've gotten this far, it's a real conflict
# Either it's a defined type, which are never
# isomorphic, or it's a non-isomorphic type, so
# we should throw an exception.
msg = "Duplicate definition: %s is already defined" % resource.ref
if existing_resource.file and existing_resource.line
msg << " in file %s at line %s" %
[existing_resource.file, existing_resource.line]
end
if resource.line or resource.file
msg << "; cannot redefine"
end
raise DuplicateResourceError.new(msg)
end
# An abstracted method for converting one catalog into another type of catalog.
# This pretty much just converts all of the resources from one class to another, using
# a conversion method.
def to_catalog(convert)
result = self.class.new(self.name)
result.version = self.version
map = {}
vertices.each do |resource|
next if virtual_not_exported?(resource)
next if block_given? and yield resource
#This is hackity hack for 1094
#Aliases aren't working in the ral catalog because the current instance of the resource
#has a reference to the catalog being converted. . . So, give it a reference to the new one
#problem solved. . .
if resource.is_a?(Puppet::Resource)
resource = resource.dup
resource.catalog = result
elsif resource.is_a?(Puppet::TransObject)
resource = resource.dup
resource.catalog = result
elsif resource.is_a?(Puppet::Parser::Resource)
resource = resource.to_resource
resource.catalog = result
end
if resource.is_a?(Puppet::Resource) and convert.to_s == "to_resource"
newres = resource
else
newres = resource.send(convert)
end
# We can't guarantee that resources don't munge their names
# (like files do with trailing slashes), so we have to keep track
# of what a resource got converted to.
map[resource.ref] = newres
result.add_resource newres
end
message = convert.to_s.gsub "_", " "
edges.each do |edge|
# Skip edges between virtual resources.
next if virtual_not_exported?(edge.source)
next if block_given? and yield edge.source
next if virtual_not_exported?(edge.target)
next if block_given? and yield edge.target
unless source = map[edge.source.ref]
raise Puppet::DevError, "Could not find resource %s when converting %s resources" % [edge.source.ref, message]
end
unless target = map[edge.target.ref]
raise Puppet::DevError, "Could not find resource %s when converting %s resources" % [edge.target.ref, message]
end
result.add_edge(source, target, edge.label)
end
map.clear
result.add_class(*self.classes)
result.tag(*self.tags)
return result
end
def virtual_not_exported?(resource)
resource.respond_to?(:virtual?) and resource.virtual? and (resource.respond_to?(:exported?) and not resource.exported?)
end
end
diff --git a/lib/puppet/resource/reference.rb b/lib/puppet/resource/reference.rb
deleted file mode 100644
index eddb2d96b..000000000
--- a/lib/puppet/resource/reference.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-#
-# Created by Luke Kanies on 2007-11-28.
-# Copyright (c) 2007. All rights reserved.
-
-require 'puppet'
-require 'puppet/resource'
-
-# A simple class to canonize how we refer to and retrieve
-# resources.
-class Puppet::Resource::Reference
- attr_reader :type, :title
- attr_accessor :catalog
-
- def ==(other)
- other.respond_to?(:title) and self.type == other.type and self.title == other.title
- end
-
- def builtin_type?
- builtin_type ? true : false
- end
-
- def initialize(argtype, argtitle = nil)
- self.type,self.title =
- if (argtitle || argtype) =~ /^([^\[\]]+)\[(.+)\]$/m then [ $1, $2 ]
- elsif argtitle then [ argtype, argtitle ]
- elsif argtype.is_a?(Puppet::Type) then [ argtype.class.name, argtype.title ]
- else raise ArgumentError, "No title provided and #{argtype.inspect} is not a valid resource reference"
- end
- @builtin_type = nil
- end
-
- # Find our resource.
- def resolve
- return catalog.resource(to_s) if catalog
- return nil
- end
-
- def title=(value)
- if @type and klass = Puppet::Type.type(@type.to_s.downcase)
- value = klass.canonicalize_ref(value)
- end
- @title = value
- end
-
- # Canonize the type so we know it's always consistent.
- def type=(value)
- if value.nil? or value.to_s.downcase == "component"
- @type = "Class"
- else
- # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com]
- x = @type = value.to_s.split("::").collect { |s| s.capitalize }.join("::")
- end
-
- if @title
- self.title = @title
- end
- end
-
- # Convert to the reference format that TransObject uses. Yay backward
- # compatibility.
- def to_trans_ref
- # We have to return different cases to provide backward compatibility
- # from 0.24.x to 0.23.x.
- if builtin_type?
- return [type.to_s.downcase, title.to_s]
- else
- return [type.to_s, title.to_s]
- end
- end
-
- # Convert to the standard way of referring to resources.
- def to_s
- "%s[%s]" % [@type, @title]
- end
-
- private
-
- def builtin_type
- if @builtin_type.nil?
- if @type =~ /::/
- @builtin_type = false
- elsif klass = Puppet::Type.type(@type.to_s.downcase)
- @builtin_type = true
- else
- @builtin_type = false
- end
- end
- @builtin_type
- end
-end
diff --git a/lib/puppet/resource/type.rb b/lib/puppet/resource/type.rb
index d47658284..c615a2ccb 100644
--- a/lib/puppet/resource/type.rb
+++ b/lib/puppet/resource/type.rb
@@ -1,236 +1,236 @@
require 'puppet/parser/parser'
require 'puppet/util/warnings'
require 'puppet/util/errors'
require 'puppet/util/inline_docs'
require 'puppet/parser/ast/leaf'
class Puppet::Resource::Type
include Puppet::Util::InlineDocs
include Puppet::Util::Warnings
include Puppet::Util::Errors
RESOURCE_SUPERTYPES = [:hostclass, :node, :definition]
attr_accessor :file, :line, :doc, :code, :parent, :code_collection
attr_reader :type, :namespace, :arguments, :behaves_like
# Are we a child of the passed class? Do a recursive search up our
# parentage tree to figure it out.
def child_of?(klass)
return false unless parent
return true if klass == parent_type
return parent_type.child_of?(klass)
end
# Now evaluate the code associated with this class or definition.
def evaluate_code(resource)
# Create a new scope.
scope = subscope(resource.scope, resource)
set_resource_parameters(resource, scope)
return nil unless c = self.code
return c.safeevaluate(scope)
end
def initialize(type, name, options = {})
@type = type.to_s.downcase.to_sym
raise ArgumentError, "Invalid resource supertype '#{type}'" unless RESOURCE_SUPERTYPES.include?(@type)
name = convert_from_ast(name) if name.is_a?(Puppet::Parser::AST::HostName)
set_name_and_namespace(name)
[:code, :doc, :line, :file, :parent].each do |param|
next unless value = options[param]
send(param.to_s + "=", value)
end
set_arguments(options[:arguments])
end
# This is only used for node names, and really only when the node name
# is a regexp.
def match(string)
return string.to_s.downcase == name unless name_is_regex?
return @name =~ string
end
# Add code from a new instance to our code.
def merge(other)
- fail ArgumentError, "#{name} is not a class; cannot add code to it" unless type == :hostclass
- fail ArgumentError, "#{other.name} is not a class; cannot add code from it" unless other.type == :hostclass
+ fail "#{name} is not a class; cannot add code to it" unless type == :hostclass
+ fail "#{other.name} is not a class; cannot add code from it" unless other.type == :hostclass
if parent and other.parent and parent != other.parent
- fail ArgumentError, "Cannot merge classes with different parent classes"
+ fail "Cannot merge classes with different parent classes (#{name} => #{parent} vs. #{other.name} => #{other.parent})"
end
# We know they're either equal or only one is set, so keep whichever parent is specified.
self.parent ||= other.parent
if other.doc
self.doc ||= ""
self.doc += other.doc
end
# This might just be an empty, stub class.
return unless other.code
unless self.code
self.code = other.code
return
end
array_class = Puppet::Parser::AST::ASTArray
unless self.code.is_a?(array_class)
self.code = array_class.new(:children => [self.code])
end
if other.code.is_a?(array_class)
code.children += other.code.children
else
code.children << other.code
end
end
# Make an instance of our resource type. This is only possible
# for those classes and nodes that don't have any arguments, and is
# only useful for things like the 'include' function.
def mk_plain_resource(scope)
type == :definition and raise ArgumentError, "Cannot create resources for defined resource types"
resource_type = type == :hostclass ? :class : :node
# Make sure our parent class has been evaluated, if we have one.
if parent and ! scope.catalog.resource(resource_type, parent)
parent_type.mk_plain_resource(scope)
end
# Do nothing if the resource already exists; this makes sure we don't
# get multiple copies of the class resource, which helps provide the
# singleton nature of classes.
if resource = scope.catalog.resource(resource_type, name)
return resource
end
- resource = Puppet::Parser::Resource.new(:type => resource_type, :title => name, :scope => scope, :source => self)
+ resource = Puppet::Parser::Resource.new(resource_type, name, :scope => scope, :source => self)
scope.compiler.add_resource(scope, resource)
scope.catalog.tag(*resource.tags)
resource
end
def name
return @name unless @name.is_a?(Regexp)
return @name.source.downcase.gsub(/[^-\w:.]/,'').sub(/^\.+/,'')
end
def name_is_regex?
@name.is_a?(Regexp)
end
def parent_type
return nil unless parent
unless @parent_type ||= code_collection.send(type, parent)
fail Puppet::ParseError, "Could not find parent resource type '#{parent}'"
end
@parent_type
end
# Set any arguments passed by the resource as variables in the scope.
def set_resource_parameters(resource, scope)
set = {}
resource.to_hash.each do |param, value|
param = param.to_sym
fail Puppet::ParseError, "#{resource.ref} does not accept attribute #{param}" unless valid_parameter?(param)
exceptwrap { scope.setvar(param.to_s, value) }
set[param] = true
end
# Verify that all required arguments are either present or
# have been provided with defaults.
arguments.each do |param, default|
param = param.to_sym
next if set.include?(param)
# Even if 'default' is a false value, it's an AST value, so this works fine
fail Puppet::ParseError, "Must pass #{param} to #{resource.ref}" unless default
scope.setvar(param.to_s, default.safeevaluate(scope))
end
scope.setvar("title", resource.title) unless set.include? :title
scope.setvar("name", resource.name) unless set.include? :name
scope.class_set(self.name,scope)
end
# Create a new subscope in which to evaluate our code.
def subscope(scope, resource)
scope.newscope :resource => resource, :namespace => self.namespace, :source => self
end
# Check whether a given argument is valid.
def valid_parameter?(param)
param = param.to_s
return true if param == "name"
return true if Puppet::Type.metaparam?(param)
return false unless defined?(@arguments)
return true if arguments.include?(param)
return false
end
def set_arguments(arguments)
@arguments = {}
return if arguments.nil?
arguments.each do |arg, default|
arg = arg.to_s
warn_if_metaparam(arg, default)
@arguments[arg] = default
end
end
private
def convert_from_ast(name)
value = name.value
if value.is_a?(Puppet::Parser::AST::Regex)
name = value.value
else
name = value
end
end
# Split an fq name into a namespace and name
def namesplit(fullname)
ary = fullname.split("::")
n = ary.pop || ""
ns = ary.join("::")
return ns, n
end
def set_name_and_namespace(name)
if name.is_a?(Regexp)
@name = name
@namespace = ""
else
@name = name.to_s.downcase
@namespace, ignored_shortname = namesplit(@name)
end
end
def warn_if_metaparam(param, default)
return unless Puppet::Type.metaparamclass(param)
if default
warnonce "#{param} is a metaparam; this value will inherit to all contained resources"
else
raise Puppet::ParseError, "#{param} is a metaparameter; please choose another parameter name in the #{self.name} definition"
end
end
end
diff --git a/lib/puppet/resource/type_collection.rb b/lib/puppet/resource/type_collection.rb
index a0bd2ddf5..e2ca56290 100644
--- a/lib/puppet/resource/type_collection.rb
+++ b/lib/puppet/resource/type_collection.rb
@@ -1,189 +1,202 @@
class Puppet::Resource::TypeCollection
attr_reader :environment
+ def clear
+ @hostclasses.clear
+ @definitions.clear
+ @nodes.clear
+ end
+
def initialize(env)
@environment = env.is_a?(String) ? Puppet::Node::Environment.new(env) : env
@hostclasses = {}
@definitions = {}
@nodes = {}
# So we can keep a list and match the first-defined regex
@node_list = []
@watched_files = {}
end
def <<(thing)
add(thing)
self
end
def add(instance)
+ if instance.type == :hostclass and other = @hostclasses[instance.name] and other.type == :hostclass
+ other.merge(instance)
+ return other
+ end
method = "add_#{instance.type}"
send(method, instance)
instance.code_collection = self
instance
end
def add_hostclass(instance)
- dupe_check(instance, @hostclasses) { |dupe| "Class #{instance.name} is already defined#{dupe.error_context}; cannot redefine" }
- dupe_check(instance, @definitions) { |dupe| "Definition #{instance.name} is already defined#{dupe.error_context}; cannot be redefined as a class" }
+ dupe_check(instance, @hostclasses) { |dupe| "Class '#{instance.name}' is already defined#{dupe.error_context}; cannot redefine" }
+ dupe_check(instance, @definitions) { |dupe| "Definition '#{instance.name}' is already defined#{dupe.error_context}; cannot be redefined as a class" }
@hostclasses[instance.name] = instance
instance
end
def hostclass(name)
@hostclasses[munge_name(name)]
end
def add_node(instance)
- dupe_check(instance, @nodes) { |dupe| "Node #{instance.name} is already defined#{dupe.error_context}; cannot redefine" }
+ dupe_check(instance, @nodes) { |dupe| "Node '#{instance.name}' is already defined#{dupe.error_context}; cannot redefine" }
@node_list << instance
@nodes[instance.name] = instance
instance
end
def node(name)
name = munge_name(name)
if node = @nodes[name]
return node
end
@node_list.each do |node|
next unless node.name_is_regex?
return node if node.match(name)
end
nil
end
def node_exists?(name)
@nodes[munge_name(name)]
end
def nodes?
@nodes.length > 0
end
- def add_definition(code)
- @definitions[code.name] = code
+ def add_definition(instance)
+ dupe_check(instance, @hostclasses) { |dupe| "'#{instance.name}' is already defined#{dupe.error_context} as a class; cannot redefine as a definition" }
+ dupe_check(instance, @definitions) { |dupe| "Definition '#{instance.name}' is already defined#{dupe.error_context}; cannot be redefined" }
+ @definitions[instance.name] = instance
end
def definition(name)
@definitions[munge_name(name)]
end
def find(namespaces, name, type)
+ #Array("") == [] for some reason
+ namespaces = [namespaces] unless namespaces.is_a?(Array)
+
if r = find_fully_qualified(name, type)
return r
end
- namespaces = Array(namespaces)
-
namespaces.each do |namespace|
ary = namespace.split("::")
while ary.length > 0
tmp_namespace = ary.join("::")
if r = find_partially_qualified(tmp_namespace, name, type)
return r
end
# Delete the second to last object, which reduces our namespace by one.
ary.pop
end
if result = send(type, name)
return result
end
end
nil
end
def find_node(name)
find("", name, :node)
end
def find_hostclass(namespace, name)
find(namespace, name, :hostclass)
end
def find_definition(namespace, name)
find(namespace, name, :definition)
end
[:hostclasses, :nodes, :definitions].each do |m|
define_method(m) do
instance_variable_get("@#{m}").dup
end
end
def perform_initial_import
parser = Puppet::Parser::Parser.new(environment)
if code = Puppet.settings.uninterpolated_value(:code, environment.to_s) and code != ""
parser.string = code
else
file = Puppet.settings.value(:manifest, environment.to_s)
return unless File.exist?(file)
parser.file = file
end
parser.parse
rescue => detail
msg = "Could not parse for environment #{environment}: #{detail}"
error = Puppet::Error.new(msg)
error.set_backtrace(detail.backtrace)
raise error
end
def stale?
@watched_files.values.detect { |file| file.changed? }
end
def version
return @version if defined?(@version)
if environment[:config_version] == ""
@version = Time.now.to_i
return @version
end
@version = Puppet::Util.execute([environment[:config_version]]).strip
rescue Puppet::ExecutionFailure => e
raise Puppet::ParseError, "Unable to set config_version: #{e.message}"
end
def watch_file(file)
@watched_files[file] = Puppet::Util::LoadedFile.new(file)
end
def watching_file?(file)
@watched_files.include?(file)
end
private
def find_fully_qualified(name, type)
return nil unless name =~ /^::/
name = name.sub(/^::/, '')
send(type, name)
end
def find_partially_qualified(namespace, name, type)
send(type, [namespace, name].join("::"))
end
def munge_name(name)
name.to_s.downcase
end
def dupe_check(instance, hash)
return unless dupe = hash[instance.name]
message = yield dupe
instance.fail Puppet::ParseError, message
end
end
diff --git a/lib/puppet/transportable.rb b/lib/puppet/transportable.rb
index 1970d9f1e..c4881701c 100644
--- a/lib/puppet/transportable.rb
+++ b/lib/puppet/transportable.rb
@@ -1,259 +1,259 @@
require 'puppet'
-require 'puppet/resource/reference'
+require 'puppet/resource'
require 'yaml'
module Puppet
# The transportable objects themselves. Basically just a hash with some
# metadata and a few extra methods. I used to have the object actually
# be a subclass of Hash, but I could never correctly dump them using
# YAML.
class TransObject
include Enumerable
attr_accessor :type, :name, :file, :line, :catalog
attr_writer :tags
%w{has_key? include? length delete empty? << [] []=}.each { |method|
define_method(method) do |*args|
@params.send(method, *args)
end
}
def each
@params.each { |p,v| yield p, v }
end
def initialize(name,type)
@type = type.to_s.downcase
@name = name
@params = {}
@tags = []
end
def longname
return [@type,@name].join('--')
end
def ref
unless defined? @ref
- @ref = Puppet::Resource::Reference.new(@type, @name)
+ @ref = Puppet::Resource.new(@type, @name)
end
@ref.to_s
end
def tags
return @tags
end
# Convert a defined type into a component.
def to_component
trans = TransObject.new(ref, :component)
@params.each { |param,value|
next unless Puppet::Type::Component.valid_parameter?(param)
Puppet.debug "Defining %s on %s" % [param, ref]
trans[param] = value
}
trans.catalog = self.catalog
Puppet::Type::Component.create(trans)
end
def to_hash
@params.dup
end
def to_s
return "%s(%s) => %s" % [@type,@name,super]
end
def to_manifest
"%s { '%s':\n%s\n}" %
[self.type.to_s, self.name,
@params.collect { |p, v|
if v.is_a? Array
" #{p} => [\'#{v.join("','")}\']"
else
" #{p} => \'#{v}\'"
end
}.join(",\n")
]
end
# Create a normalized resource from our TransObject.
def to_resource
result = Puppet::Resource.new(type, name, :parameters => @params.dup)
result.tag(*tags)
result
end
def to_yaml_properties
instance_variables.reject { |v| %w{@ref}.include?(v) }
end
def to_ref
ref
end
def to_ral
to_resource.to_ral
end
end
# Just a linear container for objects. Behaves mostly like an array, except
# that YAML will correctly dump them even with their instance variables.
class TransBucket
include Enumerable
attr_accessor :name, :type, :file, :line, :classes, :keyword, :top, :catalog
%w{delete shift include? length empty? << []}.each { |method|
define_method(method) do |*args|
#Puppet.warning "Calling %s with %s" % [method, args.inspect]
@children.send(method, *args)
#Puppet.warning @params.inspect
end
}
# Recursively yield everything.
def delve(&block)
@children.each do |obj|
block.call(obj)
if obj.is_a? self.class
obj.delve(&block)
else
obj
end
end
end
def each
@children.each { |c| yield c }
end
# Turn our heirarchy into a flat list
def flatten
@children.collect do |obj|
if obj.is_a? Puppet::TransBucket
obj.flatten
else
obj
end
end.flatten
end
def initialize(children = [])
@children = children
end
def push(*args)
args.each { |arg|
case arg
when Puppet::TransBucket, Puppet::TransObject
# nada
else
raise Puppet::DevError,
"TransBuckets cannot handle objects of type %s" %
arg.class
end
}
@children += args
end
# Convert to a parseable manifest
def to_manifest
unless self.top
unless defined? @keyword and @keyword
raise Puppet::DevError, "No keyword; cannot convert to manifest"
end
end
str = "#{@keyword} #{@name} {\n%s\n}"
str % @children.collect { |child|
child.to_manifest
}.collect { |str|
if self.top
str
else
str.gsub(/^/, " ") # indent everything once
end
}.join("\n\n") # and throw in a blank line
end
def to_yaml_properties
instance_variables
end
# Create a resource graph from our structure.
def to_catalog(clear_on_failure = true)
catalog = Puppet::Resource::Catalog.new(Facter.value("hostname"))
# This should really use the 'delve' method, but this
# whole class is going away relatively soon, hopefully,
# so it's not worth it.
delver = proc do |obj|
obj.catalog = catalog
unless container = catalog.resource(obj.to_ref)
container = obj.to_ral
catalog.add_resource container
end
obj.each do |child|
child.catalog = catalog
unless resource = catalog.resource(child.to_ref)
resource = child.to_ral
catalog.add_resource resource
end
catalog.add_edge(container, resource)
if child.is_a?(self.class)
delver.call(child)
end
end
end
begin
delver.call(self)
catalog.finalize
rescue => detail
# This is important until we lose the global resource references.
catalog.clear if (clear_on_failure)
raise
end
return catalog
end
def to_ref
unless defined? @ref
if self.type and self.name
- @ref = Puppet::Resource::Reference.new(self.type, self.name)
+ @ref = Puppet::Resource.new(self.type, self.name)
elsif self.type and ! self.name # This is old-school node types
- @ref = Puppet::Resource::Reference.new("node", self.type)
+ @ref = Puppet::Resource.new("node", self.type)
elsif ! self.type and self.name
- @ref = Puppet::Resource::Reference.new("component", self.name)
+ @ref = Puppet::Resource.new("component", self.name)
else
@ref = nil
end
end
@ref.to_s if @ref
end
def to_ral
to_resource.to_ral
end
# Create a normalized resource from our TransObject.
def to_resource
params = defined?(@parameters) ? @parameters.dup : {}
- Puppet::Resource.new(type, name, params)
+ Puppet::Resource.new(type, name, :parameters => params)
end
def param(param,value)
unless defined? @parameters
@parameters = {}
end
@parameters[param] = value
end
end
end
diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb
index 31728c374..ee545a99b 100644
--- a/lib/puppet/type.rb
+++ b/lib/puppet/type.rb
@@ -1,2051 +1,2051 @@
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/resource'
require 'puppet/util/cacher'
require 'puppet/file_collection/lookup'
require 'puppet/util/tagging'
# 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::Util::Cacher
include Puppet::FileCollection::Lookup
include Puppet::Util::Tagging
###############################
# 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 provider, then the properties, and finally the params and metaparams
# in the order they were specified in the files.
def self.allattrs
# Cache this, since it gets called multiple 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
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
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
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_parameter
@namevar_parameter ||= (
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
params.first
else
raise Puppet::DevError, "No namevar for %s" % self.name
end
)
end
def self.namevar
@namevar ||= namevar_parameter.name
end
def self.canonicalize_ref(s)
namevar_parameter.canonicalize(s)
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]
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
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
# This is a forward-compatibility method - it's the validity interface we'll use in Puppet::Resource.
def self.valid_parameter?(name)
validattr?(name)
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)
fail("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)
fail("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
# Create a transaction event. Called by Transaction or by
# a property.
def event(options = {})
Puppet::Transaction::Event.new({:resource => self, :file => file, :line => line, :tags => tags, :version => version}.merge(options))
end
# Let the catalog determine whether a given cached value is
# still valid or has expired.
def expirer
catalog
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)
@parameters[name.to_sym]
end
def parameters
@parameters.dup
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 property instance by name; to return the value, use 'resource[param]'
# LAK:NOTE(20081028) Since the 'parameter' method is now a superset of this method,
# this one should probably go away at some point.
def property(name)
if obj = @parameters[symbolize(name)] and obj.is_a?(Puppet::Property)
return obj
else
return nil
end
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 set_default(attr)
return unless klass = self.class.attrclass(attr)
return unless klass.method_defined?(:default)
return if @parameters.include?(klass.name)
return unless parameter = newattr(klass.name)
if value = parameter.default and ! value.nil?
parameter.value = value
else
@parameters.delete(parameter.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
def type
self.class.name
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
def version
return 0 unless catalog
catalog.version
end
# Return all of the property objects, in the order specified in the
# class.
def properties
self.class.properties.collect { |prop| @parameters[prop.name] }.compact
end
# 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 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.
# this is a retarded hack method to get around the difference between
# component children and file children
def self.depthfirst?
if defined? @depthfirst
return @depthfirst
else
return false
end
end
def depthfirst?
self.class.depthfirst?
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
@parent = nil
# Remove the reference to the provider.
if self.provider
@provider.clear
@provider = nil
end
end
###############################
# Code related to evaluating the resources.
# 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
if self.provider.is_a?(Puppet::Provider) and ! provider.class.suitable?
fail "Provider #{provider.class.name} is not functional on this host"
end
result = Puppet::Resource.new(type, title)
# Provide the name, so we know we'll always refer to a real thing
result[:name] = self[:name] unless self[:name] == title
if ensure_prop = property(:ensure) or (self.class.validattr?(:ensure) and ensure_prop = newattr(:ensure))
result[:ensure] = ensure_state = ensure_prop.retrieve
else
ensure_state = nil
end
properties.each do |property|
next if property.name == :ensure
if ensure_state == :absent
result[property] = :absent
else
result[property] = property.retrieve
end
end
result
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
###############################
# Code related to managing resource instances.
require 'puppet/transportable'
# retrieve a named instance of the current type
def self.[](name)
raise "Global resource access is deprecated"
@objects[name] || @aliases[name]
end
# add an instance by name to the class list of instances
def self.[]=(name,object)
raise "Global resource storage is deprecated"
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)
raise "Global resource aliasing is deprecated"
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
raise "Global resource removal is deprecated"
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)
# LAK:DEP Deprecation notice added 12/17/2008
Puppet.warning "Puppet::Type.create is deprecated; use Puppet::Type.new"
new(args)
end
# remove a specified object
def self.delete(resource)
raise "Global resource removal is deprecated"
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
raise "Global resource iteration is deprecated"
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)
raise "Global resource access is deprecated"
return @objects.has_key?(name)
end
# Retrieve all known instances. Either requires providers or must be overridden.
def self.instances
if provider_hash.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|
# 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
new(:name => instance.name, :provider => instance, :check => :all)
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
# Convert a simple hash into a Resource instance. This is a convenience method,
# so people can create RAL resources with a hash and get the same behaviour
# as we get internally when we use Resource instances.
# This should only be used directly from Ruby -- it's not used when going through
# normal Puppet usage.
def self.hash2resource(hash)
hash = hash.inject({}) { |result, ary| result[ary[0].to_sym] = ary[1]; result }
if title = hash[:title]
hash.delete(:title)
else
if self.namevar != :name
if hash.include?(:name) and hash.include?(self.namevar)
raise Puppet::Error, "Cannot provide both name and %s to resources of type %s" % [self.namevar, self.name]
end
if title = hash[self.namevar]
hash.delete(self.namevar)
end
end
unless title ||= hash[:name]
raise Puppet::Error, "You must specify a name or title for resources"
end
end
# Now create our resource.
resource = Puppet::Resource.new(self.name, title)
[:catalog].each do |attribute|
if value = hash[attribute]
hash.delete(attribute)
resource.send(attribute.to_s + "=", value)
end
end
hash.each do |param, value|
resource[param] = value
end
return resource
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
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
# 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(references)
references = [references] unless references.is_a?(Array)
references.collect do |ref|
- if ref.is_a?(Puppet::Resource::Reference)
+ if ref.is_a?(Puppet::Resource)
ref
else
- Puppet::Resource::Reference.new(ref)
+ Puppet::Resource.new(ref)
end
end
end
def validate_relationship
@value.each do |ref|
unless @resource.catalog.resource(ref.to_s)
description = self.class.direction == :in ? "dependency" : "dependent"
fail "Could not find %s %s for %s" % [description, ref.to_s, 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 |reference|
reference.catalog = resource.catalog
# Either of the two retrieval attempts could have returned
# nil.
unless related_resource = 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 = related_resource
target = @resource
else
source = @resource
target = related_resource
end
if method = self.class.callback
subargs = {
:event => self.class.events,
:callback => method
}
self.debug("subscribes to %s" % [related_resource.ref])
else
# If there's no callback, there's no point in even adding
# a label.
subargs = nil
self.debug("requires %s" % [related_resource.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.specificity }.max
defaults = defaults.find_all { |provider| provider.specificity == 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
def self.provider_hash_by_type(type)
@provider_hashes ||= {}
@provider_hashes[type] ||= {}
end
def self.provider_hash
Puppet::Type.provider_hash_by_type(self.name)
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 provider_hash.has_key?(name)
@providerloader.load(name)
end
return provider_hash[name]
end
# Just list all of the providers.
def self.providers
provider_hash.keys
end
def self.validprovider?(name)
name = Puppet::Util.symbolize(name)
return (provider_hash.has_key?(name) && provider_hash[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 = provider_hash[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 => provider_hash,
: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 provider_hash.has_key? name
rmclass(name,
:hash => provider_hash,
: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 provider_hash.empty?
providerloader.loadall
end
provider_hash.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(rel_catalog = nil)
rel_catalog ||= catalog
raise(Puppet::DevError, "You cannot add relationships without a catalog") unless rel_catalog
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(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 = rel_catalog.resource(type, 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
###############################
# 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 catalog
warning "Cannot schedule without a schedule-containing catalog"
return nil
end
unless defined? @schedule
if name = self[:schedule]
if sched = catalog.resource(: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
# Define the initial list of tags.
def tags=(list)
tag(self.class.name)
tag(*list)
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_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
@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
# is the resource exported
attr_accessor :exported
# is the resource virtual (it should not :-))
attr_accessor :virtual
# 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
attr_reader :original_parameters
# initialize the type instance
def initialize(resource)
raise Puppet::DevError, "Got TransObject instead of Resource or hash" if resource.is_a?(Puppet::TransObject)
resource = self.class.hash2resource(resource) unless resource.is_a?(Puppet::Resource)
# The list of parameter/property instances.
@parameters = {}
# Set the title first, so any failures print correctly.
if resource.type.to_s.downcase.to_sym == self.class.name
self.title = resource.title
else
# This should only ever happen for components
self.title = resource.ref
end
[:file, :line, :catalog, :exported, :virtual].each do |getter|
setter = getter.to_s + "="
if val = resource.send(getter)
self.send(setter, val)
end
end
@tags = resource.tags
@original_parameters = resource.to_hash
set_name(@original_parameters)
set_default(:provider)
set_parameters(@original_parameters)
self.validate if self.respond_to?(:validate)
end
private
# Set our resource's name.
def set_name(hash)
n = self.class.namevar
self[n] = hash[n]
hash.delete(n)
end
# Set all of the parameters from a hash, in the appropriate order.
def set_parameters(hash)
# Use the order provided by allattrs, but add in any
# extra attributes from the resource so we get failures
# on invalid attributes.
no_values = []
(self.class.allattrs + hash.keys).uniq.each do |attr|
begin
# Set any defaults immediately. This is mostly done so
# that the default provider is available for any other
# property validation.
if hash.has_key?(attr)
self[attr] = hash[attr]
else
no_values << attr
end
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
end
no_values.each do |attr|
set_default(attr)
end
end
public
# 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
# 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)
if parents = catalog.adjacent(self, :direction => :in)
# 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
def to_resource
# this 'type instance' versus 'resource' distinction seems artificial
# I'd like to see it collapsed someday ~JW
self.to_trans.to_resource
end
%w{exported virtual}.each do |m|
define_method(m+"?") do
self.send(m)
end
end
end # Puppet::Type
end
require 'puppet/provider'
# Always load these types.
require 'puppet/type/component'
diff --git a/lib/puppet/type/component.rb b/lib/puppet/type/component.rb
index bf9007ab4..d16cdda06 100644
--- a/lib/puppet/type/component.rb
+++ b/lib/puppet/type/component.rb
@@ -1,87 +1,87 @@
require 'puppet'
require 'puppet/type'
require 'puppet/transaction'
Puppet::Type.newtype(:component) do
include Enumerable
newparam(:name) do
desc "The name of the component. Generally optional."
isnamevar
end
# Override how parameters are handled so that we support the extra
# parameters that are used with defined resource types.
def [](param)
return super if self.class.valid_parameter?(param)
@extra_parameters[param.to_sym]
end
# Override how parameters are handled so that we support the extra
# parameters that are used with defined resource types.
def []=(param, value)
return super if self.class.valid_parameter?(param)
@extra_parameters[param.to_sym] = value
end
# Initialize a new component
def initialize(*args)
@extra_parameters = {}
super
if catalog and ! catalog.resource(ref)
catalog.alias(self, ref)
end
end
# Component paths are special because they function as containers.
def pathbuilder
if reference.type == "Class"
# 'main' is the top class, so we want to see '//' instead of
# its name.
if reference.title == "main"
myname = ""
else
myname = reference.title
end
else
myname = reference.to_s
end
if p = self.parent
return [p.pathbuilder, myname]
else
return [myname]
end
end
def ref
reference.to_s
end
# We want our title to just be the whole reference, rather than @title.
def title
ref
end
def title=(str)
- @reference = Puppet::Resource::Reference.new(str)
+ @reference = Puppet::Resource.new(str)
end
def refresh
catalog.adjacent(self).each do |child|
if child.respond_to?(:refresh)
child.refresh
child.log "triggering %s" % :refresh
end
end
end
def to_s
reference.to_s
end
private
attr_reader :reference
end
diff --git a/lib/puppet/type/tidy.rb b/lib/puppet/type/tidy.rb
index 830f47640..c912ad3ad 100755
--- a/lib/puppet/type/tidy.rb
+++ b/lib/puppet/type/tidy.rb
@@ -1,335 +1,335 @@
Puppet::Type.newtype(:tidy) do
require 'puppet/file_serving/fileset'
@doc = "Remove unwanted files based on specific criteria. Multiple
criteria are OR'd together, so a file that is too large but is not
old enough will still get tidied.
If you don't specify either 'age' or 'size', then all files will
be removed.
This resource type works by generating a file resource for every file
that should be deleted and then letting that resource perform the
actual deletion.
"
newparam(:path) do
desc "The path to the file or directory to manage. Must be fully
qualified."
isnamevar
end
newparam(:matches) do
desc "One or more (shell type) file glob patterns, which restrict
the list of files to be tidied to those whose basenames match
at least one of the patterns specified. Multiple patterns can
be specified using an array.
Example::
tidy { \"/tmp\":
age => \"1w\",
recurse => false,
matches => [ \"[0-9]pub*.tmp\", \"*.temp\", \"tmpfile?\" ]
}
This removes files from \/tmp if they are one week old or older,
are not in a subdirectory and match one of the shell globs given.
Note that the patterns are matched against the
basename of each file -- that is, your glob patterns should not
have any '/' characters in them, since you are only specifying
against the last bit of the file."
# Make sure we convert to an array.
munge do |value|
value = [value] unless value.is_a?(Array)
value
end
# Does a given path match our glob patterns, if any? Return true
# if no patterns have been provided.
def tidy?(path, stat)
basename = File.basename(path)
flags = File::FNM_DOTMATCH | File::FNM_PATHNAME
return true if value.find {|pattern| File.fnmatch(pattern, basename, flags) }
return false
end
end
newparam(:backup) do
desc "Whether tidied files should be backed up. Any values are passed
directly to the file resources used for actual file deletion, so use
its backup documentation to determine valid values."
end
newparam(:age) do
desc "Tidy files whose age is equal to or greater than
the specified time. You can choose seconds, minutes,
hours, days, or weeks by specifying the first letter of any
of those words (e.g., '1w').
Specifying 0 will remove all files."
@@ageconvertors = {
:s => 1,
:m => 60
}
@@ageconvertors[:h] = @@ageconvertors[:m] * 60
@@ageconvertors[:d] = @@ageconvertors[:h] * 24
@@ageconvertors[:w] = @@ageconvertors[:d] * 7
def convert(unit, multi)
if num = @@ageconvertors[unit]
return num * multi
else
self.fail "Invalid age unit '%s'" % unit
end
end
def tidy?(path, stat)
# If the file's older than we allow, we should get rid of it.
if (Time.now.to_i - stat.send(resource[:type]).to_i) > value
return true
else
return false
end
end
munge do |age|
unit = multi = nil
case age
when /^([0-9]+)(\w)\w*$/
multi = Integer($1)
unit = $2.downcase.intern
when /^([0-9]+)$/
multi = Integer($1)
unit = :d
else
self.fail "Invalid tidy age %s" % age
end
convert(unit, multi)
end
end
newparam(:size) do
desc "Tidy files whose size is equal to or greater than
the specified size. Unqualified values are in kilobytes, but
*b*, *k*, and *m* can be appended to specify *bytes*, *kilobytes*,
and *megabytes*, respectively. Only the first character is
significant, so the full word can also be used."
@@sizeconvertors = {
:b => 0,
:k => 1,
:m => 2,
:g => 3
}
def convert(unit, multi)
if num = @@sizeconvertors[unit]
result = multi
num.times do result *= 1024 end
return result
else
self.fail "Invalid size unit '%s'" % unit
end
end
def tidy?(path, stat)
if stat.size >= value
return true
else
return false
end
end
munge do |size|
case size
when /^([0-9]+)(\w)\w*$/
multi = Integer($1)
unit = $2.downcase.intern
when /^([0-9]+)$/
multi = Integer($1)
unit = :k
else
self.fail "Invalid tidy size %s" % age
end
convert(unit, multi)
end
end
newparam(:type) do
desc "Set the mechanism for determining age."
newvalues(:atime, :mtime, :ctime)
defaultto :atime
end
newparam(:recurse) do
desc "If target is a directory, recursively descend
into the directory looking for files to tidy."
newvalues(:true, :false, :inf, /^[0-9]+$/)
# Replace the validation so that we allow numbers in
# addition to string representations of them.
validate { |arg| }
munge do |value|
newval = super(value)
case newval
when :true, :inf; true
when :false; false
when Integer, Fixnum, Bignum; value
when /^\d+$/; Integer(value)
else
raise ArgumentError, "Invalid recurse value %s" % value.inspect
end
end
end
newparam(:rmdirs, :boolean => true) do
desc "Tidy directories in addition to files; that is, remove
directories whose age is older than the specified criteria.
This will only remove empty directories, so all contained
files must also be tidied before a directory gets removed."
newvalues :true, :false
end
# Erase PFile's validate method
validate do
end
def self.instances
[]
end
@depthfirst = true
def initialize(hash)
super
# only allow backing up into filebuckets
unless self[:backup].is_a? Puppet::FileBucket::Dipper
self[:backup] = false
end
end
# Make a file resource to remove a given file.
def mkfile(path)
# Force deletion, so directories actually get deleted.
Puppet::Type.type(:file).new :path => path, :backup => self[:backup], :ensure => :absent, :force => true
end
def retrieve
# Our ensure property knows how to retrieve everything for us.
if obj = @parameters[:ensure]
return obj.retrieve
else
return {}
end
end
# Hack things a bit so we only ever check the ensure property.
def properties
[]
end
def eval_generate
[]
end
def generate
return [] unless stat(self[:path])
case self[:recurse]
when Integer, Fixnum, Bignum, /^\d+$/
parameter = { :recurse => true, :recurselimit => self[:recurse] }
when true, :true, :inf
parameter = { :recurse => true }
end
if parameter
files = Puppet::FileServing::Fileset.new(self[:path], parameter).files.collect do |f|
f == "." ? self[:path] : File.join(self[:path], f)
end
else
files = [self[:path]]
end
result = files.find_all { |path| tidy?(path) }.collect { |path| mkfile(path) }.each { |file| notice "Tidying %s" % file.ref }.sort { |a,b| b[:path] <=> a[:path] }
# No need to worry about relationships if we don't have rmdirs; there won't be
# any directories.
return result unless rmdirs?
# Now make sure that all directories require the files they contain, if all are available,
# so that a directory is emptied before we try to remove it.
files_by_name = result.inject({}) { |hash, file| hash[file[:path]] = file; hash }
files_by_name.keys.sort { |a,b| b <=> b }.each do |path|
dir = File.dirname(path)
next unless resource = files_by_name[dir]
if resource[:require]
- resource[:require] << Puppet::Resource::Reference.new(:file, path)
+ resource[:require] << Puppet::Resource.new(:file, path)
else
- resource[:require] = [Puppet::Resource::Reference.new(:file, path)]
+ resource[:require] = [Puppet::Resource.new(:file, path)]
end
end
return result
end
# Does a given path match our glob patterns, if any? Return true
# if no patterns have been provided.
def matches?(path)
return true unless self[:matches]
basename = File.basename(path)
flags = File::FNM_DOTMATCH | File::FNM_PATHNAME
if self[:matches].find {|pattern| File.fnmatch(pattern, basename, flags) }
return true
else
debug "No specified patterns match %s, not tidying" % path
return false
end
end
# Should we remove the specified file?
def tidy?(path)
return false unless stat = self.stat(path)
return false if stat.ftype == "directory" and ! rmdirs?
# The 'matches' parameter isn't OR'ed with the other tests --
# it's just used to reduce the list of files we can match.
return false if param = parameter(:matches) and ! param.tidy?(path, stat)
tested = false
[:age, :size].each do |name|
next unless param = parameter(name)
tested = true
return true if param.tidy?(path, stat)
end
# If they don't specify either, then the file should always be removed.
return true unless tested
return false
end
def stat(path)
begin
File.lstat(path)
rescue Errno::ENOENT => error
info "File does not exist"
return nil
rescue Errno::EACCES => error
warning "Could not stat; permission denied"
return nil
end
end
end
diff --git a/lib/puppet/util/settings.rb b/lib/puppet/util/settings.rb
index f50d46642..e3f95d89e 100644
--- a/lib/puppet/util/settings.rb
+++ b/lib/puppet/util/settings.rb
@@ -1,963 +1,962 @@
require 'puppet'
require 'sync'
require 'getoptlong'
require 'puppet/external/event-loop'
require 'puppet/util/cacher'
require 'puppet/util/loadedfile'
require 'puppet/resource'
-require 'puppet/resource/reference'
# The class for handling configuration files.
class Puppet::Util::Settings
include Enumerable
include Puppet::Util::Cacher
require 'puppet/util/settings/setting'
require 'puppet/util/settings/file_setting'
require 'puppet/util/settings/boolean_setting'
attr_accessor :file
attr_reader :timer
# Retrieve a config value
def [](param)
value(param)
end
# Set a config value. This doesn't set the defaults, it sets the value itself.
def []=(param, value)
set_value(param, value, :memory)
end
# Generate the list of valid arguments, in a format that GetoptLong can
# understand, and add them to the passed option list.
def addargs(options)
# Add all of the config parameters as valid options.
self.each { |name, setting|
setting.getopt_args.each { |args| options << args }
}
return options
end
# Generate the list of valid arguments, in a format that OptionParser can
# understand, and add them to the passed option list.
def optparse_addargs(options)
# Add all of the config parameters as valid options.
self.each { |name, setting|
options << setting.optparse_args
}
return options
end
# Is our parameter a boolean parameter?
def boolean?(param)
param = param.to_sym
if @config.include?(param) and @config[param].kind_of? BooleanSetting
return true
else
return false
end
end
# Remove all set values, potentially skipping cli values.
def clear(exceptcli = false)
@sync.synchronize do
unsafe_clear(exceptcli)
end
end
# Remove all set values, potentially skipping cli values.
def unsafe_clear(exceptcli = false)
@values.each do |name, values|
@values.delete(name) unless exceptcli and name == :cli
end
# Don't clear the 'used' in this case, since it's a config file reparse,
# and we want to retain this info.
unless exceptcli
@used = []
end
@cache.clear
@name = nil
end
# This is mostly just used for testing.
def clearused
@cache.clear
@used = []
end
# Do variable interpolation on the value.
def convert(value, environment = nil)
return value unless value
return value unless value.is_a? String
newval = value.gsub(/\$(\w+)|\$\{(\w+)\}/) do |value|
varname = $2 || $1
if varname == "environment" and environment
environment
elsif pval = self.value(varname)
pval
else
raise Puppet::DevError, "Could not find value for %s" % value
end
end
return newval
end
# Return a value's description.
def description(name)
if obj = @config[name.to_sym]
obj.desc
else
nil
end
end
def each
@config.each { |name, object|
yield name, object
}
end
# Iterate over each section name.
def eachsection
yielded = []
@config.each do |name, object|
section = object.section
unless yielded.include? section
yield section
yielded << section
end
end
end
# Return an object by name.
def setting(param)
param = param.to_sym
@config[param]
end
# Handle a command-line argument.
def handlearg(opt, value = nil)
@cache.clear
value = munge_value(value) if value
str = opt.sub(/^--/,'')
bool = true
newstr = str.sub(/^no-/, '')
if newstr != str
str = newstr
bool = false
end
str = str.intern
if @config[str].is_a?(Puppet::Util::Settings::BooleanSetting)
if value == "" or value.nil?
value = bool
end
end
set_value(str, value, :cli)
end
def without_noop
old_noop = value(:noop,:cli) and set_value(:noop, false, :cli) if valid?(:noop)
yield
ensure
set_value(:noop, old_noop, :cli) if valid?(:noop)
end
def include?(name)
name = name.intern if name.is_a? String
@config.include?(name)
end
# check to see if a short name is already defined
def shortinclude?(short)
short = short.intern if name.is_a? String
@shortnames.include?(short)
end
# Create a new collection of config settings.
def initialize
@config = {}
@shortnames = {}
@created = []
@searchpath = nil
# Mutex-like thing to protect @values
@sync = Sync.new
# Keep track of set values.
@values = Hash.new { |hash, key| hash[key] = {} }
# And keep a per-environment cache
@cache = Hash.new { |hash, key| hash[key] = {} }
# A central concept of a name.
@name = nil
# The list of sections we've used.
@used = []
end
# NOTE: ACS ahh the util classes. . .sigh
# as part of a fix for 1183, I pulled the logic for the following 5 methods out of the executables and puppet.rb
# They probably deserve their own class, but I don't want to do that until I can refactor environments
# its a little better than where they were
# Prints the contents of a config file with the available config settings, or it
# prints a single value of a config setting.
def print_config_options
env = value(:environment)
val = value(:configprint)
if val == "all"
hash = {}
each do |name, obj|
val = value(name,env)
val = val.inspect if val == ""
hash[name] = val
end
hash.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, val|
puts "%s = %s" % [name, val]
end
else
val.split(/\s*,\s*/).sort.each do |v|
if include?(v)
#if there is only one value, just print it for back compatibility
if v == val
puts value(val,env)
break
end
puts "%s = %s" % [v, value(v,env)]
else
puts "invalid parameter: %s" % v
return false
end
end
end
true
end
def generate_config
puts to_config
true
end
def generate_manifest
puts to_manifest
true
end
def print_configs
return print_config_options if value(:configprint) != ""
return generate_config if value(:genconfig)
return generate_manifest if value(:genmanifest)
end
def print_configs?
return (value(:configprint) != "" || value(:genconfig) || value(:genmanifest)) && true
end
# Return a given object's file metadata.
def metadata(param)
if obj = @config[param.to_sym] and obj.is_a?(FileSetting)
return [:owner, :group, :mode].inject({}) do |meta, p|
if v = obj.send(p)
meta[p] = v
end
meta
end
else
nil
end
end
# Make a directory with the appropriate user, group, and mode
def mkdir(default)
obj = get_config_file_default(default)
Puppet::Util::SUIDManager.asuser(obj.owner, obj.group) do
mode = obj.mode || 0750
Dir.mkdir(obj.value, mode)
end
end
# Figure out our name.
def name
unless @name
unless @config[:name]
return nil
end
searchpath.each do |source|
next if source == :name
@sync.synchronize do
@name = @values[source][:name]
end
break if @name
end
unless @name
@name = convert(@config[:name].default).intern
end
end
@name
end
# Return all of the parameters associated with a given section.
def params(section = nil)
if section
section = section.intern if section.is_a? String
@config.find_all { |name, obj|
obj.section == section
}.collect { |name, obj|
name
}
else
@config.keys
end
end
# Parse the configuration file. Just provides
# thread safety.
def parse
raise "No :config setting defined; cannot parse unknown config file" unless self[:config]
# Create a timer so that this file will get checked automatically
# and reparsed if necessary.
set_filetimeout_timer()
@sync.synchronize do
unsafe_parse(self[:config])
end
end
# Unsafely parse the file -- this isn't thread-safe and causes plenty of problems if used directly.
def unsafe_parse(file)
return unless FileTest.exist?(file)
begin
data = parse_file(file)
rescue => details
puts details.backtrace if Puppet[:trace]
Puppet.err "Could not parse #{file}: #{details}"
return
end
unsafe_clear(true)
data.each do |area, values|
@values[area] = values
end
# Determine our environment, if we have one.
if @config[:environment]
env = self.value(:environment).to_sym
else
env = "none"
end
# Call any hooks we should be calling.
settings_with_hooks.each do |setting|
each_source(env) do |source|
if value = @values[source][setting.name]
# We still have to use value() to retrieve the value, since
# we want the fully interpolated value, not $vardir/lib or whatever.
# This results in extra work, but so few of the settings
# will have associated hooks that it ends up being less work this
# way overall.
setting.handle(self.value(setting.name, env))
break
end
end
end
# We have to do it in the reverse of the search path,
# because multiple sections could set the same value
# and I'm too lazy to only set the metadata once.
searchpath.reverse.each do |source|
if meta = @values[source][:_meta]
set_metadata(meta)
end
end
end
# Create a new setting. The value is passed in because it's used to determine
# what kind of setting we're creating, but the value itself might be either
# a default or a value, so we can't actually assign it.
def newsetting(hash)
klass = nil
if hash[:section]
hash[:section] = hash[:section].to_sym
end
if type = hash[:type]
unless klass = {:setting => Setting, :file => FileSetting, :boolean => BooleanSetting}[type]
raise ArgumentError, "Invalid setting type '%s'" % type
end
hash.delete(:type)
else
case hash[:default]
when true, false, "true", "false"
klass = BooleanSetting
when /^\$\w+\//, /^\//
klass = FileSetting
when String, Integer, Float # nothing
klass = Setting
else
raise Puppet::Error, "Invalid value '%s' for %s" % [value.inspect, hash[:name]]
end
end
hash[:settings] = self
setting = klass.new(hash)
return setting
end
# This has to be private, because it doesn't add the settings to @config
private :newsetting
# Iterate across all of the objects in a given section.
def persection(section)
section = section.to_sym
self.each { |name, obj|
if obj.section == section
yield obj
end
}
end
# Cache this in an easily clearable way, since we were
# having trouble cleaning it up after tests.
cached_attr(:file) do
if path = self[:config] and FileTest.exist?(path)
Puppet::Util::LoadedFile.new(path)
end
end
# Reparse our config file, if necessary.
def reparse
if file and file.changed?
Puppet.notice "Reparsing %s" % file.file
parse
reuse()
end
end
def reuse
return unless defined? @used
@sync.synchronize do # yay, thread-safe
new = @used
@used = []
self.use(*new)
end
end
# The order in which to search for values.
def searchpath(environment = nil)
if environment
[:cli, :memory, environment, :name, :main]
else
[:cli, :memory, :name, :main]
end
end
# Get a list of objects per section
def sectionlist
sectionlist = []
self.each { |name, obj|
section = obj.section || "puppet"
sections[section] ||= []
unless sectionlist.include?(section)
sectionlist << section
end
sections[section] << obj
}
return sectionlist, sections
end
def service_user_available?
return @service_user_available if defined?(@service_user_available)
return @service_user_available = false unless user_name = self[:user]
user = Puppet::Type.type(:user).new :name => self[:user], :check => :ensure
return @service_user_available = user.exists?
end
def set_value(param, value, type)
param = param.to_sym
unless setting = @config[param]
raise ArgumentError,
"Attempt to assign a value to unknown configuration parameter %s" % param.inspect
end
if setting.respond_to?(:munge)
value = setting.munge(value)
end
if setting.respond_to?(:handle)
setting.handle(value)
end
# Reset the name, so it's looked up again.
if param == :name
@name = nil
end
@sync.synchronize do # yay, thread-safe
@values[type][param] = value
@cache.clear
clearused
# Clear the list of environments, because they cache, at least, the module path.
# We *could* preferentially just clear them if the modulepath is changed,
# but we don't really know if, say, the vardir is changed and the modulepath
# is defined relative to it. We need the defined? stuff because of loading
# order issues.
Puppet::Node::Environment.clear if defined?(Puppet::Node) and defined?(Puppet::Node::Environment)
end
return value
end
private :set_value
# Set a bunch of defaults in a given section. The sections are actually pretty
# pointless, but they help break things up a bit, anyway.
def setdefaults(section, defs)
section = section.to_sym
call = []
defs.each { |name, hash|
if hash.is_a? Array
unless hash.length == 2
raise ArgumentError, "Defaults specified as an array must contain only the default value and the decription"
end
tmp = hash
hash = {}
[:default, :desc].zip(tmp).each { |p,v| hash[p] = v }
end
name = name.to_sym
hash[:name] = name
hash[:section] = section
if @config.include?(name)
raise ArgumentError, "Parameter %s is already defined" % name
end
tryconfig = newsetting(hash)
if short = tryconfig.short
if other = @shortnames[short]
raise ArgumentError, "Parameter %s is already using short name '%s'" % [other.name, short]
end
@shortnames[short] = tryconfig
end
@config[name] = tryconfig
# Collect the settings that need to have their hooks called immediately.
# We have to collect them so that we can be sure we're fully initialized before
# the hook is called.
call << tryconfig if tryconfig.call_on_define
}
call.each { |setting| setting.handle(self.value(setting.name)) }
end
# Create a timer to check whether the file should be reparsed.
def set_filetimeout_timer
return unless timeout = self[:filetimeout] and timeout = Integer(timeout) and timeout > 0
timer = EventLoop::Timer.new(:interval => timeout, :tolerance => 1, :start? => true) { self.reparse() }
end
# Convert the settings we manage into a catalog full of resources that model those settings.
def to_catalog(*sections)
sections = nil if sections.empty?
catalog = Puppet::Resource::Catalog.new("Settings")
@config.values.find_all { |value| value.is_a?(FileSetting) }.each do |file|
next unless (sections.nil? or sections.include?(file.section))
next unless resource = file.to_resource
next if catalog.resource(resource.ref)
catalog.add_resource(resource)
end
add_user_resources(catalog, sections)
catalog
end
# Convert our list of config settings into a configuration file.
def to_config
str = %{The configuration file for #{Puppet[:name]}. Note that this file
is likely to have unused configuration parameters in it; any parameter that's
valid anywhere in Puppet can be in any config file, even if it's not used.
Every section can specify three special parameters: owner, group, and mode.
These parameters affect the required permissions of any files specified after
their specification. Puppet will sometimes use these parameters to check its
own configured state, so they can be used to make Puppet a bit more self-managing.
Generated on #{Time.now}.
}.gsub(/^/, "# ")
# Add a section heading that matches our name.
if @config.include?(:name)
str += "[%s]\n" % self[:name]
end
eachsection do |section|
persection(section) do |obj|
str += obj.to_config + "\n"
end
end
return str
end
# Convert to a parseable manifest
def to_manifest
catalog = to_catalog
# The resource list is a list of references, not actual instances.
catalog.resources.collect do |ref|
catalog.resource(ref).to_manifest
end.join("\n\n")
end
# Create the necessary objects to use a section. This is idempotent;
# you can 'use' a section as many times as you want.
def use(*sections)
sections = sections.collect { |s| s.to_sym }
@sync.synchronize do # yay, thread-safe
sections = sections.reject { |s| @used.include?(s) }
return if sections.empty?
begin
catalog = to_catalog(*sections).to_ral
rescue => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Could not create resources for managing Puppet's files and directories in sections %s: %s" % [sections.inspect, detail]
# We need some way to get rid of any resources created during the catalog creation
# but not cleaned up.
return
end
without_noop do
catalog.host_config = false
catalog.apply do |transaction|
if transaction.any_failed?
report = transaction.report
failures = report.logs.find_all { |log| log.level == :err }
raise "Got %s failure(s) while initializing: %s" % [failures.length, failures.collect { |l| l.to_s }.join("; ")]
end
end
end
sections.each { |s| @used << s }
@used.uniq!
end
end
def valid?(param)
param = param.to_sym
@config.has_key?(param)
end
def uninterpolated_value(param, environment = nil)
param = param.to_sym
environment = environment.to_sym if environment
# See if we can find it within our searchable list of values
val = catch :foundval do
each_source(environment) do |source|
# Look for the value. We have to test the hash for whether
# it exists, because the value might be false.
@sync.synchronize do
if @values[source].include?(param)
throw :foundval, @values[source][param]
end
end
end
throw :foundval, nil
end
# If we didn't get a value, use the default
val = @config[param].default if val.nil?
return val
end
# Find the correct value using our search path. Optionally accept an environment
# in which to search before the other configuration sections.
def value(param, environment = nil)
param = param.to_sym
environment = environment.to_sym if environment
# Short circuit to nil for undefined parameters.
return nil unless @config.include?(param)
# Yay, recursion.
#self.reparse() unless [:config, :filetimeout].include?(param)
# Check the cache first. It needs to be a per-environment
# cache so that we don't spread values from one env
# to another.
if cached = @cache[environment||"none"][param]
return cached
end
val = uninterpolated_value(param, environment)
# Convert it if necessary
val = convert(val, environment)
# And cache it
@cache[environment||"none"][param] = val
return val
end
# Open a file with the appropriate user, group, and mode
def write(default, *args, &bloc)
obj = get_config_file_default(default)
writesub(default, value(obj.name), *args, &bloc)
end
# Open a non-default file under a default dir with the appropriate user,
# group, and mode
def writesub(default, file, *args, &bloc)
obj = get_config_file_default(default)
chown = nil
if Puppet::Util::SUIDManager.uid == 0
chown = [obj.owner, obj.group]
else
chown = [nil, nil]
end
Puppet::Util::SUIDManager.asuser(*chown) do
mode = obj.mode || 0640
if args.empty?
args << "w"
end
args << mode
# Update the umask to make non-executable files
Puppet::Util.withumask(File.umask ^ 0111) do
File.open(file, *args) do |file|
yield file
end
end
end
end
def readwritelock(default, *args, &bloc)
file = value(get_config_file_default(default).name)
tmpfile = file + ".tmp"
sync = Sync.new
unless FileTest.directory?(File.dirname(tmpfile))
raise Puppet::DevError, "Cannot create %s; directory %s does not exist" %
[file, File.dirname(file)]
end
sync.synchronize(Sync::EX) do
File.open(file, ::File::CREAT|::File::RDWR, 0600) do |rf|
rf.lock_exclusive do
if File.exist?(tmpfile)
raise Puppet::Error, ".tmp file already exists for %s; Aborting locked write. Check the .tmp file and delete if appropriate" %
[file]
end
# If there's a failure, remove our tmpfile
begin
writesub(default, tmpfile, *args, &bloc)
rescue
File.unlink(tmpfile) if FileTest.exist?(tmpfile)
raise
end
begin
File.rename(tmpfile, file)
rescue => detail
Puppet.err "Could not rename %s to %s: %s" % [file, tmpfile, detail]
File.unlink(tmpfile) if FileTest.exist?(tmpfile)
end
end
end
end
end
private
def get_config_file_default(default)
obj = nil
unless obj = @config[default]
raise ArgumentError, "Unknown default %s" % default
end
unless obj.is_a? FileSetting
raise ArgumentError, "Default %s is not a file" % default
end
return obj
end
# Create the transportable objects for users and groups.
def add_user_resources(catalog, sections)
return unless Puppet.features.root?
return unless self[:mkusers]
@config.each do |name, setting|
next unless setting.respond_to?(:owner)
next unless sections.nil? or sections.include?(setting.section)
if user = setting.owner and user != "root" and catalog.resource(:user, user).nil?
resource = Puppet::Resource.new(:user, user, :parameters => {:ensure => :present})
if self[:group]
resource[:gid] = self[:group]
end
catalog.add_resource resource
end
if group = setting.group and ! %w{root wheel}.include?(group) and catalog.resource(:group, group).nil?
catalog.add_resource Puppet::Resource.new(:group, group, :parameters => {:ensure => :present})
end
end
end
# Yield each search source in turn.
def each_source(environment)
searchpath(environment).each do |source|
# Modify the source as necessary.
source = self.name if source == :name
yield source
end
end
# Return all settings that have associated hooks; this is so
# we can call them after parsing the configuration file.
def settings_with_hooks
@config.values.find_all { |setting| setting.respond_to?(:handle) }
end
# Extract extra setting information for files.
def extract_fileinfo(string)
result = {}
value = string.sub(/\{\s*([^}]+)\s*\}/) do
params = $1
params.split(/\s*,\s*/).each do |str|
if str =~ /^\s*(\w+)\s*=\s*([\w\d]+)\s*$/
param, value = $1.intern, $2
result[param] = value
unless [:owner, :mode, :group].include?(param)
raise ArgumentError, "Invalid file option '%s'" % param
end
if param == :mode and value !~ /^\d+$/
raise ArgumentError, "File modes must be numbers"
end
else
raise ArgumentError, "Could not parse '%s'" % string
end
end
''
end
result[:value] = value.sub(/\s*$/, '')
return result
end
# Convert arguments into booleans, integers, or whatever.
def munge_value(value)
# Handle different data types correctly
return case value
when /^false$/i; false
when /^true$/i; true
when /^\d+$/i; Integer(value)
when true; true
when false; false
else
value.gsub(/^["']|["']$/,'').sub(/\s+$/, '')
end
end
# This is an abstract method that just turns a file in to a hash of hashes.
# We mostly need this for backward compatibility -- as of May 2007 we need to
# support parsing old files with any section, or new files with just two
# valid sections.
def parse_file(file)
text = read_file(file)
result = Hash.new { |names, name|
names[name] = {}
}
count = 0
# Default to 'main' for the section.
section = :main
result[section][:_meta] = {}
text.split(/\n/).each { |line|
count += 1
case line
when /^\s*\[(\w+)\]$/
section = $1.intern # Section names
# Add a meta section
result[section][:_meta] ||= {}
when /^\s*#/; next # Skip comments
when /^\s*$/; next # Skip blanks
when /^\s*(\w+)\s*=\s*(.*)$/ # settings
var = $1.intern
# We don't want to munge modes, because they're specified in octal, so we'll
# just leave them as a String, since Puppet handles that case correctly.
if var == :mode
value = $2
else
value = munge_value($2)
end
# Check to see if this is a file argument and it has extra options
begin
if value.is_a?(String) and options = extract_fileinfo(value)
value = options[:value]
options.delete(:value)
result[section][:_meta][var] = options
end
result[section][var] = value
rescue Puppet::Error => detail
detail.file = file
detail.line = line
raise
end
else
error = Puppet::Error.new("Could not match line %s" % line)
error.file = file
error.line = line
raise error
end
}
return result
end
# Read the file in.
def read_file(file)
begin
return File.read(file)
rescue Errno::ENOENT
raise ArgumentError, "No such file %s" % file
rescue Errno::EACCES
raise ArgumentError, "Permission denied to file %s" % file
end
end
# Set file metadata.
def set_metadata(meta)
meta.each do |var, values|
values.each do |param, value|
@config[var].send(param.to_s + "=", value)
end
end
end
end
diff --git a/spec/integration/application/puppet.rb b/spec/integration/application/puppet.rb
index 1342f3c5f..cfafc9c0c 100755
--- a/spec/integration/application/puppet.rb
+++ b/spec/integration/application/puppet.rb
@@ -1,33 +1,33 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet_spec/files'
require 'puppet/application/puppet'
describe "Puppet" do
include PuppetSpec::Files
describe "when applying provided catalogs" do
confine "PSON library is missing; cannot test applying catalogs" => Puppet.features.pson?
it "should be able to apply catalogs provided in a file in pson" do
file_to_create = tmpfile("pson_catalog")
catalog = Puppet::Resource::Catalog.new
- resource = Puppet::Resource.new(:file, file_to_create, :content => "my stuff")
+ resource = Puppet::Resource.new(:file, file_to_create, :parameters => {:content => "my stuff"})
catalog.add_resource resource
manifest = tmpfile("manifest")
File.open(manifest, "w") { |f| f.print catalog.to_pson }
puppet = Puppet::Application[:puppet]
puppet.options[:catalog] = manifest
puppet.apply
File.should be_exist(file_to_create)
File.read(file_to_create).should == "my stuff"
end
end
end
diff --git a/spec/integration/indirector/catalog/compiler.rb b/spec/integration/indirector/catalog/compiler.rb
index 16102cafe..b4067a6bb 100755
--- a/spec/integration/indirector/catalog/compiler.rb
+++ b/spec/integration/indirector/catalog/compiler.rb
@@ -1,67 +1,68 @@
#!/usr/bin/env ruby
Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }
require 'puppet/resource/catalog'
Puppet::Resource::Catalog.indirection.terminus(:compiler)
describe Puppet::Resource::Catalog::Compiler do
before do
+ Facter.stubs(:value).returns "something"
@catalog = Puppet::Resource::Catalog.new
@one = Puppet::Resource.new(:file, "/one")
@two = Puppet::Resource.new(:file, "/two")
@catalog.add_resource(@one, @two)
end
after { Puppet.settings.clear }
it "should remove virtual resources when filtering" do
@one.virtual = true
Puppet::Resource::Catalog.indirection.terminus.filter(@catalog).resources.should == [ @two.ref ]
end
it "should not remove exported resources when filtering" do
@one.exported = true
Puppet::Resource::Catalog.indirection.terminus.filter(@catalog).resources.sort.should == [ @one.ref, @two.ref ]
end
it "should remove virtual exported resources when filtering" do
@one.exported = true
@one.virtual = true
Puppet::Resource::Catalog.indirection.terminus.filter(@catalog).resources.should == [ @two.ref ]
end
it "should filter out virtual resources when finding a catalog" do
@one.virtual = true
request = stub 'request', :name => "mynode"
Puppet::Resource::Catalog.indirection.terminus.stubs(:extract_facts_from_request)
Puppet::Resource::Catalog.indirection.terminus.stubs(:node_from_request)
Puppet::Resource::Catalog.indirection.terminus.stubs(:compile).returns(@catalog)
Puppet::Resource::Catalog.find(request).resources.should == [ @two.ref ]
end
it "should not filter out exported resources when finding a catalog" do
@one.exported = true
request = stub 'request', :name => "mynode"
Puppet::Resource::Catalog.indirection.terminus.stubs(:extract_facts_from_request)
Puppet::Resource::Catalog.indirection.terminus.stubs(:node_from_request)
Puppet::Resource::Catalog.indirection.terminus.stubs(:compile).returns(@catalog)
Puppet::Resource::Catalog.find(request).resources.sort.should == [ @one.ref, @two.ref ]
end
it "should filter out virtual exported resources when finding a catalog" do
@one.exported = true
@one.virtual = true
request = stub 'request', :name => "mynode"
Puppet::Resource::Catalog.indirection.terminus.stubs(:extract_facts_from_request)
Puppet::Resource::Catalog.indirection.terminus.stubs(:node_from_request)
Puppet::Resource::Catalog.indirection.terminus.stubs(:compile).returns(@catalog)
Puppet::Resource::Catalog.find(request).resources.should == [ @two.ref ]
end
end
diff --git a/spec/unit/configurer.rb b/spec/unit/configurer.rb
index 9fc46affd..48a197a37 100755
--- a/spec/unit/configurer.rb
+++ b/spec/unit/configurer.rb
@@ -1,448 +1,449 @@
#!/usr/bin/env ruby
#
# Created by Luke Kanies on 2007-11-12.
# Copyright (c) 2007. All rights reserved.
require File.dirname(__FILE__) + '/../spec_helper'
require 'puppet/configurer'
describe Puppet::Configurer do
before do
Puppet.settings.stubs(:use).returns(true)
@agent = Puppet::Configurer.new
end
it "should include the Plugin Handler module" do
Puppet::Configurer.ancestors.should be_include(Puppet::Configurer::PluginHandler)
end
it "should include the Fact Handler module" do
Puppet::Configurer.ancestors.should be_include(Puppet::Configurer::FactHandler)
end
it "should use the puppetdlockfile as its lockfile path" do
Puppet.settings.expects(:value).with(:puppetdlockfile).returns("/my/lock")
Puppet::Configurer.lockfile_path.should == "/my/lock"
end
describe "when executing a pre-run hook" do
it "should do nothing if the hook is set to an empty string" do
Puppet.settings[:prerun_command] = ""
Puppet::Util.expects(:exec).never
@agent.execute_prerun_command
end
it "should execute any pre-run command provided via the 'prerun_command' setting" do
Puppet.settings[:prerun_command] = "/my/command"
Puppet::Util.expects(:execute).with { |args| args[0] == "/my/command" }
@agent.execute_prerun_command
end
it "should fail if the command fails" do
Puppet.settings[:prerun_command] = "/my/command"
Puppet::Util.expects(:execute).raises Puppet::ExecutionFailure
lambda { @agent.execute_prerun_command }.should raise_error(Puppet::Configurer::CommandHookError)
end
end
describe "when executing a post-run hook" do
it "should do nothing if the hook is set to an empty string" do
Puppet.settings[:postrun_command] = ""
Puppet::Util.expects(:exec).never
@agent.execute_postrun_command
end
it "should execute any post-run command provided via the 'postrun_command' setting" do
Puppet.settings[:postrun_command] = "/my/command"
Puppet::Util.expects(:execute).with { |args| args[0] == "/my/command" }
@agent.execute_postrun_command
end
it "should fail if the command fails" do
Puppet.settings[:postrun_command] = "/my/command"
Puppet::Util.expects(:execute).raises Puppet::ExecutionFailure
lambda { @agent.execute_postrun_command }.should raise_error(Puppet::Configurer::CommandHookError)
end
end
end
describe Puppet::Configurer, "when initializing a report" do
it "should return an instance of a transaction report" do
Puppet.settings.stubs(:use).returns(true)
@agent = Puppet::Configurer.new
@agent.initialize_report.should be_instance_of(Puppet::Transaction::Report)
end
end
describe Puppet::Configurer, "when executing a catalog run" do
before do
Puppet.settings.stubs(:use).returns(true)
@agent = Puppet::Configurer.new
+ @agent.stubs(:prepare)
@agent.stubs(:facts_for_uploading).returns({})
@agent.stubs(:retrieve_catalog).returns Puppet::Resource::Catalog.new
Puppet::Util::Log.stubs(:newdestination)
Puppet::Util::Log.stubs(:close)
end
it "should prepare for the run" do
@agent.expects(:prepare)
@agent.run
end
it "should initialize a transaction report" do
report = stub 'report'
@agent.expects(:initialize_report).returns report
@agent.run
end
it "should set the report as a log destination" do
report = stub 'report'
@agent.expects(:initialize_report).returns report
Puppet::Util::Log.expects(:newdestination).with(report)
@agent.run
end
it "should retrieve the catalog" do
@agent.expects(:retrieve_catalog)
@agent.run
end
it "should log a failure and do nothing if no catalog can be retrieved" do
@agent.expects(:retrieve_catalog).returns nil
- Puppet.expects(:err)
+ Puppet.expects(:err).with "Could not retrieve catalog; skipping run"
@agent.run
end
it "should apply the catalog with all options to :run" do
catalog = stub 'catalog', :retrieval_duration= => nil
@agent.expects(:retrieve_catalog).returns catalog
catalog.expects(:apply).with { |args| args[:one] == true }
@agent.run :one => true
end
it "should accept a catalog and use it instead of retrieving a different one" do
catalog = stub 'catalog', :retrieval_duration= => nil
@agent.expects(:retrieve_catalog).never
catalog.expects(:apply)
@agent.run :one => true, :catalog => catalog
end
it "should benchmark how long it takes to apply the catalog" do
@agent.expects(:benchmark).with(:notice, "Finished catalog run")
catalog = stub 'catalog', :retrieval_duration= => nil
@agent.expects(:retrieve_catalog).returns catalog
catalog.expects(:apply).never # because we're not yielding
@agent.run
end
it "should execute post-run hooks after the run" do
@agent.expects(:execute_postrun_command)
@agent.run
end
it "should send the report" do
report = stub 'report'
@agent.expects(:initialize_report).returns report
@agent.expects(:send_report).with { |r, trans| r == report }
@agent.run
end
it "should send the transaction report with a reference to the transaction if a run was actually made" do
report = stub 'report'
@agent.expects(:initialize_report).returns report
catalog = stub 'catalog', :retrieval_duration= => nil
trans = stub 'transaction'
catalog.expects(:apply).returns trans
@agent.expects(:send_report).with { |r, t| t == trans }
@agent.run :catalog => catalog
end
it "should send the transaction report even if the catalog could not be retrieved" do
@agent.expects(:retrieve_catalog).returns nil
report = stub 'report'
@agent.expects(:initialize_report).returns report
@agent.expects(:send_report)
@agent.run
end
it "should send the transaction report even if there is a failure" do
@agent.expects(:retrieve_catalog).raises "whatever"
report = stub 'report'
@agent.expects(:initialize_report).returns report
@agent.expects(:send_report)
lambda { @agent.run }.should raise_error
end
it "should remove the report as a log destination when the run is finished" do
report = stub 'report'
@agent.expects(:initialize_report).returns report
Puppet::Util::Log.expects(:close).with(report)
@agent.run
end
it "should return the report as the result of the run" do
report = stub 'report'
@agent.expects(:initialize_report).returns report
@agent.run.should equal(report)
end
end
describe Puppet::Configurer, "when sending a report" do
before do
Puppet.settings.stubs(:use).returns(true)
@configurer = Puppet::Configurer.new
@report = stub 'report'
@trans = stub 'transaction'
end
it "should require a report" do
lambda { @configurer.send_report }.should raise_error(ArgumentError)
end
it "should allow specification of a transaction" do
lambda { @configurer.send_report(@report, @trans) }.should_not raise_error(ArgumentError)
end
it "should use any provided transaction to add metrics to the report" do
@trans.expects(:generate_report)
@configurer.send_report(@report, @trans)
end
it "should print a report summary if configured to do so" do
Puppet.settings[:summarize] = true
@report.expects(:summary).returns "stuff"
@configurer.expects(:puts).with("stuff")
@configurer.send_report(@report)
end
it "should not print a report summary if not configured to do so" do
Puppet.settings[:summarize] = false
@configurer.expects(:puts).never
@configurer.send_report(@report)
end
it "should save the report if reporting is enabled" do
Puppet.settings[:report] = true
@report.expects(:save)
@configurer.send_report(@report)
end
it "should not save the report if reporting is disabled" do
Puppet.settings[:report] = false
@report.expects(:save).never
@configurer.send_report(@report)
end
it "should log but not fail if saving the report fails" do
Puppet.settings[:report] = true
@report.expects(:save).raises "whatever"
Puppet.expects(:err)
lambda { @configurer.send_report(@report) }.should_not raise_error
end
end
describe Puppet::Configurer, "when retrieving a catalog" do
before do
Puppet.settings.stubs(:use).returns(true)
@agent = Puppet::Configurer.new
@agent.stubs(:facts_for_uploading).returns({})
@catalog = Puppet::Resource::Catalog.new
@agent.stubs(:convert_catalog).returns @catalog
end
it "should use the Catalog class to get its catalog" do
Puppet::Resource::Catalog.expects(:find).returns @catalog
@agent.retrieve_catalog
end
it "should use its certname to retrieve the catalog" do
Facter.stubs(:value).returns "eh"
Puppet.expects(:[]).with(:certname).returns "myhost.domain.com"
Puppet::Resource::Catalog.expects(:find).with { |name, options| name == "myhost.domain.com" }.returns @catalog
@agent.retrieve_catalog
end
it "should pass the prepared facts and the facts format as arguments when retrieving the catalog" do
@agent.expects(:facts_for_uploading).returns(:facts => "myfacts", :facts_format => :foo)
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:facts] == "myfacts" and options[:facts_format] == :foo }.returns @catalog
@agent.retrieve_catalog
end
it "should default to returning a catalog retrieved directly from the server, skipping the cache" do
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns @catalog
@agent.retrieve_catalog.should == @catalog
end
it "should log and return the cached catalog when no catalog can be retrieved from the server" do
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns @catalog
Puppet.expects(:notice)
@agent.retrieve_catalog.should == @catalog
end
it "should not look in the cache for a catalog if one is returned from the server" do
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns @catalog
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.never
@agent.retrieve_catalog.should == @catalog
end
it "should return the cached catalog when retrieving the remote catalog throws an exception" do
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.raises "eh"
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns @catalog
@agent.retrieve_catalog.should == @catalog
end
it "should log and return nil if no catalog can be retrieved from the server and :usecacheonfailure is disabled" do
Puppet.stubs(:[])
Puppet.expects(:[]).with(:usecacheonfailure).returns false
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil
Puppet.expects(:warning)
@agent.retrieve_catalog.should be_nil
end
it "should return nil if no cached catalog is available and no catalog can be retrieved from the server" do
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_cache] == true }.returns nil
Puppet::Resource::Catalog.expects(:find).with { |name, options| options[:ignore_terminus] == true }.returns nil
@agent.retrieve_catalog.should be_nil
end
it "should convert the catalog before returning" do
Puppet::Resource::Catalog.stubs(:find).returns @catalog
@agent.expects(:convert_catalog).with { |cat, dur| cat == @catalog }.returns "converted catalog"
@agent.retrieve_catalog.should == "converted catalog"
end
it "should return nil if there is an error while retrieving the catalog" do
Puppet::Resource::Catalog.expects(:find).raises "eh"
@agent.retrieve_catalog.should be_nil
end
end
describe Puppet::Configurer, "when converting the catalog" do
before do
Puppet.settings.stubs(:use).returns(true)
@agent = Puppet::Configurer.new
@catalog = Puppet::Resource::Catalog.new
@oldcatalog = stub 'old_catalog', :to_ral => @catalog
end
it "should convert the catalog to a RAL-formed catalog" do
@oldcatalog.expects(:to_ral).returns @catalog
@agent.convert_catalog(@oldcatalog, 10).should equal(@catalog)
end
it "should finalize the catalog" do
@catalog.expects(:finalize)
@agent.convert_catalog(@oldcatalog, 10)
end
it "should record the passed retrieval time with the RAL catalog" do
@catalog.expects(:retrieval_duration=).with 10
@agent.convert_catalog(@oldcatalog, 10)
end
it "should write the RAL catalog's class file" do
@catalog.expects(:write_class_file)
@agent.convert_catalog(@oldcatalog, 10)
end
it "should mark the RAL catalog as a host catalog" do
@catalog.expects(:host_config=).with true
@agent.convert_catalog(@oldcatalog, 10)
end
end
describe Puppet::Configurer, "when preparing for a run" do
before do
Puppet.settings.stubs(:use).returns(true)
@agent = Puppet::Configurer.new
@agent.stubs(:dostorage)
@agent.stubs(:download_fact_plugins)
@agent.stubs(:download_plugins)
@agent.stubs(:execute_prerun_command)
@facts = {"one" => "two", "three" => "four"}
end
it "should initialize the metadata store" do
@agent.class.stubs(:facts).returns(@facts)
@agent.expects(:dostorage)
@agent.prepare
end
it "should download fact plugins" do
@agent.expects(:download_fact_plugins)
@agent.prepare
end
it "should download plugins" do
@agent.expects(:download_plugins)
@agent.prepare
end
it "should perform the pre-run commands" do
@agent.expects(:execute_prerun_command)
@agent.prepare
end
end
diff --git a/spec/unit/indirector/catalog/compiler.rb b/spec/unit/indirector/catalog/compiler.rb
index d11daaa96..8339d1861 100755
--- a/spec/unit/indirector/catalog/compiler.rb
+++ b/spec/unit/indirector/catalog/compiler.rb
@@ -1,265 +1,265 @@
#!/usr/bin/env ruby
#
# Created by Luke Kanies on 2007-9-23.
# Copyright (c) 2007. All rights reserved.
require File.dirname(__FILE__) + '/../../../spec_helper'
require 'puppet/indirector/catalog/compiler'
describe Puppet::Resource::Catalog::Compiler do
before do
Puppet::Rails.stubs(:init)
Facter.stubs(:to_hash).returns({})
- Facter.stubs(:[]).returns(Facter::Util::Fact.new("something"))
+ Facter.stubs(:value).returns(Facter::Util::Fact.new("something"))
end
describe "when initializing" do
before do
Puppet.expects(:version).returns(1)
Facter.expects(:value).with('fqdn').returns("my.server.com")
Facter.expects(:value).with('ipaddress').returns("my.ip.address")
end
it "should gather data about itself" do
Puppet::Resource::Catalog::Compiler.new
end
it "should cache the server metadata and reuse it" do
compiler = Puppet::Resource::Catalog::Compiler.new
node1 = stub 'node1', :merge => nil
node2 = stub 'node2', :merge => nil
compiler.stubs(:compile)
Puppet::Node.stubs(:find).with('node1').returns(node1)
Puppet::Node.stubs(:find).with('node2').returns(node2)
compiler.find(stub('request', :node => 'node1', :options => {}))
compiler.find(stub('node2request', :node => 'node2', :options => {}))
end
it "should provide a method for determining if the catalog is networked" do
compiler = Puppet::Resource::Catalog::Compiler.new
compiler.should respond_to(:networked?)
end
describe "and storeconfigs is enabled" do
before do
- Puppet.settings[:storeconfigs] = true
+ Puppet.settings.expects(:value).with(:storeconfigs).returns true
end
it "should initialize Rails if it is available" do
Puppet.features.expects(:rails?).returns true
Puppet::Rails.expects(:init)
Puppet::Resource::Catalog::Compiler.new
end
it "should fail if Rails is unavailable" do
Puppet.features.expects(:rails?).returns false
Puppet::Rails.expects(:init).never
lambda { Puppet::Resource::Catalog::Compiler.new }.should raise_error(Puppet::Error)
end
end
end
describe "when finding catalogs" do
before do
Facter.stubs(:value).returns("whatever")
@compiler = Puppet::Resource::Catalog::Compiler.new
@name = "me"
@node = Puppet::Node.new @name
@node.stubs(:merge)
Puppet::Node.stubs(:find).returns @node
@request = stub 'request', :key => "does not matter", :node => @name, :options => {}
end
it "should directly use provided nodes" do
Puppet::Node.expects(:find).never
@compiler.expects(:compile).with(@node)
@request.stubs(:options).returns(:use_node => @node)
@compiler.find(@request)
end
it "should use the request's node name if no explicit node is provided" do
Puppet::Node.expects(:find).with(@name).returns(@node)
@compiler.expects(:compile).with(@node)
@compiler.find(@request)
end
it "should use the provided node name if no explicit node is provided and no authenticated node information is available" do
@request.expects(:node).returns nil
@request.expects(:key).returns "my_node"
Puppet::Node.expects(:find).with("my_node").returns @node
@compiler.expects(:compile).with(@node)
@compiler.find(@request)
end
it "should fail if no node is passed and none can be found" do
Puppet::Node.stubs(:find).with(@name).returns(nil)
proc { @compiler.find(@request) }.should raise_error(ArgumentError)
end
it "should fail intelligently when searching for a node raises an exception" do
Puppet::Node.stubs(:find).with(@name).raises "eh"
proc { @compiler.find(@request) }.should raise_error(Puppet::Error)
end
it "should pass the found node to the compiler for compiling" do
Puppet::Node.expects(:find).with(@name).returns(@node)
config = mock 'config'
Puppet::Parser::Compiler.expects(:compile).with(@node)
@compiler.find(@request)
end
it "should extract and save any facts from the request" do
Puppet::Node.expects(:find).with(@name).returns @node
@compiler.expects(:extract_facts_from_request).with(@request)
Puppet::Parser::Compiler.stubs(:compile)
@compiler.find(@request)
end
it "should return the results of compiling as the catalog" do
Puppet::Node.stubs(:find).returns(@node)
config = mock 'config'
result = mock 'result'
Puppet::Parser::Compiler.expects(:compile).returns result
@compiler.find(@request).should equal(result)
end
it "should benchmark the compile process" do
Puppet::Node.stubs(:find).returns(@node)
@compiler.stubs(:networked?).returns(true)
@compiler.expects(:benchmark).with do |level, message|
level == :notice and message =~ /^Compiled catalog/
end
Puppet::Parser::Compiler.stubs(:compile)
@compiler.find(@request)
end
end
describe "when extracting facts from the request" do
before do
+ Facter.stubs(:value).returns "something"
@compiler = Puppet::Resource::Catalog::Compiler.new
@request = stub 'request', :options => {}
@facts = stub 'facts', :save => nil
- Facter.stubs(:value).returns "something"
end
it "should do nothing if no facts are provided" do
Puppet::Node::Facts.expects(:convert_from).never
@request.options[:facts] = nil
@compiler.extract_facts_from_request(@request)
end
it "should use the Facts class to deserialize the provided facts" do
@request.options[:facts_format] = "foo"
@request.options[:facts] = "bar"
Puppet::Node::Facts.expects(:convert_from).returns @facts
@compiler.extract_facts_from_request(@request)
end
it "should use the provided fact format" do
@request.options[:facts_format] = "foo"
@request.options[:facts] = "bar"
Puppet::Node::Facts.expects(:convert_from).with { |format, text| format == "foo" }.returns @facts
@compiler.extract_facts_from_request(@request)
end
it "should convert the facts into a fact instance and save it" do
@request.options[:facts_format] = "foo"
@request.options[:facts] = "bar"
Puppet::Node::Facts.expects(:convert_from).returns @facts
@facts.expects(:save)
@compiler.extract_facts_from_request(@request)
end
end
describe "when finding nodes" do
before do
Facter.stubs(:value).returns("whatever")
@compiler = Puppet::Resource::Catalog::Compiler.new
@name = "me"
@node = mock 'node'
@request = stub 'request', :node => @name, :options => {}
@compiler.stubs(:compile)
end
it "should look node information up via the Node class with the provided key" do
@node.stubs :merge
Puppet::Node.expects(:find).with(@name).returns(@node)
@compiler.find(@request)
end
end
describe "after finding nodes" do
before do
Puppet.expects(:version).returns(1)
Facter.expects(:value).with('fqdn').returns("my.server.com")
Facter.expects(:value).with('ipaddress').returns("my.ip.address")
@compiler = Puppet::Resource::Catalog::Compiler.new
@name = "me"
@node = mock 'node'
@request = stub 'request', :node => @name, :options => {}
@compiler.stubs(:compile)
Puppet::Node.stubs(:find).with(@name).returns(@node)
end
it "should add the server's Puppet version to the node's parameters as 'serverversion'" do
@node.expects(:merge).with { |args| args["serverversion"] == "1" }
@compiler.find(@request)
end
it "should add the server's fqdn to the node's parameters as 'servername'" do
@node.expects(:merge).with { |args| args["servername"] == "my.server.com" }
@compiler.find(@request)
end
it "should add the server's IP address to the node's parameters as 'serverip'" do
@node.expects(:merge).with { |args| args["serverip"] == "my.ip.address" }
@compiler.find(@request)
end
end
describe "when filtering resources" do
before :each do
Facter.stubs(:value)
@compiler = Puppet::Resource::Catalog::Compiler.new
@catalog = stub_everything 'catalog'
@catalog.stubs(:respond_to?).with(:filter).returns(true)
end
it "should delegate to the catalog instance filtering" do
@catalog.expects(:filter)
@compiler.filter(@catalog)
end
it "should filter out virtual resources" do
resource = mock 'resource', :virtual? => true
@catalog.stubs(:filter).yields(resource)
@compiler.filter(@catalog)
end
it "should return the same catalog if it doesn't support filtering" do
@catalog.stubs(:respond_to?).with(:filter).returns(false)
@compiler.filter(@catalog).should == @catalog
end
it "should return the filtered catalog" do
catalog = stub 'filtered catalog'
@catalog.stubs(:filter).returns(catalog)
@compiler.filter(@catalog).should == catalog
end
end
end
diff --git a/spec/unit/parser/ast/resource.rb b/spec/unit/parser/ast/resource.rb
index b257cb116..391f4c770 100755
--- a/spec/unit/parser/ast/resource.rb
+++ b/spec/unit/parser/ast/resource.rb
@@ -1,103 +1,102 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../spec_helper'
describe Puppet::Parser::AST::Resource do
ast = Puppet::Parser::AST
before :each do
@title = stub_everything 'title'
@compiler = stub_everything 'compiler'
@scope = Puppet::Parser::Scope.new(:compiler => @compiler)
- @param1 = stub_everything 'parameter', :is_a? => true
@scope.stubs(:resource).returns(stub_everything)
- @params = ast::ASTArray.new( :children => [@param1])
- @resource = ast::Resource.new(:title => @title, :type => "Resource", :params => @params )
+ @resource = ast::Resource.new(:title => @title, :type => "Resource", :params => ast::ASTArray.new(:children => []) )
@resource.stubs(:qualified_type).returns("Resource")
- Puppet::Parser::Resource.stubs(:new).returns(stub_everything)
end
it "should evaluate all its parameters" do
- @param1.expects(:safeevaluate).with(@scope)
+ param = stub 'param'
+ param.expects(:safeevaluate).with(@scope).returns Puppet::Parser::Resource::Param.new(:name => "myparam", :value => "myvalue", :source => stub("source"))
+ @resource.stubs(:params).returns [param]
@resource.evaluate(@scope)
end
it "should evaluate its title" do
@title.expects(:safeevaluate).with(@scope)
@resource.evaluate(@scope)
end
it "should flatten the titles array" do
titles = stub 'titles'
title_array = stub 'title_array', :is_a? => true
titles.stubs(:safeevaluate).with(@scope).returns(title_array)
title_array.expects(:flatten).returns([])
@resource.title = titles
@resource.evaluate(@scope)
end
it "should create one resource objects per title" do
titles = stub 'titles'
title_array = stub 'title_array', :is_a? => true
title_array.stubs(:flatten).returns([@title])
titles.stubs(:safeevaluate).with(@scope).returns(title_array)
- Puppet::Parser::Resource.expects(:new).with { |hash| hash[:title] == @title }
-
@resource.title = titles
- @resource.evaluate(@scope)
+ result = @resource.evaluate(@scope)
+ result[0].should be_instance_of(Puppet::Parser::Resource)
+ result[0].title.should == @title
end
it "should handover resources to the compiler" do
resource = stub 'resource'
titles = stub 'titles'
title_array = stub 'title_array', :is_a? => true
title_array.stubs(:flatten).returns([@title])
titles.stubs(:safeevaluate).with(@scope).returns(title_array)
Puppet::Parser::Resource.stubs(:new).returns(resource)
@compiler.expects(:add_resource).with(@scope, resource)
@resource.title = titles
@resource.evaluate(@scope)
end
it "should return the newly created resources" do
resource = stub 'resource'
titles = stub 'titles'
title_array = stub 'title_array', :is_a? => true
title_array.stubs(:flatten).returns([@title])
titles.stubs(:safeevaluate).with(@scope).returns(title_array)
- Puppet::Parser::Resource.stubs(:new).returns(resource)
- @compiler.stubs(:add_resource).with(resource)
+ @compiler.stubs(:add_resource)
@resource.title = titles
- @resource.evaluate(@scope).should == [resource]
+ @resource.evaluate(@scope)[0].should be_instance_of(Puppet::Parser::Resource)
end
it "should generate virtual resources if it is virtual" do
@resource.virtual = true
- Puppet::Parser::Resource.expects(:new).with { |hash| hash[:virtual] == true }
+ result = @resource.evaluate(@scope)
+ result[0].should be_virtual
@resource.evaluate(@scope)
end
it "should generate virtual and exported resources if it is exported" do
@resource.exported = true
- Puppet::Parser::Resource.expects(:new).with { |hash| hash[:virtual] == true and hash[:exported] == true }
-
- @resource.evaluate(@scope)
+ result = @resource.evaluate(@scope)
+ result[0].should be_virtual
+ result[0].should be_exported
end
end
diff --git a/spec/unit/parser/ast/resource_reference.rb b/spec/unit/parser/ast/resource_reference.rb
index 10d9678c3..ee42694b9 100755
--- a/spec/unit/parser/ast/resource_reference.rb
+++ b/spec/unit/parser/ast/resource_reference.rb
@@ -1,69 +1,46 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../spec_helper'
describe Puppet::Parser::AST::ResourceReference do
ast = Puppet::Parser::AST
before :each do
@scope = Puppet::Parser::Scope.new()
end
- def newref(title, type)
+ def newref(type, title)
title = stub 'title', :safeevaluate => title
ref = Puppet::Parser::AST::ResourceReference.new(:type => type, :title => title)
end
- it "should evaluate correctly reference to builtin types" do
- newref("/tmp/yay", "File").evaluate(@scope).to_s.should == "File[/tmp/yay]"
+ it "should correctly produce reference strings" do
+ newref("File", "/tmp/yay").evaluate(@scope).to_s.should == "File[/tmp/yay]"
end
- %{ "one::two" "one-two"}.each do |type|
- it "should evaluate correctly reference to define" do
- klass = stub 'klass', :title => "three", :name => type
- @scope.stubs(:find_definition).returns(klass)
-
- newref("three", type).evaluate(@scope).to_ref.should == Puppet::Parser::Resource::Reference.new( :type => type, :title => "three" ).to_ref
- end
+ it "should produce a single resource when the title evaluates to a string" do
+ newref("File", "/tmp/yay").evaluate(@scope).should == Puppet::Resource.new("file", "/tmp/yay")
end
- it "should be able to call qualified_class" do
- klass = stub 'klass', :title => "three", :name => "one"
- @scope.expects(:find_hostclass).with("one").returns(klass)
- newref("three","class").qualified_class(@scope,"one").should == "one"
- end
-
- it "should be able to find qualified classes when evaluating" do
- klass = stub 'klass', :title => "one", :name => "one"
- @scope.stubs(:find_hostclass).returns(klass)
-
- evaled = newref("one", "class").evaluate(@scope)
- evaled.type.should == "Class"
- evaled.title.should == "one"
- end
-
- it "should return an array of reference if given an array of titles" do
+ it "should return an array of resources if given an array of titles" do
titles = mock 'titles', :safeevaluate => ["title1","title2"]
- ref = ast::ResourceReference.new( :title => titles, :type => "Resource" )
- ref.stubs(:qualified_type).with(@scope).returns("Resource")
-
- ref.evaluate(@scope).should have(2).elements
+ ref = ast::ResourceReference.new( :title => titles, :type => "File" )
+ ref.evaluate(@scope).should == [
+ Puppet::Resource.new("file", "title1"),
+ Puppet::Resource.new("file", "title2")
+ ]
end
- it "should qualify class of all titles for Class resource references" do
- titles = mock 'titles', :safeevaluate => ["title1","title2"]
- ref = ast::ResourceReference.new( :title => titles, :type => "Class" )
- ref.expects(:qualified_class).with(@scope,"title1").returns("class")
- ref.expects(:qualified_class).with(@scope,"title2").returns("class")
-
- ref.evaluate(@scope)
+ it "should pass its scope's namespaces to all created resource references" do
+ @scope.add_namespace "foo"
+ newref("File", "/tmp/yay").evaluate(@scope).namespaces.should == ["foo"]
end
it "should return a correct representation when converting to string" do
type = stub 'type', :is_a? => true, :to_s => "file"
title = stub 'title', :is_a? => true, :to_s => "[/tmp/a, /tmp/b]"
ast::ResourceReference.new( :type => type, :title => title ).to_s.should == "File[/tmp/a, /tmp/b]"
end
end
diff --git a/spec/unit/parser/collector.rb b/spec/unit/parser/collector.rb
index 7f88bf754..9c2d722e5 100755
--- a/spec/unit/parser/collector.rb
+++ b/spec/unit/parser/collector.rb
@@ -1,556 +1,555 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/parser/collector'
describe Puppet::Parser::Collector, "when initializing" do
before do
@scope = mock 'scope'
@resource_type = 'resource_type'
@form = :exported
@vquery = mock 'vquery'
@equery = mock 'equery'
@collector = Puppet::Parser::Collector.new(@scope, @resource_type, @equery, @vquery, @form)
end
it "should require a scope" do
@collector.scope.should equal(@scope)
end
it "should require a resource type" do
@collector.type.should == 'Resource_type'
end
it "should only accept :virtual or :exported as the collector form" do
proc { @collector = Puppet::Parser::Collector.new(@scope, @resource_type, @vquery, @equery, :other) }.should raise_error(ArgumentError)
end
it "should accept an optional virtual query" do
@collector.vquery.should equal(@vquery)
end
it "should accept an optional exported query" do
@collector.equery.should equal(@equery)
end
it "should canonize the type name" do
@collector = Puppet::Parser::Collector.new(@scope, "resource::type", @equery, @vquery, @form)
@collector.type.should == "Resource::Type"
end
it "should accept an optional resource override" do
@collector = Puppet::Parser::Collector.new(@scope, "resource::type", @equery, @vquery, @form)
override = { :params => "whatever" }
@collector.add_override(override)
@collector.overrides.should equal(override)
end
end
describe Puppet::Parser::Collector, "when collecting specific virtual resources" do
before do
@scope = mock 'scope'
- @resource_type = mock 'resource_type'
@vquery = mock 'vquery'
@equery = mock 'equery'
- @collector = Puppet::Parser::Collector.new(@scope, @resource_type, @equery, @vquery, :virtual)
+ @collector = Puppet::Parser::Collector.new(@scope, "resource_type", @equery, @vquery, :virtual)
end
it "should not fail when it does not find any resources to collect" do
@collector.resources = ["File[virtual1]", "File[virtual2]"]
@scope.stubs(:findresource).returns(false)
proc { @collector.evaluate }.should_not raise_error
end
it "should mark matched resources as non-virtual" do
@collector.resources = ["File[virtual1]", "File[virtual2]"]
one = stub_everything 'one'
one.expects(:virtual=).with(false)
@scope.stubs(:findresource).with("File[virtual1]").returns(one)
@scope.stubs(:findresource).with("File[virtual2]").returns(nil)
@collector.evaluate
end
it "should return matched resources" do
@collector.resources = ["File[virtual1]", "File[virtual2]"]
one = stub_everything 'one'
@scope.stubs(:findresource).with("File[virtual1]").returns(one)
@scope.stubs(:findresource).with("File[virtual2]").returns(nil)
@collector.evaluate.should == [one]
end
it "should delete itself from the compile's collection list if it has found all of its resources" do
@collector.resources = ["File[virtual1]"]
one = stub_everything 'one'
@compiler.expects(:delete_collection).with(@collector)
@scope.expects(:compiler).returns(@compiler)
@scope.stubs(:findresource).with("File[virtual1]").returns(one)
@collector.evaluate
end
it "should not delete itself from the compile's collection list if it has unfound resources" do
@collector.resources = ["File[virtual1]"]
one = stub_everything 'one'
@compiler.expects(:delete_collection).never
@scope.stubs(:findresource).with("File[virtual1]").returns(nil)
@collector.evaluate
end
end
describe Puppet::Parser::Collector, "when collecting virtual and catalog resources" do
before do
@scope = mock 'scope'
@compiler = mock 'compile'
@scope.stubs(:compiler).returns(@compiler)
@resource_type = "Mytype"
@vquery = proc { |res| true }
@collector = Puppet::Parser::Collector.new(@scope, @resource_type, nil, @vquery, :virtual)
end
it "should find all virtual resources matching the vquery" do
one = stub_everything 'one', :type => "Mytype", :virtual? => true
two = stub_everything 'two', :type => "Mytype", :virtual? => true
@compiler.expects(:resources).returns([one, two])
@collector.evaluate.should == [one, two]
end
it "should find all non-virtual resources matching the vquery" do
one = stub_everything 'one', :type => "Mytype", :virtual? => false
two = stub_everything 'two', :type => "Mytype", :virtual? => false
@compiler.expects(:resources).returns([one, two])
@collector.evaluate.should == [one, two]
end
it "should mark all matched resources as non-virtual" do
one = stub_everything 'one', :type => "Mytype", :virtual? => true
one.expects(:virtual=).with(false)
@compiler.expects(:resources).returns([one])
@collector.evaluate
end
it "should return matched resources" do
one = stub_everything 'one', :type => "Mytype", :virtual? => true
two = stub_everything 'two', :type => "Mytype", :virtual? => true
@compiler.expects(:resources).returns([one, two])
@collector.evaluate.should == [one, two]
end
it "should return all resources of the correct type if there is no virtual query" do
one = stub_everything 'one', :type => "Mytype", :virtual? => true
two = stub_everything 'two', :type => "Mytype", :virtual? => true
one.expects(:virtual=).with(false)
two.expects(:virtual=).with(false)
@compiler.expects(:resources).returns([one, two])
@collector = Puppet::Parser::Collector.new(@scope, @resource_type, nil, nil, :virtual)
@collector.evaluate.should == [one, two]
end
it "should not return or mark resources of a different type" do
one = stub_everything 'one', :type => "Mytype", :virtual? => true
two = stub_everything 'two', :type => :other, :virtual? => true
one.expects(:virtual=).with(false)
two.expects(:virtual=).never
@compiler.expects(:resources).returns([one, two])
@collector.evaluate.should == [one]
end
it "should create a resource with overriden parameters" do
one = stub_everything 'one', :type => "Mytype", :virtual? => true, :title => "test"
param = stub 'param'
@compiler.stubs(:add_override)
@compiler.expects(:resources).returns([one])
@collector.add_override(:params => param )
Puppet::Parser::Resource.expects(:new).with { |h|
h[:params] == param
}
@collector.evaluate
end
it "should define a new allow all child_of? on overriden resource" do
one = stub_everything 'one', :type => "Mytype", :virtual? => true, :title => "test"
param = stub 'param'
source = stub 'source'
@compiler.stubs(:add_override)
@compiler.expects(:resources).returns([one])
@collector.add_override(:params => param, :source => source )
Puppet::Parser::Resource.stubs(:new)
source.expects(:meta_def).with { |name,block| name == :child_of? }
@collector.evaluate
end
it "should not override already overriden resources for this same collection in a previous run" do
one = stub_everything 'one', :type => "Mytype", :virtual? => true, :title => "test"
param = stub 'param'
@compiler.stubs(:add_override)
@compiler.expects(:resources).at_least(2).returns([one])
@collector.add_override(:params => param )
Puppet::Parser::Resource.expects(:new).once.with { |h|
h[:params] == param
}
@collector.evaluate
@collector.evaluate
end
it "should not return resources that were collected in a previous run of this collector" do
one = stub_everything 'one', :type => "Mytype", :virtual? => true, :title => "test"
@compiler.stubs(:resources).returns([one])
@collector.evaluate
@collector.evaluate.should be_false
end
it "should tell the compiler about the overriden resources" do
one = stub_everything 'one', :type => "Mytype", :virtual? => true, :title => "test"
param = stub 'param'
one.expects(:virtual=).with(false)
@compiler.expects(:resources).returns([one])
@collector.add_override(:params => param )
Puppet::Parser::Resource.stubs(:new).returns("whatever")
@compiler.expects(:add_override).with("whatever")
@collector.evaluate
end
it "should not return or mark non-matching resources" do
@collector.vquery = proc { |res| res.name == :one }
one = stub_everything 'one', :name => :one, :type => "Mytype", :virtual? => true
two = stub_everything 'two', :name => :two, :type => "Mytype", :virtual? => true
one.expects(:virtual=).with(false)
two.expects(:virtual=).never
@compiler.expects(:resources).returns([one, two])
@collector.evaluate.should == [one]
end
end
describe Puppet::Parser::Collector, "when collecting exported resources" do
confine "Cannot test Rails integration without ActiveRecord" => Puppet.features.rails?
before do
@scope = stub 'scope', :host => "myhost", :debug => nil
@compiler = mock 'compile'
@scope.stubs(:compiler).returns(@compiler)
@resource_type = "Mytype"
@equery = "test = true"
@vquery = proc { |r| true }
Puppet.settings.stubs(:value).with(:storeconfigs).returns true
@collector = Puppet::Parser::Collector.new(@scope, @resource_type, @equery, @vquery, :exported)
end
# Stub most of our interface to Rails.
def stub_rails(everything = false)
ActiveRecord::Base.stubs(:connected?).returns(false)
Puppet::Rails.stubs(:init)
if everything
Puppet::Rails::Host.stubs(:find_by_name).returns(nil)
Puppet::Rails::Resource.stubs(:find).returns([])
end
end
it "should just return false if :storeconfigs is not enabled" do
Puppet.settings.expects(:value).with(:storeconfigs).returns false
@collector.evaluate.should be_false
end
it "should use initialize the Rails support if ActiveRecord is not connected" do
@compiler.stubs(:resources).returns([])
ActiveRecord::Base.expects(:connected?).returns(false)
Puppet::Rails.expects(:init)
Puppet::Rails::Host.stubs(:find_by_name).returns(nil)
Puppet::Rails::Resource.stubs(:find).returns([])
@collector.evaluate
end
it "should return all matching resources from the current compile and mark them non-virtual and non-exported" do
stub_rails(true)
one = stub 'one', :type => "Mytype", :virtual? => true, :exported? => true, :ref => "one"
two = stub 'two', :type => "Mytype", :virtual? => true, :exported? => true, :ref => "two"
one.stubs(:exported=)
one.stubs(:virtual=)
two.stubs(:exported=)
two.stubs(:virtual=)
@compiler.expects(:resources).returns([one, two])
@collector.evaluate.should == [one, two]
end
it "should mark all returned resources as not virtual" do
stub_rails(true)
one = stub 'one', :type => "Mytype", :virtual? => true, :exported? => true, :ref => "one"
one.stubs(:exported=)
one.expects(:virtual=).with(false)
@compiler.expects(:resources).returns([one])
@collector.evaluate.should == [one]
end
it "should convert all found resources into parser resources" do
stub_rails()
Puppet::Rails::Host.stubs(:find_by_name).returns(nil)
one = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true, :ref => "one"
Puppet::Rails::Resource.stubs(:find).returns([one])
resource = mock 'resource'
one.expects(:to_resource).with(@scope).returns(resource)
resource.stubs(:exported=)
resource.stubs(:virtual=)
resource.stubs(:ref)
@compiler.stubs(:resources).returns([])
@scope.stubs(:findresource).returns(nil)
@compiler.stubs(:add_resource)
@collector.evaluate.should == [resource]
end
it "should override all exported collected resources if collector has an override" do
stub_rails()
Puppet::Rails::Host.stubs(:find_by_name).returns(nil)
one = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true, :ref => "one"
Puppet::Rails::Resource.stubs(:find).returns([one])
resource = mock 'resource'
one.expects(:to_resource).with(@scope).returns(resource)
resource.stubs(:exported=)
resource.stubs(:virtual=)
resource.stubs(:ref)
resource.stubs(:title)
@compiler.stubs(:resources).returns([])
@scope.stubs(:findresource).returns(nil)
param = stub 'param'
@compiler.stubs(:add_override)
@compiler.stubs(:add_resource)
@collector.add_override(:params => param )
Puppet::Parser::Resource.expects(:new).once.with { |h|
h[:params] == param
}
@collector.evaluate
end
it "should store converted resources in the compile's resource list" do
stub_rails()
Puppet::Rails::Host.stubs(:find_by_name).returns(nil)
one = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true, :ref => "one"
Puppet::Rails::Resource.stubs(:find).returns([one])
resource = mock 'resource'
one.expects(:to_resource).with(@scope).returns(resource)
resource.stubs(:exported=)
resource.stubs(:virtual=)
resource.stubs(:ref)
@compiler.stubs(:resources).returns([])
@scope.stubs(:findresource).returns(nil)
@compiler.expects(:add_resource).with(@scope, resource)
@collector.evaluate.should == [resource]
end
# This way one host doesn't store another host's resources as exported.
it "should mark resources collected from the database as not exported" do
stub_rails()
Puppet::Rails::Host.stubs(:find_by_name).returns(nil)
one = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true, :ref => "one"
Puppet::Rails::Resource.stubs(:find).returns([one])
resource = mock 'resource'
one.expects(:to_resource).with(@scope).returns(resource)
resource.expects(:exported=).with(false)
resource.stubs(:virtual=)
resource.stubs(:ref)
@compiler.stubs(:resources).returns([])
@scope.stubs(:findresource).returns(nil)
@compiler.stubs(:add_resource)
@collector.evaluate
end
it "should fail if an equivalent resource already exists in the compile" do
stub_rails()
Puppet::Rails::Host.stubs(:find_by_name).returns(nil)
rails = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true, :id => 1, :ref => "yay"
inmemory = stub 'one', :type => "Mytype", :virtual? => true, :exported? => true, :rails_id => 2
Puppet::Rails::Resource.stubs(:find).returns([rails])
resource = mock 'resource'
@compiler.stubs(:resources).returns([])
@scope.stubs(:findresource).returns(inmemory)
@compiler.stubs(:add_resource)
proc { @collector.evaluate }.should raise_error(Puppet::ParseError)
end
it "should ignore exported resources that match already-collected resources" do
stub_rails()
Puppet::Rails::Host.stubs(:find_by_name).returns(nil)
rails = stub 'one', :restype => "Mytype", :title => "one", :virtual? => true, :exported? => true, :id => 1, :ref => "yay"
inmemory = stub 'one', :type => "Mytype", :virtual? => true, :exported? => true, :rails_id => 1
Puppet::Rails::Resource.stubs(:find).returns([rails])
resource = mock 'resource'
@compiler.stubs(:resources).returns([])
@scope.stubs(:findresource).returns(inmemory)
@compiler.stubs(:add_resource)
proc { @collector.evaluate }.should_not raise_error(Puppet::ParseError)
end
end
describe Puppet::Parser::Collector, "when building its ActiveRecord query for collecting exported resources" do
confine "Cannot test Rails integration without ActiveRecord" => Puppet.features.rails?
before do
@scope = stub 'scope', :host => "myhost", :debug => nil
@compiler = mock 'compile'
@scope.stubs(:compiler).returns(@compiler)
@resource_type = "Mytype"
@equery = nil
@vquery = proc { |r| true }
@resource = stub_everything 'collected'
@collector = Puppet::Parser::Collector.new(@scope, @resource_type, @equery, @vquery, :exported)
@collector.stubs(:exported_resource).with(@resource).returns(@resource)
@compiler.stubs(:resources).returns([])
ActiveRecord::Base.stubs(:connected?).returns(false)
Puppet::Rails.stubs(:init)
Puppet::Rails::Host.stubs(:find_by_name).returns(nil)
Puppet::Rails::Resource.stubs(:find).returns([])
Puppet.settings.stubs(:value).with(:storeconfigs).returns true
end
it "should exclude all resources from the host if ActiveRecord contains information for this host" do
@host = mock 'host'
@host.stubs(:id).returns 5
Puppet::Rails::Host.expects(:find_by_name).with(@scope.host).returns(@host)
Puppet::Rails::Resource.stubs(:find).with { |*arguments|
options = arguments[1]
options[:conditions][0] =~ /^host_id != \?/ and options[:conditions][1] == 5
}.returns([@resource])
@collector.evaluate.should == [@resource]
end
it "should join with parameter names, parameter values when querying ActiveRecord" do
@collector.equery = "param_names.name = title"
Puppet::Rails::Resource.stubs(:find).with { |*arguments|
options = arguments[1]
options[:joins] == {:param_values => :param_name}
}.returns([@resource])
@collector.evaluate.should == [@resource]
end
it "should join with tag tables when querying ActiveRecord with a tag exported query" do
@collector.equery = "puppet_tags.name = test"
Puppet::Rails::Resource.stubs(:find).with { |*arguments|
options = arguments[1]
options[:joins] == {:resource_tags => :puppet_tag}
}.returns([@resource])
@collector.evaluate.should == [@resource]
end
it "should not join parameters when querying ActiveRecord with a tag exported query" do
@collector.equery = "puppet_tags.name = test"
Puppet::Rails::Resource.stubs(:find).with { |*arguments|
options = arguments[1]
options[:joins] == {:param_values => :param_name}
}.returns([@resource])
@collector.evaluate.should be_false
end
it "should only search for exported resources with the matching type" do
Puppet::Rails::Resource.stubs(:find).with { |*arguments|
options = arguments[1]
options[:conditions][0].include?("(exported=? AND restype=?)") and options[:conditions][1] == true and options[:conditions][2] == "Mytype"
}.returns([@resource])
@collector.evaluate.should == [@resource]
end
it "should include the export query if one is provided" do
@collector.equery = "test = true"
Puppet::Rails::Resource.stubs(:find).with { |*arguments|
options = arguments[1]
options[:conditions][0].include?("test = true")
}.returns([@resource])
@collector.evaluate.should == [@resource]
end
end
diff --git a/spec/unit/parser/compiler.rb b/spec/unit/parser/compiler.rb
index 333046c77..6fd4d1fb5 100755
--- a/spec/unit/parser/compiler.rb
+++ b/spec/unit/parser/compiler.rb
@@ -1,605 +1,604 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
class CompilerTestResource
attr_accessor :builtin, :virtual, :evaluated, :type, :title
def initialize(type, title)
@type = type
@title = title
end
def ref
"%s[%s]" % [type.to_s.capitalize, title]
end
def evaluated?
@evaluated
end
def builtin?
@builtin
end
def virtual?
@virtual
end
def evaluate
end
end
describe Puppet::Parser::Compiler do
before :each do
@node = Puppet::Node.new "testnode"
@known_resource_types = Puppet::Resource::TypeCollection.new "development"
@scope_resource = stub 'scope_resource', :builtin? => true, :finish => nil, :ref => 'Class[main]', :type => "class"
@scope = stub 'scope', :resource => @scope_resource, :source => mock("source")
@compiler = Puppet::Parser::Compiler.new(@node)
@compiler.environment.stubs(:known_resource_types).returns @known_resource_types
end
it "should have a class method that compiles, converts, and returns a catalog" do
compiler = stub 'compiler'
Puppet::Parser::Compiler.expects(:new).with(@node).returns compiler
catalog = stub 'catalog'
compiler.expects(:compile).returns catalog
converted_catalog = stub 'converted_catalog'
catalog.expects(:to_resource).returns converted_catalog
Puppet::Parser::Compiler.compile(@node).should equal(converted_catalog)
end
it "should fail intelligently when a class-level compile fails" do
Puppet::Parser::Compiler.expects(:new).raises ArgumentError
lambda { Puppet::Parser::Compiler.compile(@node) }.should raise_error(Puppet::Error)
end
it "should use the node's environment as its environment" do
@compiler.environment.should equal(@node.environment)
end
it "should include the resource type collection helper" do
Puppet::Parser::Compiler.ancestors.should be_include(Puppet::Resource::TypeCollectionHelper)
end
it "should be able to return a class list containing all added classes" do
@compiler.add_class ""
@compiler.add_class "one"
@compiler.add_class "two"
@compiler.classlist.sort.should == %w{one two}.sort
end
describe "when initializing" do
it "should set its node attribute" do
@compiler.node.should equal(@node)
end
it "should detect when ast nodes are absent" do
@compiler.ast_nodes?.should be_false
end
it "should detect when ast nodes are present" do
@known_resource_types.expects(:nodes?).returns true
@compiler.ast_nodes?.should be_true
end
it "should copy the known_resource_types version to the catalog" do
@compiler.catalog.version.should == @known_resource_types.version
end
it "should copy any node classes into the class list" do
node = Puppet::Node.new("mynode")
node.classes = %w{foo bar}
compiler = Puppet::Parser::Compiler.new(node)
compiler.classlist.should include("foo")
compiler.classlist.should include("bar")
end
end
describe "when managing scopes" do
it "should create a top scope" do
@compiler.topscope.should be_instance_of(Puppet::Parser::Scope)
end
it "should be able to create new scopes" do
@compiler.newscope(@compiler.topscope).should be_instance_of(Puppet::Parser::Scope)
end
it "should correctly set the level of newly created scopes" do
@compiler.newscope(@compiler.topscope, :level => 5).level.should == 5
end
it "should set the parent scope of the new scope to be the passed-in parent" do
scope = mock 'scope'
newscope = @compiler.newscope(scope)
newscope.parent.should equal(scope)
end
it "should set the parent scope of the new scope to its topscope if the parent passed in is nil" do
scope = mock 'scope'
newscope = @compiler.newscope(nil)
newscope.parent.should equal(@compiler.topscope)
end
end
describe "when compiling" do
def compile_methods
[:set_node_parameters, :evaluate_main, :evaluate_ast_node, :evaluate_node_classes, :evaluate_generators, :fail_on_unevaluated,
:finish, :store, :extract]
end
# Stub all of the main compile methods except the ones we're specifically interested in.
def compile_stub(*except)
(compile_methods - except).each { |m| @compiler.stubs(m) }
end
it "should set node parameters as variables in the top scope" do
params = {"a" => "b", "c" => "d"}
@node.stubs(:parameters).returns(params)
compile_stub(:set_node_parameters)
@compiler.compile
@compiler.topscope.lookupvar("a").should == "b"
@compiler.topscope.lookupvar("c").should == "d"
end
it "should set the client and server versions on the catalog" do
params = {"clientversion" => "2", "serverversion" => "3"}
@node.stubs(:parameters).returns(params)
compile_stub(:set_node_parameters)
@compiler.compile
@compiler.catalog.client_version.should == "2"
@compiler.catalog.server_version.should == "3"
end
it "should evaluate any existing classes named in the node" do
classes = %w{one two three four}
main = stub 'main'
one = stub 'one', :name => "one"
three = stub 'three', :name => "three"
@node.stubs(:name).returns("whatever")
@node.stubs(:classes).returns(classes)
@compiler.expects(:evaluate_classes).with(classes, @compiler.topscope)
@compiler.class.publicize_methods(:evaluate_node_classes) { @compiler.evaluate_node_classes }
end
it "should evaluate the main class if it exists" do
compile_stub(:evaluate_main)
- main_class = mock 'main_class'
+ main_class = @known_resource_types.add Puppet::Resource::Type.new(:hostclass, "")
main_class.expects(:evaluate_code).with { |r| r.is_a?(Puppet::Parser::Resource) }
@compiler.topscope.expects(:source=).with(main_class)
- @known_resource_types.stubs(:find_hostclass).with("", "").returns(main_class)
@compiler.compile
end
it "should create a new, empty 'main' if no main class exists" do
compile_stub(:evaluate_main)
@compiler.compile
- @known_resource_types.find_hostclass("", "").should be_instance_of(Puppet::Resource::Type)
+ @known_resource_types.find_hostclass([""], "").should be_instance_of(Puppet::Resource::Type)
end
it "should evaluate any node classes" do
@node.stubs(:classes).returns(%w{one two three four})
@compiler.expects(:evaluate_classes).with(%w{one two three four}, @compiler.topscope)
@compiler.send(:evaluate_node_classes)
end
it "should evaluate all added collections" do
colls = []
# And when the collections fail to evaluate.
colls << mock("coll1-false")
colls << mock("coll2-false")
colls.each { |c| c.expects(:evaluate).returns(false) }
@compiler.add_collection(colls[0])
@compiler.add_collection(colls[1])
compile_stub(:evaluate_generators)
@compiler.compile
end
it "should ignore builtin resources" do
resource = stub 'builtin', :ref => "File[testing]", :builtin? => true, :type => "file"
@compiler.add_resource(@scope, resource)
resource.expects(:evaluate).never
@compiler.compile
end
it "should evaluate unevaluated resources" do
resource = CompilerTestResource.new(:file, "testing")
@compiler.add_resource(@scope, resource)
# We have to now mark the resource as evaluated
resource.expects(:evaluate).with { |*whatever| resource.evaluated = true }
@compiler.compile
end
it "should not evaluate already-evaluated resources" do
resource = stub 'already_evaluated', :ref => "File[testing]", :builtin? => false, :evaluated? => true, :virtual? => false, :type => "file"
@compiler.add_resource(@scope, resource)
resource.expects(:evaluate).never
@compiler.compile
end
it "should evaluate unevaluated resources created by evaluating other resources" do
resource = CompilerTestResource.new(:file, "testing")
@compiler.add_resource(@scope, resource)
resource2 = CompilerTestResource.new(:file, "other")
# We have to now mark the resource as evaluated
resource.expects(:evaluate).with { |*whatever| resource.evaluated = true; @compiler.add_resource(@scope, resource2) }
resource2.expects(:evaluate).with { |*whatever| resource2.evaluated = true }
@compiler.compile
end
it "should call finish() on all resources" do
# Add a resource that does respond to :finish
- resource = Puppet::Parser::Resource.new :scope => @scope, :type => "file", :title => "finish"
+ resource = Puppet::Parser::Resource.new "file", "finish", :scope => @scope
resource.expects(:finish)
@compiler.add_resource(@scope, resource)
# And one that does not
dnf = stub "dnf", :ref => "File[dnf]", :type => "file"
@compiler.add_resource(@scope, dnf)
@compiler.send(:finish)
end
it "should call finish() in add_resource order" do
resources = sequence('resources')
- resource1 = Puppet::Parser::Resource.new :scope => @scope, :type => "file", :title => "finish1"
+ resource1 = Puppet::Parser::Resource.new "file", "finish1", :scope => @scope
resource1.expects(:finish).in_sequence(resources)
@compiler.add_resource(@scope, resource1)
- resource2 = Puppet::Parser::Resource.new :scope => @scope, :type => "file", :title => "finish2"
+ resource2 = Puppet::Parser::Resource.new "file", "finish2", :scope => @scope
resource2.expects(:finish).in_sequence(resources)
@compiler.add_resource(@scope, resource2)
@compiler.send(:finish)
end
it "should return added resources in add order" do
resource1 = stub "1", :ref => "File[yay]", :type => "file"
@compiler.add_resource(@scope, resource1)
resource2 = stub "2", :ref => "File[youpi]", :type => "file"
@compiler.add_resource(@scope, resource2)
@compiler.resources.should == [resource1, resource2]
end
it "should add resources that do not conflict with existing resources" do
resource = CompilerTestResource.new(:file, "yay")
@compiler.add_resource(@scope, resource)
@compiler.catalog.should be_vertex(resource)
end
it "should fail to add resources that conflict with existing resources" do
file1 = Puppet::Type.type(:file).new :path => "/foo"
file2 = Puppet::Type.type(:file).new :path => "/foo"
@compiler.add_resource(@scope, file1)
lambda { @compiler.add_resource(@scope, file2) }.should raise_error(Puppet::Resource::Catalog::DuplicateResourceError)
end
it "should add an edge from the scope resource to the added resource" do
resource = stub "noconflict", :ref => "File[yay]", :type => "file"
@compiler.add_resource(@scope, resource)
@compiler.catalog.should be_edge(@scope.resource, resource)
end
it "should add edges from the class resources to the main class" do
main = CompilerTestResource.new(:class, :main)
@compiler.add_resource(@scope, main)
resource = CompilerTestResource.new(:class, "foo")
@compiler.add_resource(@scope, resource)
@compiler.catalog.should be_edge(main, resource)
end
it "should just add edges to the scope resource for the class resources when no main class can be found" do
resource = CompilerTestResource.new(:class, "foo")
@compiler.add_resource(@scope, resource)
@compiler.catalog.should be_edge(@scope.resource, resource)
end
it "should have a method for looking up resources" do
resource = stub 'resource', :ref => "Yay[foo]", :type => "file"
@compiler.add_resource(@scope, resource)
@compiler.findresource("Yay[foo]").should equal(resource)
end
it "should be able to look resources up by type and title" do
resource = stub 'resource', :ref => "Yay[foo]", :type => "file"
@compiler.add_resource(@scope, resource)
@compiler.findresource("Yay", "foo").should equal(resource)
end
it "should not evaluate virtual defined resources" do
resource = stub 'notevaluated', :ref => "File[testing]", :builtin? => false, :evaluated? => false, :virtual? => true, :type => "file"
@compiler.add_resource(@scope, resource)
resource.expects(:evaluate).never
@compiler.compile
end
end
describe "when evaluating collections" do
it "should evaluate each collection" do
2.times { |i|
coll = mock 'coll%s' % i
@compiler.add_collection(coll)
# This is the hard part -- we have to emulate the fact that
# collections delete themselves if they are done evaluating.
coll.expects(:evaluate).with do
@compiler.delete_collection(coll)
end
}
@compiler.class.publicize_methods(:evaluate_collections) { @compiler.evaluate_collections }
end
it "should not fail when there are unevaluated resource collections that do not refer to specific resources" do
coll = stub 'coll', :evaluate => false
coll.expects(:resources).returns(nil)
@compiler.add_collection(coll)
lambda { @compiler.compile }.should_not raise_error
end
it "should fail when there are unevaluated resource collections that refer to a specific resource" do
coll = stub 'coll', :evaluate => false
coll.expects(:resources).returns(:something)
@compiler.add_collection(coll)
lambda { @compiler.compile }.should raise_error(Puppet::ParseError)
end
it "should fail when there are unevaluated resource collections that refer to multiple specific resources" do
coll = stub 'coll', :evaluate => false
coll.expects(:resources).returns([:one, :two])
@compiler.add_collection(coll)
lambda { @compiler.compile }.should raise_error(Puppet::ParseError)
end
end
describe "when told to evaluate missing classes" do
it "should fail if there's no source listed for the scope" do
scope = stub 'scope', :source => nil
proc { @compiler.evaluate_classes(%w{one two}, scope) }.should raise_error(Puppet::DevError)
end
it "should tag the catalog with the name of each not-found class" do
@compiler.catalog.expects(:tag).with("notfound")
@scope.expects(:find_hostclass).with("notfound").returns(nil)
@compiler.evaluate_classes(%w{notfound}, @scope)
end
end
describe "when evaluating found classes" do
before do
@class = stub 'class', :name => "my::class"
@scope.stubs(:find_hostclass).with("myclass").returns(@class)
@resource = stub 'resource', :ref => "Class[myclass]", :type => "file"
end
it "should evaluate each class" do
@compiler.catalog.stubs(:tag)
@class.expects(:mk_plain_resource).with(@scope)
@scope.stubs(:class_scope).with(@class)
@compiler.evaluate_classes(%w{myclass}, @scope)
end
it "should not evaluate the resources created for found classes unless asked" do
@compiler.catalog.stubs(:tag)
@resource.expects(:evaluate).never
@class.expects(:mk_plain_resource).returns(@resource)
@scope.stubs(:class_scope).with(@class)
@compiler.evaluate_classes(%w{myclass}, @scope)
end
it "should immediately evaluate the resources created for found classes when asked" do
@compiler.catalog.stubs(:tag)
@resource.expects(:evaluate)
@class.expects(:mk_plain_resource).returns(@resource)
@scope.stubs(:class_scope).with(@class)
@compiler.evaluate_classes(%w{myclass}, @scope, false)
end
it "should skip classes that have already been evaluated" do
@compiler.catalog.stubs(:tag)
@scope.stubs(:class_scope).with(@class).returns("something")
@compiler.expects(:add_resource).never
@resource.expects(:evaluate).never
Puppet::Parser::Resource.expects(:new).never
@compiler.evaluate_classes(%w{myclass}, @scope, false)
end
it "should skip classes previously evaluated with different capitalization" do
@compiler.catalog.stubs(:tag)
@scope.stubs(:find_hostclass).with("MyClass").returns(@class)
@scope.stubs(:class_scope).with(@class).returns("something")
@compiler.expects(:add_resource).never
@resource.expects(:evaluate).never
Puppet::Parser::Resource.expects(:new).never
@compiler.evaluate_classes(%w{MyClass}, @scope, false)
end
it "should return the list of found classes" do
@compiler.catalog.stubs(:tag)
@compiler.stubs(:add_resource)
@scope.stubs(:find_hostclass).with("notfound").returns(nil)
@scope.stubs(:class_scope).with(@class)
Puppet::Parser::Resource.stubs(:new).returns(@resource)
@class.stubs :mk_plain_resource
@compiler.evaluate_classes(%w{myclass notfound}, @scope).should == %w{myclass}
end
end
describe "when evaluating AST nodes with no AST nodes present" do
it "should do nothing" do
@compiler.expects(:ast_nodes?).returns(false)
@compiler.known_resource_types.expects(:nodes).never
Puppet::Parser::Resource.expects(:new).never
@compiler.send(:evaluate_ast_node)
end
end
describe "when evaluating AST nodes with AST nodes present" do
before do
@compiler.known_resource_types.stubs(:nodes?).returns true
# Set some names for our test
@node.stubs(:names).returns(%w{a b c})
@compiler.known_resource_types.stubs(:node).with("a").returns(nil)
@compiler.known_resource_types.stubs(:node).with("b").returns(nil)
@compiler.known_resource_types.stubs(:node).with("c").returns(nil)
# It should check this last, of course.
@compiler.known_resource_types.stubs(:node).with("default").returns(nil)
end
it "should fail if the named node cannot be found" do
proc { @compiler.send(:evaluate_ast_node) }.should raise_error(Puppet::ParseError)
end
it "should evaluate the first node class matching the node name" do
node_class = stub 'node', :name => "c", :evaluate_code => nil
@compiler.known_resource_types.stubs(:node).with("c").returns(node_class)
node_resource = stub 'node resource', :ref => "Node[c]", :evaluate => nil, :type => "node"
node_class.expects(:mk_plain_resource).returns(node_resource)
@compiler.compile
end
it "should match the default node if no matching node can be found" do
node_class = stub 'node', :name => "default", :evaluate_code => nil
@compiler.known_resource_types.stubs(:node).with("default").returns(node_class)
node_resource = stub 'node resource', :ref => "Node[default]", :evaluate => nil, :type => "node"
node_class.expects(:mk_plain_resource).returns(node_resource)
@compiler.compile
end
it "should evaluate the node resource immediately rather than using lazy evaluation" do
node_class = stub 'node', :name => "c"
@compiler.known_resource_types.stubs(:node).with("c").returns(node_class)
node_resource = stub 'node resource', :ref => "Node[c]", :type => "node"
node_class.expects(:mk_plain_resource).returns(node_resource)
node_resource.expects(:evaluate)
@compiler.send(:evaluate_ast_node)
end
it "should set the node's scope as the top scope" do
node_resource = stub 'node resource', :ref => "Node[c]", :evaluate => nil, :type => "node"
node_class = stub 'node', :name => "c", :mk_plain_resource => node_resource
@compiler.known_resource_types.stubs(:node).with("c").returns(node_class)
# The #evaluate method normally does this.
scope = stub 'scope', :source => "mysource"
@compiler.topscope.expects(:class_scope).with(node_class).returns(scope)
node_resource.stubs(:evaluate)
@compiler.compile
@compiler.topscope.should equal(scope)
end
end
describe "when managing resource overrides" do
before do
@override = stub 'override', :ref => "My[ref]", :type => "my"
@resource = stub 'resource', :ref => "My[ref]", :builtin? => true, :type => "my"
end
it "should be able to store overrides" do
lambda { @compiler.add_override(@override) }.should_not raise_error
end
it "should apply overrides to the appropriate resources" do
@compiler.add_resource(@scope, @resource)
@resource.expects(:merge).with(@override)
@compiler.add_override(@override)
@compiler.compile
end
it "should accept overrides before the related resource has been created" do
@resource.expects(:merge).with(@override)
# First store the override
@compiler.add_override(@override)
# Then the resource
@compiler.add_resource(@scope, @resource)
# And compile, so they get resolved
@compiler.compile
end
it "should fail if the compile is finished and resource overrides have not been applied" do
@compiler.add_override(@override)
lambda { @compiler.compile }.should raise_error(Puppet::ParseError)
end
end
end
diff --git a/spec/unit/parser/functions/defined.rb b/spec/unit/parser/functions/defined.rb
new file mode 100755
index 000000000..0da8c4a31
--- /dev/null
+++ b/spec/unit/parser/functions/defined.rb
@@ -0,0 +1,50 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+describe "the 'defined' function" do
+
+ before :each do
+ @scope = Puppet::Parser::Scope.new()
+ @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("foo"))
+ @scope.compiler = @compiler
+ end
+
+ it "should exist" do
+ Puppet::Parser::Functions.function("defined").should == "function_defined"
+ end
+
+ it "should be true when the name is defined as a class" do
+ @scope.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "yayness")
+ @scope.function_defined("yayness").should be_true
+ end
+
+ it "should be true when the name is defined as a definition" do
+ @scope.known_resource_types.add Puppet::Resource::Type.new(:definition, "yayness")
+ @scope.function_defined("yayness").should be_true
+ end
+
+ it "should be true when the name is defined as a builtin type" do
+ @scope.function_defined("file").should be_true
+ end
+
+
+ it "should be true when any of the provided names are defined" do
+ @scope.known_resource_types.add Puppet::Resource::Type.new(:definition, "yayness")
+ @scope.function_defined(["meh", "yayness", "booness"]).should be_true
+ end
+
+ it "should be false when a single given name is not defined" do
+ @scope.function_defined("meh").should be_false
+ end
+
+ it "should be false when none of the names are defined" do
+ @scope.function_defined(["meh", "yayness", "booness"]).should be_false
+ end
+
+ it "should be true when a resource reference is provided and the resource is in the catalog" do
+ resource = Puppet::Resource.new("file", "/my/file")
+ @compiler.add_resource(@scope, resource)
+ @scope.function_defined(resource).should be_true
+ end
+end
diff --git a/spec/unit/parser/functions/require.rb b/spec/unit/parser/functions/require.rb
index 924990a5d..1d9ce931c 100755
--- a/spec/unit/parser/functions/require.rb
+++ b/spec/unit/parser/functions/require.rb
@@ -1,69 +1,69 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../../spec_helper'
describe "the require function" do
before :each do
@catalog = stub 'catalog'
@compiler = stub 'compiler', :catalog => @catalog
@resource = stub 'resource', :set_parameter => nil, :metaparam_compatibility_mode? => false, :[] => nil
@scope = Puppet::Parser::Scope.new()
@scope.stubs(:resource).returns @resource
@scope.stubs(:findresource)
@scope.stubs(:compiler).returns(@compiler)
@klass = stub 'class', :name => "myclass"
@scope.stubs(:find_hostclass).returns(@klass)
end
it "should exist" do
Puppet::Parser::Functions.function("require").should == "function_require"
end
it "should delegate to the 'include' puppet function" do
@scope.expects(:function_include).with("myclass")
@scope.function_require("myclass")
end
it "should set the 'require' prarameter on the resource to a resource reference" do
- @resource.expects(:set_parameter).with { |name, value| name == :require and value[0].is_a?(Puppet::Parser::Resource::Reference) }
+ @resource.expects(:set_parameter).with { |name, value| name == :require and value[0].is_a?(Puppet::Resource) }
@scope.stubs(:function_include)
@scope.function_require("myclass")
end
it "should verify the 'include' function is loaded" do
Puppet::Parser::Functions.expects(:function).with(:include).returns(:function_include)
@scope.stubs(:function_include)
@scope.function_require("myclass")
end
it "should include the class but not add a dependency if used on a client not at least version 0.25" do
@resource.expects(:metaparam_compatibility_mode?).returns true
@scope.expects(:warning)
@resource.expects(:set_parameter).never
@scope.expects(:function_include)
@scope.function_require("myclass")
end
it "should lookup the absolute class path" do
@scope.stubs(:function_include)
@scope.expects(:find_hostclass).with("myclass").returns(@klass)
@klass.expects(:name).returns("myclass")
@scope.function_require("myclass")
end
it "should append the required class to the require parameter" do
@scope.stubs(:function_include)
Puppet::Parser::Resource::Reference.stubs(:new).returns(:require2)
@resource.expects(:[]).with(:require).returns(:require1)
@resource.expects(:set_parameter).with(:require, [:require1, :require2])
@scope.function_require("myclass")
end
end
diff --git a/spec/unit/parser/functions/tag.rb b/spec/unit/parser/functions/tag.rb
new file mode 100755
index 000000000..5fb467e59
--- /dev/null
+++ b/spec/unit/parser/functions/tag.rb
@@ -0,0 +1,24 @@
+#! /usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+describe "the 'tag' function" do
+
+ before :each do
+ @scope = Puppet::Parser::Scope.new()
+ end
+
+ it "should exist" do
+ Puppet::Parser::Functions.function(:tag).should == "function_tag"
+ end
+
+ it "should tag the resource with any provided tags" do
+ resource = Puppet::Parser::Resource.new(:file, "/file", :scope => @scope)
+ @scope.expects(:resource).returns resource
+
+ @scope.function_tag ["one", "two"]
+
+ resource.should be_tagged("one")
+ resource.should be_tagged("two")
+ end
+end
diff --git a/spec/unit/parser/parser.rb b/spec/unit/parser/parser.rb
index 84749c3fb..8cc29c9b8 100755
--- a/spec/unit/parser/parser.rb
+++ b/spec/unit/parser/parser.rb
@@ -1,455 +1,471 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
describe Puppet::Parser do
ast = Puppet::Parser::AST
before :each do
@known_resource_types = Puppet::Resource::TypeCollection.new("development")
@parser = Puppet::Parser::Parser.new "development"
@parser.stubs(:known_resource_types).returns @known_resource_types
@true_ast = Puppet::Parser::AST::Boolean.new :value => true
end
it "should require an environment at initialization" do
lambda { Puppet::Parser::Parser.new }.should raise_error(ArgumentError)
end
it "should set the environment" do
env = Puppet::Node::Environment.new
Puppet::Parser::Parser.new(env).environment.should == env
end
it "should convert the environment into an environment instance if a string is provided" do
env = Puppet::Node::Environment.new("testing")
Puppet::Parser::Parser.new("testing").environment.should == env
end
it "should be able to look up the environment-specific resource type collection" do
rtc = Puppet::Node::Environment.new("development").known_resource_types
parser = Puppet::Parser::Parser.new "development"
parser.known_resource_types.should equal(rtc)
end
describe "when parsing files" do
before do
FileTest.stubs(:exist?).returns true
File.stubs(:open)
@parser.stubs(:watch_file)
end
it "should treat files ending in 'rb' as ruby files" do
@parser.expects(:parse_ruby_file)
@parser.file = "/my/file.rb"
@parser.parse
end
describe "in ruby" do
it "should use the ruby interpreter to load the file" do
@parser.file = "/my/file.rb"
@parser.expects(:require).with "/my/file.rb"
@parser.parse_ruby_file
end
end
end
describe "when parsing append operator" do
it "should not raise syntax errors" do
lambda { @parser.parse("$var += something") }.should_not raise_error
end
it "shouldraise syntax error on incomplete syntax " do
lambda { @parser.parse("$var += ") }.should raise_error
end
it "should call ast::VarDef with append=true" do
ast::VarDef.expects(:new).with { |h| h[:append] == true }
@parser.parse("$var += 2")
end
it "should work with arrays too" do
ast::VarDef.expects(:new).with { |h| h[:append] == true }
@parser.parse("$var += ['test']")
end
end
describe "when parsing 'if'" do
it "not, it should create the correct ast objects" do
ast::Not.expects(:new).with { |h| h[:value].is_a?(ast::Boolean) }
@parser.parse("if ! true { $var = 1 }")
end
it "boolean operation, it should create the correct ast objects" do
ast::BooleanOperator.expects(:new).with {
|h| h[:rval].is_a?(ast::Boolean) and h[:lval].is_a?(ast::Boolean) and h[:operator]=="or"
}
@parser.parse("if true or true { $var = 1 }")
end
it "comparison operation, it should create the correct ast objects" do
ast::ComparisonOperator.expects(:new).with {
|h| h[:lval].is_a?(ast::Name) and h[:rval].is_a?(ast::Name) and h[:operator]=="<"
}
@parser.parse("if 1 < 2 { $var = 1 }")
end
end
describe "when parsing if complex expressions" do
it "should create a correct ast tree" do
aststub = stub_everything 'ast'
ast::ComparisonOperator.expects(:new).with {
|h| h[:rval].is_a?(ast::Name) and h[:lval].is_a?(ast::Name) and h[:operator]==">"
}.returns(aststub)
ast::ComparisonOperator.expects(:new).with {
|h| h[:rval].is_a?(ast::Name) and h[:lval].is_a?(ast::Name) and h[:operator]=="=="
}.returns(aststub)
ast::BooleanOperator.expects(:new).with {
|h| h[:rval]==aststub and h[:lval]==aststub and h[:operator]=="and"
}
@parser.parse("if (1 > 2) and (1 == 2) { $var = 1 }")
end
it "should raise an error on incorrect expression" do
lambda { @parser.parse("if (1 > 2 > ) or (1 == 2) { $var = 1 }") }.should raise_error
end
end
describe "when parsing resource references" do
it "should not raise syntax errors" do
lambda { @parser.parse('exec { test: param => File["a"] }') }.should_not raise_error
end
it "should not raise syntax errors with multiple references" do
lambda { @parser.parse('exec { test: param => File["a","b"] }') }.should_not raise_error
end
it "should create an ast::ResourceReference" do
ast::Resource.stubs(:new)
ast::ResourceReference.expects(:new).with { |arg|
arg[:line]==1 and arg[:type]=="File" and arg[:title].is_a?(ast::ASTArray)
}
@parser.parse('exec { test: command => File["a","b"] }')
end
end
describe "when parsing resource overrides" do
it "should not raise syntax errors" do
lambda { @parser.parse('Resource["title"] { param => value }') }.should_not raise_error
end
it "should not raise syntax errors with multiple overrides" do
lambda { @parser.parse('Resource["title1","title2"] { param => value }') }.should_not raise_error
end
it "should create an ast::ResourceOverride" do
ast::ResourceOverride.expects(:new).with { |arg|
arg[:line]==1 and arg[:object].is_a?(ast::ResourceReference) and arg[:params].is_a?(ast::ResourceParam)
}
@parser.parse('Resource["title1","title2"] { param => value }')
end
end
describe "when parsing if statements" do
it "should not raise errors with empty if" do
lambda { @parser.parse("if true { }") }.should_not raise_error
end
it "should not raise errors with empty else" do
lambda { @parser.parse("if false { notice('if') } else { }") }.should_not raise_error
end
it "should not raise errors with empty if and else" do
lambda { @parser.parse("if false { } else { }") }.should_not raise_error
end
it "should create a nop node for empty branch" do
ast::Nop.expects(:new)
@parser.parse("if true { }")
end
it "should create a nop node for empty else branch" do
ast::Nop.expects(:new)
@parser.parse("if true { notice('test') } else { }")
end
end
describe "when parsing function calls" do
it "should not raise errors with no arguments" do
lambda { @parser.parse("tag()") }.should_not raise_error
end
it "should not raise errors with rvalue function with no args" do
lambda { @parser.parse("$a = template()") }.should_not raise_error
end
it "should not raise errors with arguments" do
lambda { @parser.parse("notice(1)") }.should_not raise_error
end
it "should not raise errors with multiple arguments" do
lambda { @parser.parse("notice(1,2)") }.should_not raise_error
end
it "should not raise errors with multiple arguments and a trailing comma" do
lambda { @parser.parse("notice(1,2,)") }.should_not raise_error
end
end
describe "when parsing arrays with trailing comma" do
it "should not raise errors with a trailing comma" do
lambda { @parser.parse("$a = [1,2,]") }.should_not raise_error
end
end
describe "when providing AST context" do
before do
@lexer = stub 'lexer', :line => 50, :file => "/foo/bar", :getcomment => "whev"
@parser.stubs(:lexer).returns @lexer
end
it "should include the lexer's line" do
@parser.ast_context[:line].should == 50
end
it "should include the lexer's file" do
@parser.ast_context[:file].should == "/foo/bar"
end
it "should include the docs if directed to do so" do
@parser.ast_context(true)[:doc].should == "whev"
end
it "should not include the docs when told not to" do
@parser.ast_context(false)[:doc].should be_nil
end
it "should not include the docs by default" do
@parser.ast_context()[:doc].should be_nil
end
end
describe "when building ast nodes" do
before do
@lexer = stub 'lexer', :line => 50, :file => "/foo/bar", :getcomment => "whev"
@parser.stubs(:lexer).returns @lexer
@class = stub 'class', :use_docs => false
end
it "should return a new instance of the provided class created with the provided options" do
@class.expects(:new).with { |opts| opts[:foo] == "bar" }
@parser.ast(@class, :foo => "bar")
end
it "should merge the ast context into the provided options" do
@class.expects(:new).with { |opts| opts[:file] == "/foo" }
@parser.expects(:ast_context).returns :file => "/foo"
@parser.ast(@class, :foo => "bar")
end
it "should prefer provided options over AST context" do
@class.expects(:new).with { |opts| opts[:file] == "/bar" }
@parser.expects(:ast_context).returns :file => "/foo"
@parser.ast(@class, :file => "/bar")
end
it "should include docs when the AST class uses them" do
@class.expects(:use_docs).returns true
@class.stubs(:new)
@parser.expects(:ast_context).with(true).returns({})
@parser.ast(@class, :file => "/bar")
end
end
describe "when creating a node" do
before :each do
@lexer = stub 'lexer'
@lexer.stubs(:getcomment)
@parser.stubs(:lexer).returns(@lexer)
@node = stub_everything 'node'
@parser.stubs(:ast_context).returns({})
@parser.stubs(:node).returns(nil)
@nodename = stub 'nodename', :is_a? => false, :value => "foo"
@nodename.stubs(:is_a?).with(Puppet::Parser::AST::HostName).returns(true)
end
it "should return an array of nodes" do
@parser.newnode(@nodename).should be_instance_of(Array)
end
end
describe "when retrieving a specific node" do
it "should delegate to the known_resource_types node" do
@known_resource_types.expects(:node).with("node")
@parser.node("node")
end
end
describe "when retrieving a specific class" do
it "should delegate to the loaded code" do
@known_resource_types.expects(:hostclass).with("class")
@parser.hostclass("class")
end
end
describe "when retrieving a specific definitions" do
it "should delegate to the loaded code" do
@known_resource_types.expects(:definition).with("define")
@parser.definition("define")
end
end
describe "when determining the configuration version" do
it "should determine it from the resource type collection" do
@parser.known_resource_types.expects(:version).returns "foo"
@parser.version.should == "foo"
end
end
describe "when looking up definitions" do
it "should check for them by name" do
@parser.stubs(:find_or_load).with("namespace","name",:definition).returns(:this_value)
@parser.find_definition("namespace","name").should == :this_value
end
end
describe "when looking up hostclasses" do
it "should check for them by name" do
@parser.stubs(:find_or_load).with("namespace","name",:hostclass).returns(:this_value)
@parser.find_hostclass("namespace","name").should == :this_value
end
end
describe "when looking up names" do
before :each do
@known_resource_types = mock 'loaded code'
@known_resource_types.stubs(:find_my_type).with('loaded_namespace', 'loaded_name').returns(true)
@known_resource_types.stubs(:find_my_type).with('bogus_namespace', 'bogus_name' ).returns(false)
@parser = Puppet::Parser::Parser.new "development"
@parser.stubs(:known_resource_types).returns @known_resource_types
end
describe "that are already loaded" do
it "should not try to load anything" do
@parser.expects(:load).never
@parser.find_or_load("loaded_namespace","loaded_name",:my_type)
end
it "should return true" do
@parser.find_or_load("loaded_namespace","loaded_name",:my_type).should == true
end
end
describe "that aren't already loaded" do
it "should first attempt to load them with the all lowercase fully qualified name" do
@known_resource_types.stubs(:find_my_type).with("foo_namespace","foo_name").returns(false,true,true)
@parser.expects(:load).with("foo_namespace::foo_name").returns(true).then.raises(Exception)
@parser.find_or_load("Foo_namespace","Foo_name",:my_type).should == true
end
it "should next attempt to load them with the all lowercase namespace" do
@known_resource_types.stubs(:find_my_type).with("foo_namespace","foo_name").returns(false,false,true,true)
@parser.expects(:load).with("foo_namespace::foo_name").returns(false).then.raises(Exception)
@parser.expects(:load).with("foo_namespace" ).returns(true ).then.raises(Exception)
@parser.find_or_load("Foo_namespace","Foo_name",:my_type).should == true
end
it "should finally attempt to load them with the all lowercase unqualified name" do
@known_resource_types.stubs(:find_my_type).with("foo_namespace","foo_name").returns(false,false,false,true,true)
@parser.expects(:load).with("foo_namespace::foo_name").returns(false).then.raises(Exception)
@parser.expects(:load).with("foo_namespace" ).returns(false).then.raises(Exception)
@parser.expects(:load).with( "foo_name").returns(true ).then.raises(Exception)
@parser.find_or_load("Foo_namespace","Foo_name",:my_type).should == true
end
it "should return false if the name isn't found" do
@parser.stubs(:load).returns(false)
@parser.find_or_load("Bogus_namespace","Bogus_name",:my_type).should == false
end
it "should directly look for fully qualified classes" do
@known_resource_types.stubs(:find_hostclass).with("foo_namespace","::foo_name").returns(false, true)
@parser.expects(:load).with("foo_name").returns true
@parser.find_or_load("foo_namespace","::foo_name",:hostclass)
end
end
end
describe "when loading classnames" do
before :each do
@known_resource_types = mock 'loaded code'
@parser = Puppet::Parser::Parser.new "development"
@parser.stubs(:known_resource_types).returns @known_resource_types
end
it "should just return false if the classname is empty" do
@parser.expects(:import).never
@parser.load("").should == false
end
it "should just return true if the item is loaded" do
pending "Need to access internal state (@parser's @loaded) to force this"
@parser.load("").should == false
end
end
describe "when parsing classes" do
before :each do
@krt = Puppet::Resource::TypeCollection.new("development")
@parser = Puppet::Parser::Parser.new "development"
@parser.stubs(:known_resource_types).returns @krt
end
it "should create new classes" do
@parser.parse("class foobar {}")
@krt.hostclass("foobar").should be_instance_of(Puppet::Resource::Type)
end
it "should correctly set the parent class when one is provided" do
@parser.parse("class foobar inherits yayness {}")
@krt.hostclass("foobar").parent.should == "yayness"
end
+ it "should correctly set the parent class for multiple classes at a time" do
+ @parser.parse("class foobar inherits yayness {}\nclass boo inherits bar {}")
+ @krt.hostclass("foobar").parent.should == "yayness"
+ @krt.hostclass("boo").parent.should == "bar"
+ end
+
it "should define the code when some is provided" do
@parser.parse("class foobar { $var = val }")
@krt.hostclass("foobar").code.should_not be_nil
end
it "should define parameters when provided" do
@parser.parse("class foobar($biz,$baz) {}")
@krt.hostclass("foobar").arguments.should == {"biz" => nil, "baz" => nil}
end
end
describe "when parsing resources" do
before :each do
@krt = Puppet::Resource::TypeCollection.new("development")
@parser = Puppet::Parser::Parser.new "development"
@parser.stubs(:known_resource_types).returns @krt
end
it "should be able to parse class resources" do
@krt.add(Puppet::Resource::Type.new(:hostclass, "foobar", :arguments => {"biz" => nil}))
lambda { @parser.parse("class { foobar: biz => stuff }") }.should_not raise_error
end
+
+ it "should correctly mark exported resources as exported" do
+ @parser.parse("@@file { '/file': }")
+ @krt.hostclass("").code[0].exported.should be_true
+ end
+
+ it "should correctly mark virtual resources as virtual" do
+ @parser.parse("@file { '/file': }")
+ @krt.hostclass("").code[0].virtual.should be_true
+ end
end
end
diff --git a/spec/unit/parser/resource.rb b/spec/unit/parser/resource.rb
index bb3001c8e..0c70c817e 100755
--- a/spec/unit/parser/resource.rb
+++ b/spec/unit/parser/resource.rb
@@ -1,534 +1,585 @@
#!/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
@node = Puppet::Node.new("yaynode")
@known_resource_types = Puppet::Resource::TypeCollection.new("env")
@compiler = Puppet::Parser::Compiler.new(@node)
@compiler.environment.stubs(:known_resource_types).returns @known_resource_types
@source = newclass ""
@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|
+ {: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)
elsif not args[:params].is_a? Array
args[:params] = paramify(args[:source], params)
end
- Puppet::Parser::Resource.new(args)
+ Puppet::Parser::Resource.new("resource", "testing", 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
def newclass(name)
@known_resource_types.add Puppet::Resource::Type.new(:hostclass, name)
end
def newdefine(name)
@known_resource_types.add Puppet::Resource::Type.new(:definition, name)
end
def newnode(name)
@known_resource_types.add Puppet::Resource::Type.new(:node, name)
end
it "should use the file lookup module" do
Puppet::Parser::Resource.ancestors.should be_include(Puppet::FileCollection::Lookup)
end
+ it "should get its environment from its scope" do
+ scope = stub 'scope', :source => stub("source")
+ scope.expects(:environment).returns "foo"
+ Puppet::Parser::Resource.new("file", "whatever", :scope => scope).environment.should == "foo"
+ end
+
+ it "should get its namespaces from its scope" do
+ scope = stub 'scope', :source => stub("source")
+ scope.expects(:namespaces).returns %w{one two}
+ Puppet::Parser::Resource.new("file", "whatever", :scope => scope).namespaces.should == %w{one two}
+ 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
+ @resource = Puppet::Parser::Resource.new("file", "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
+ @resource = Puppet::Parser::Resource.new("file", "whatever", :scope => @scope, :source => @source).isomorphic?.should be_false
end
it "should be isomorphic if it is not builtin" do
newdefine "whatever"
- @resource = Puppet::Parser::Resource.new(:type => "whatever", :title => "whatever", :scope => @scope, :source => @source).isomorphic?.should be_true
+ @resource = Puppet::Parser::Resource.new("whatever", "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 use a Puppet::Resource for converting to a ral resource" do
trans = mock 'resource', :to_ral => "yay"
@resource = mkresource
@resource.expects(:to_resource).returns trans
@resource.to_ral.should == "yay"
end
it "should be able to use the indexing operator to access parameters" do
- resource = Puppet::Parser::Resource.new(:type => "resource", :title => "testing", :source => "source", :scope => "scope")
+ resource = Puppet::Parser::Resource.new("resource", "testing", :source => "source", :scope => "scope")
resource["foo"] = "bar"
resource["foo"].should == "bar"
end
it "should return the title when asked for a parameter named 'title'" do
- Puppet::Parser::Resource.new(:type => "resource", :title => "testing", :source => "source", :scope => "scope")[:title].should == "testing"
+ Puppet::Parser::Resource.new("resource", "testing", :source => "source", :scope => "scope")[:title].should == "testing"
end
describe "when initializing" do
before do
- @arguments = {:type => "resource", :title => "testing", :scope => stub('scope', :source => mock('source'))}
+ @arguments = {: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
+ it "should fail unless #{name.to_s} is specified" do
+ lambda { Puppet::Parser::Resource.new('file', '/my/file') }.should raise_error(ArgumentError)
end
it "should set the reference correctly" do
- res = Puppet::Parser::Resource.new(@arguments)
+ res = Puppet::Parser::Resource.new("resource", "testing", @arguments)
res.ref.should == "Resource[testing]"
end
it "should be tagged with user tags" do
tags = [ "tag1", "tag2" ]
@arguments[:params] = [ param(:tag, tags , :source) ]
- res = Puppet::Parser::Resource.new(@arguments)
+ res = Puppet::Parser::Resource.new("resource", "testing", @arguments)
(res.tags & tags).should == tags
end
end
describe "when refering to a resource with name canonicalization" do
before do
- @arguments = {:type => "file", :title => "/path/", :scope => stub('scope', :source => mock('source'))}
+ @arguments = {:scope => stub('scope', :source => mock('source'))}
end
it "should canonicalize its own name" do
- res = Puppet::Parser::Resource.new(@arguments)
+ res = Puppet::Parser::Resource.new("file", "/path/", @arguments)
res.ref.should == "File[/path]"
end
end
describe "when evaluating" do
- before do
- @type = Puppet::Parser::Resource
-
- @definition = newdefine "mydefine"
- @class = newclass "myclass"
- @nodedef = newnode("mynode")
- 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)
+ definition = newdefine "mydefine"
+ res = Puppet::Parser::Resource.new("mydefine", "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 = newclass "myclass"
+ res = Puppet::Parser::Resource.new("class", "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)
+ nodedef = newnode("mynode")
+ res = Puppet::Parser::Resource.new("node", "mynode", :scope => @scope, :source => @source)
+ nodedef.expects(:evaluate_code).with(res)
res.evaluate
end
end
describe "when finishing" do
before do
@class = newclass "myclass"
@nodedef = newnode("mynode")
- @resource = Puppet::Parser::Resource.new(:type => "file", :title => "whatever", :scope => @scope, :source => @source)
+ @resource = Puppet::Parser::Resource.new("file", "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 be running in metaparam compatibility mode if running a version below 0.25" do
catalog = stub 'catalog', :client_version => "0.24.8"
@resource.stubs(:catalog).returns catalog
@resource.should be_metaparam_compatibility_mode
end
it "should be running in metaparam compatibility mode if running no client version is available" do
catalog = stub 'catalog', :client_version => nil
@resource.stubs(:catalog).returns catalog
@resource.should be_metaparam_compatibility_mode
end
it "should not be running in metaparam compatibility mode if running a version at or above 0.25" do
catalog = stub 'catalog', :client_version => "0.25.0"
@resource.stubs(:catalog).returns catalog
@resource.should_not be_metaparam_compatibility_mode
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 not copy relationship metaparams when not in metaparam compatibility mode" do
@scope.setvar("require", "bar")
@resource.stubs(:metaparam_compatibility_mode?).returns false
@resource.class.publicize_methods(:add_metaparams) { @resource.add_metaparams }
@resource["require"].should be_nil
end
it "should copy relationship metaparams when in metaparam compatibility mode" do
@scope.setvar("require", "bar")
@resource.stubs(:metaparam_compatibility_mode?).returns true
@resource.class.publicize_methods(:add_metaparams) { @resource.add_metaparams }
@resource["require"].should == "bar"
end
it "should stack relationship metaparams when in metaparam compatibility mode" do
@resource.set_parameter("require", "foo")
@scope.setvar("require", "bar")
@resource.stubs(:metaparam_compatibility_mode?).returns true
@resource.class.publicize_methods(:add_metaparams) { @resource.add_metaparams }
@resource["require"].should == ["foo", "bar"]
end
it "should copy all metaparams that it finds" do
@scope.setvar("noop", "foo")
@scope.setvar("schedule", "bar")
@resource.class.publicize_methods(:add_metaparams) { @resource.add_metaparams }
@resource["noop"].should == "foo"
@resource["schedule"].should == "bar"
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'))
+ @resource = Puppet::Parser::Resource.new("file", "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 = Puppet::Parser::Resource.new("file", "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 = Puppet::Parser::Resource.new("one::two", "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 = Puppet::Parser::Resource.new("file", "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
it "should promote tag overrides to real tags" do
@source.stubs(:child_of?).returns true
param = Puppet::Parser::Resource::Param.new(:name => :tag, :value => "testing", :source => @resource.source)
@override.set_parameter(param)
@resource.merge(@override)
@resource.tagged?("testing").should be_true
end
end
it "should be able to be converted to a normal resource" do
@source = stub 'scope', :name => "myscope"
@resource = mkresource :source => @source
@resource.should respond_to(:to_resource)
end
it "should use its resource converter to convert to a transportable resource" do
@source = stub 'scope', :name => "myscope"
@resource = mkresource :source => @source
newresource = Puppet::Resource.new(:file, "/my")
Puppet::Resource.expects(:new).returns(newresource)
newresource.expects(:to_trans).returns "mytrans"
@resource.to_trans.should == "mytrans"
end
it "should return nil if converted to a transportable resource and it is virtual" do
@source = stub 'scope', :name => "myscope"
@resource = mkresource :source => @source
@resource.expects(:virtual?).returns true
@resource.to_trans.should be_nil
end
describe "when being converted to a resource" do
before do
@source = stub 'scope', :name => "myscope"
@parser_resource = mkresource :source => @source, :params => {:foo => "bar", :fee => "fum"}
end
it "should create an instance of Puppet::Resource" do
@parser_resource.to_resource.should be_instance_of(Puppet::Resource)
end
it "should set the type correctly on the Puppet::Resource" do
@parser_resource.to_resource.type.should == @parser_resource.type
end
it "should set the title correctly on the Puppet::Resource" do
@parser_resource.to_resource.title.should == @parser_resource.title
end
it "should copy over all of the parameters" do
result = @parser_resource.to_resource.to_hash
# The name will be in here, also.
result[:foo].should == "bar"
result[:fee].should == "fum"
end
it "should copy over the tags" do
@parser_resource.tag "foo"
@parser_resource.tag "bar"
@parser_resource.to_resource.tags.should == @parser_resource.tags
end
it "should copy over the line" do
@parser_resource.line = 40
@parser_resource.to_resource.line.should == 40
end
it "should copy over the file" do
@parser_resource.file = "/my/file"
@parser_resource.to_resource.file.should == "/my/file"
end
it "should copy over the 'exported' value" do
@parser_resource.exported = true
@parser_resource.to_resource.exported.should be_true
end
it "should copy over the 'virtual' value" do
@parser_resource.virtual = true
@parser_resource.to_resource.virtual.should be_true
end
- it "should convert any parser resource references to Puppet::Resource::Reference instances" do
- ref = Puppet::Parser::Resource::Reference.new(:title => "/my/file", :type => "file")
+ it "should convert any parser resource references to Puppet::Resource instances" do
+ ref = Puppet::Resource.new("file", "/my/file")
@parser_resource = mkresource :source => @source, :params => {:foo => "bar", :fee => ref}
result = @parser_resource.to_resource
- result[:fee].should == Puppet::Resource::Reference.new(:file, "/my/file")
+ result[:fee].should == Puppet::Resource.new(:file, "/my/file")
end
- it "should convert any parser resource references to Puppet::Resource::Reference instances even if they are in an array" do
- ref = Puppet::Parser::Resource::Reference.new(:title => "/my/file", :type => "file")
+ it "should convert any parser resource references to Puppet::Resource instances even if they are in an array" do
+ ref = Puppet::Resource.new("file", "/my/file")
@parser_resource = mkresource :source => @source, :params => {:foo => "bar", :fee => ["a", ref]}
result = @parser_resource.to_resource
- result[:fee].should == ["a", Puppet::Resource::Reference.new(:file, "/my/file")]
+ result[:fee].should == ["a", Puppet::Resource.new(:file, "/my/file")]
end
- it "should convert any parser resource references to Puppet::Resource::Reference instances even if they are in an array of array, and even deeper" do
- ref1 = Puppet::Parser::Resource::Reference.new(:title => "/my/file1", :type => "file")
- ref2 = Puppet::Parser::Resource::Reference.new(:title => "/my/file2", :type => "file")
+ it "should convert any parser resource references to Puppet::Resource instances even if they are in an array of array, and even deeper" do
+ ref1 = Puppet::Resource.new("file", "/my/file1")
+ ref2 = Puppet::Resource.new("file", "/my/file2")
@parser_resource = mkresource :source => @source, :params => {:foo => "bar", :fee => ["a", [ref1,ref2]]}
result = @parser_resource.to_resource
- result[:fee].should == ["a", Puppet::Resource::Reference.new(:file, "/my/file1"), Puppet::Resource::Reference.new(:file, "/my/file2")]
+ result[:fee].should == ["a", Puppet::Resource.new(:file, "/my/file1"), Puppet::Resource.new(:file, "/my/file2")]
end
it "should fail if the same param is declared twice" do
lambda do
@parser_resource = mkresource :source => @source, :params => [
Puppet::Parser::Resource::Param.new(
:name => :foo, :value => "bar", :source => @source
),
Puppet::Parser::Resource::Param.new(
:name => :foo, :value => "baz", :source => @source
)
]
end.should raise_error(Puppet::ParseError)
end
end
+
+ describe "when validating" do
+ it "should check each parameter" do
+ resource = Puppet::Parser::Resource.new :foo, "bar", :scope => stub("scope"), :source => stub("source")
+ resource[:one] = :two
+ resource[:three] = :four
+ resource.expects(:validate_parameter).with(:one)
+ resource.expects(:validate_parameter).with(:three)
+ resource.send(:validate)
+ end
+
+ it "should raise a parse error when there's a failure" do
+ resource = Puppet::Parser::Resource.new :foo, "bar", :scope => stub("scope"), :source => stub("source")
+ resource[:one] = :two
+ resource.expects(:validate_parameter).with(:one).raises ArgumentError
+ lambda { resource.send(:validate) }.should raise_error(Puppet::ParseError)
+ end
+ end
+
+ describe "when setting parameters" do
+ before do
+ @source = newclass "foobar"
+ @resource = Puppet::Parser::Resource.new :foo, "bar", :scope => stub("scope"), :source => @source
+ end
+
+ it "should accept Param instances and add them to the parameter list" do
+ param = Puppet::Parser::Resource::Param.new :name => "foo", :value => "bar", :source => @source
+ @resource.set_parameter(param)
+ @resource["foo"].should == "bar"
+ end
+
+ it "should fail when provided a parameter name but no value" do
+ lambda { @resource.set_parameter("myparam") }.should raise_error(ArgumentError)
+ end
+
+ it "should use its source when provided a parameter name and value" do
+ @resource.set_parameter("myparam", "myvalue")
+ @resource["myparam"].should == "myvalue"
+ end
+ end
+
+ # part of #629 -- the undef keyword. Make sure 'undef' params get skipped.
+ it "should not include 'undef' parameters when converting itself to a hash" do
+ resource = Puppet::Parser::Resource.new "file", "/tmp/testing", :source => mock("source"), :scope => mock("scope")
+ resource[:owner] = :undef
+ resource[:mode] = "755"
+ resource.to_hash[:owner].should be_nil
+ end
end
diff --git a/spec/unit/parser/resource/reference.rb b/spec/unit/parser/resource/reference.rb
deleted file mode 100755
index a38604226..000000000
--- a/spec/unit/parser/resource/reference.rb
+++ /dev/null
@@ -1,134 +0,0 @@
-#!/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 get its environment from its scope" do
- env = stub 'environment'
- scope = stub 'scope', :environment => env
- @type.new(:title => "foo", :type => "bar", :scope => scope).environment.should equal(env)
- end
-
- it "should use the resource type collection helper to find its known resource types" do
- Puppet::Parser::Resource::Reference.ancestors.should include(Puppet::Resource::TypeCollectionHelper)
- 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 reference types" do
- ref = @type.new(:type => "foo::bar", :title => "/tmp/yay")
- ref.to_s.should == "Foo::Bar[/tmp/yay]"
- end
-
- it "should canonize resource reference values" do
- ref = @type.new(:type => "file", :title => "/tmp/yay/")
- ref.to_s.should == "File[/tmp/yay]"
- end
-
- it "should canonize resource reference values without order dependencies" do
- args = [[:title, "/tmp/yay/"], [:type, "file"]]
- ref = @type.new(args)
- ref.to_s.should == "File[/tmp/yay]"
- end
-
-end
-
-describe Puppet::Parser::Resource::Reference, " when modeling defined types" do
- def newclass(name)
- @known_resource_types.add Puppet::Resource::Type.new(:hostclass, name)
- end
-
- def newdefine(name)
- @known_resource_types.add Puppet::Resource::Type.new(:definition, name)
- end
-
- def newnode(name)
- @known_resource_types.add Puppet::Resource::Type.new(:node, name)
- end
-
- before do
- @type = Puppet::Parser::Resource::Reference
-
- @known_resource_types = Puppet::Resource::TypeCollection.new("myenv")
- @definition = newdefine("mydefine")
- @class = newclass("myclass")
- @nodedef = newnode("mynode")
- @node = Puppet::Node.new("yaynode")
-
- @compiler = Puppet::Parser::Compiler.new(@node)
- @compiler.environment.stubs(:known_resource_types).returns @known_resource_types
- 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 = newclass "top"
- sub = newclass "other::top"
-
- scope = @compiler.topscope.class.new(:parent => @compiler.topscope, :namespace => "other", :compiler => @compiler)
-
- ref = @type.new(:type => "class", :title => "top", :scope => scope)
- ref.definedtype.name.should equal(top.name)
- end
-
- it "should only look for fully qualified definitions" do
- top = newdefine "top"
- sub = newdefine "other::top"
-
- scope = @compiler.topscope.class.new(:parent => @compiler.topscope, :namespace => "other", :compiler => @compiler)
-
- ref = @type.new(:type => "top", :title => "foo", :scope => scope)
- ref.definedtype.name.should equal(top.name)
- end
-end
diff --git a/spec/unit/parser/scope.rb b/spec/unit/parser/scope.rb
index 799d05766..3d648fedf 100755
--- a/spec/unit/parser/scope.rb
+++ b/spec/unit/parser/scope.rb
@@ -1,360 +1,515 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
describe Puppet::Parser::Scope do
before :each do
@topscope = Puppet::Parser::Scope.new()
# This is necessary so we don't try to use the compiler to discover our parent.
@topscope.parent = nil
@scope = Puppet::Parser::Scope.new()
+ @scope.compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("foo"))
@scope.parent = @topscope
end
it "should be able to store references to class scopes" do
lambda { @scope.class_set "myname", "myscope" }.should_not raise_error
end
it "should be able to retrieve class scopes by name" do
@scope.class_set "myname", "myscope"
@scope.class_scope("myname").should == "myscope"
end
it "should be able to retrieve class scopes by object" do
klass = mock 'ast_class'
klass.expects(:name).returns("myname")
@scope.class_set "myname", "myscope"
@scope.class_scope(klass).should == "myscope"
end
# #620 - Nodes and classes should conflict, else classes don't get evaluated
describe "when evaluating nodes and classes with the same name (#620)" do
before do
@node = stub :nodescope? => true
@class = stub :nodescope? => false
end
it "should fail if a node already exists with the same name as the class being evaluated" do
@scope.class_set("one", @node)
lambda { @scope.class_set("one", @class) }.should raise_error(Puppet::ParseError)
end
it "should fail if a class already exists with the same name as the node being evaluated" do
@scope.class_set("one", @class)
lambda { @scope.class_set("one", @node) }.should raise_error(Puppet::ParseError)
end
end
it "should get its environment from its compiler" do
env = stub 'environment'
compiler = stub 'compiler', :environment => env
scope = Puppet::Parser::Scope.new :compiler => compiler
scope.environment.should equal(env)
end
it "should use the resource type collection helper to find its known resource types" do
Puppet::Parser::Scope.ancestors.should include(Puppet::Resource::TypeCollectionHelper)
end
describe "when looking up a variable" do
it "should default to an empty string" do
@scope.lookupvar("var").should == ""
end
it "should return an string when asked for a string" do
@scope.lookupvar("var", true).should == ""
end
it "should return ':undefined' for unset variables when asked not to return a string" do
@scope.lookupvar("var", false).should == :undefined
end
it "should be able to look up values" do
@scope.setvar("var", "yep")
@scope.lookupvar("var").should == "yep"
end
it "should be able to look up hashes" do
@scope.setvar("var", {"a" => "b"})
@scope.lookupvar("var").should == {"a" => "b"}
end
it "should be able to look up variables in parent scopes" do
@topscope.setvar("var", "parentval")
@scope.lookupvar("var").should == "parentval"
end
it "should prefer its own values to parent values" do
@topscope.setvar("var", "parentval")
@scope.setvar("var", "childval")
@scope.lookupvar("var").should == "childval"
end
describe "and the variable is qualified" do
before do
@compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("foonode"))
@scope.compiler = @compiler
@known_resource_types = @scope.known_resource_types
end
def newclass(name)
@known_resource_types.add Puppet::Resource::Type.new(:hostclass, name)
end
def create_class_scope(name)
klass = newclass(name)
- Puppet::Parser::Resource.new(:type => "class", :title => name, :scope => @scope, :source => mock('source')).evaluate
+ Puppet::Parser::Resource.new("class", name, :scope => @scope, :source => mock('source')).evaluate
return @scope.class_scope(klass)
end
it "should be able to look up explicitly fully qualified variables from main" do
other_scope = create_class_scope("")
other_scope.setvar("othervar", "otherval")
@scope.lookupvar("::othervar").should == "otherval"
end
it "should be able to look up explicitly fully qualified variables from other scopes" do
other_scope = create_class_scope("other")
other_scope.setvar("var", "otherval")
@scope.lookupvar("::other::var").should == "otherval"
end
it "should be able to look up deeply qualified variables" do
other_scope = create_class_scope("other::deep::klass")
other_scope.setvar("var", "otherval")
@scope.lookupvar("other::deep::klass::var").should == "otherval"
end
it "should return an empty string for qualified variables that cannot be found in other classes" do
other_scope = create_class_scope("other::deep::klass")
@scope.lookupvar("other::deep::klass::var").should == ""
end
it "should warn and return an empty string for qualified variables whose classes have not been evaluated" do
klass = newclass("other::deep::klass")
@scope.expects(:warning)
@scope.lookupvar("other::deep::klass::var").should == ""
end
it "should warn and return an empty string for qualified variables whose classes do not exist" do
@scope.expects(:warning)
@scope.lookupvar("other::deep::klass::var").should == ""
end
it "should return ':undefined' when asked for a non-string qualified variable from a class that does not exist" do
@scope.stubs(:warning)
@scope.lookupvar("other::deep::klass::var", false).should == :undefined
end
it "should return ':undefined' when asked for a non-string qualified variable from a class that has not been evaluated" do
@scope.stubs(:warning)
klass = newclass("other::deep::klass")
@scope.lookupvar("other::deep::klass::var", false).should == :undefined
end
end
end
describe "when setvar is called with append=true" do
it "should raise error if the variable is already defined in this scope" do
@scope.setvar("var","1", :append => false)
lambda { @scope.setvar("var","1", :append => true) }.should raise_error(Puppet::ParseError)
end
it "should lookup current variable value" do
@scope.expects(:lookupvar).with("var").returns("2")
@scope.setvar("var","1", :append => true)
end
it "should store the concatenated string '42'" do
@topscope.setvar("var","4", :append => false)
@scope.setvar("var","2", :append => true)
@scope.lookupvar("var").should == "42"
end
it "should store the concatenated array [4,2]" do
@topscope.setvar("var",[4], :append => false)
@scope.setvar("var",[2], :append => true)
@scope.lookupvar("var").should == [4,2]
end
it "should store the merged hash {a => b, c => d}" do
@topscope.setvar("var",{"a" => "b"}, :append => false)
@scope.setvar("var",{"c" => "d"}, :append => true)
@scope.lookupvar("var").should == {"a" => "b", "c" => "d"}
end
it "should raise an error when appending a hash with something other than another hash" do
@topscope.setvar("var",{"a" => "b"}, :append => false)
lambda { @scope.setvar("var","not a hash", :append => true) }.should raise_error
end
end
describe "when calling number?" do
it "should return nil if called with anything not a number" do
Puppet::Parser::Scope.number?([2]).should be_nil
end
it "should return a Fixnum for a Fixnum" do
Puppet::Parser::Scope.number?(2).should be_an_instance_of(Fixnum)
end
it "should return a Float for a Float" do
Puppet::Parser::Scope.number?(2.34).should be_an_instance_of(Float)
end
it "should return 234 for '234'" do
Puppet::Parser::Scope.number?("234").should == 234
end
it "should return nil for 'not a number'" do
Puppet::Parser::Scope.number?("not a number").should be_nil
end
it "should return 23.4 for '23.4'" do
Puppet::Parser::Scope.number?("23.4").should == 23.4
end
it "should return 23.4e13 for '23.4e13'" do
Puppet::Parser::Scope.number?("23.4e13").should == 23.4e13
end
it "should understand negative numbers" do
Puppet::Parser::Scope.number?("-234").should == -234
end
it "should know how to convert exponential float numbers ala '23e13'" do
Puppet::Parser::Scope.number?("23e13").should == 23e13
end
it "should understand hexadecimal numbers" do
Puppet::Parser::Scope.number?("0x234").should == 0x234
end
it "should understand octal numbers" do
Puppet::Parser::Scope.number?("0755").should == 0755
end
it "should return nil on malformed integers" do
Puppet::Parser::Scope.number?("0.24.5").should be_nil
end
it "should convert strings with leading 0 to integer if they are not octal" do
Puppet::Parser::Scope.number?("0788").should == 788
end
it "should convert strings of negative integers" do
Puppet::Parser::Scope.number?("-0788").should == -788
end
it "should return nil on malformed hexadecimal numbers" do
Puppet::Parser::Scope.number?("0x89g").should be_nil
end
end
describe "when using ephemeral variables" do
it "should store the variable value" do
@scope.setvar("1", :value, :ephemeral => true)
@scope.lookupvar("1").should == :value
end
it "should remove the variable value when unset_ephemeral_var is called" do
@scope.setvar("1", :value, :ephemeral => true)
@scope.stubs(:parent).returns(nil)
@scope.unset_ephemeral_var
@scope.lookupvar("1", false).should == :undefined
end
it "should not remove classic variables when unset_ephemeral_var is called" do
@scope.setvar("myvar", :value1)
@scope.setvar("1", :value2, :ephemeral => true)
@scope.stubs(:parent).returns(nil)
@scope.unset_ephemeral_var
@scope.lookupvar("myvar", false).should == :value1
end
end
describe "when interpolating string" do
(0..9).each do |n|
it "should allow $#{n} to match" do
@scope.setvar(n.to_s, "value", :ephemeral => true)
@scope.strinterp("$#{n}").should == "value"
end
end
(0..9).each do |n|
it "should not allow $#{n} to match if not ephemeral" do
@scope.setvar(n.to_s, "value", :ephemeral => false)
@scope.strinterp("$#{n}").should_not == "value"
end
end
it "should not allow $10 to match" do
@scope.setvar("10", "value", :ephemeral => true)
@scope.strinterp('==$10==').should_not == "==value=="
end
it "should not allow ${10} to match" do
@scope.setvar("10", "value", :ephemeral => true)
@scope.strinterp('==${10}==').should == "==value=="
end
+
+ describe "with qualified variables" do
+ before do
+ @scopes = {}
+ klass = @scope.known_resource_types.add(Puppet::Resource::Type.new(:hostclass, ""))
+ Puppet::Parser::Resource.new("class", :main, :scope => @scope, :source => mock('source')).evaluate
+ @scopes[""] = @scope.compiler.class_scope(klass)
+ @scopes[""].setvar("test", "value")
+
+ %w{one one::two one::two::three}.each do |name|
+ klass = @scope.known_resource_types.add(Puppet::Resource::Type.new(:hostclass, name))
+ Puppet::Parser::Resource.new("class", name, :scope => @scope, :source => mock('source')).evaluate
+ @scopes[name] = @scope.compiler.class_scope(klass)
+ @scopes[name].setvar("test", "value-#{name.sub(/.+::/,'')}")
+ end
+ end
+ {
+ "===${one::two::three::test}===" => "===value-three===",
+ "===$one::two::three::test===" => "===value-three===",
+ "===${one::two::test}===" => "===value-two===",
+ "===$one::two::test===" => "===value-two===",
+ "===${one::test}===" => "===value-one===",
+ "===$one::test===" => "===value-one===",
+ "===${::test}===" => "===value===",
+ "===$::test===" => "===value==="
+ }.each do |input, output|
+ it "should parse '#{input}' correctly" do
+ @scope.strinterp(input).should == output
+ end
+ end
+ end
+
+ tests = {
+ "===${test}===" => "===value===",
+ "===${test} ${test} ${test}===" => "===value value value===",
+ "===$test ${test} $test===" => "===value value value===",
+ "===\\$test===" => "===$test===",
+ '===\\$test string===' => "===$test string===",
+ '===$test string===' => "===value string===",
+ '===a testing $===' => "===a testing $===",
+ '===a testing \$===' => "===a testing $===",
+ "===an escaped \\\n carriage return===" => "===an escaped carriage return===",
+ '\$' => "$",
+ '\s' => "\s",
+ '\t' => "\t",
+ '\n' => "\n"
+ }
+
+ tests.each do |input, output|
+ it "should parse '#{input}' correctly" do
+ @scope.setvar("test", "value")
+ @scope.strinterp(input).should == output
+ end
+ end
+
+ # #523
+ %w{d f h l w z}.each do |l|
+ it "should parse '#{l}' when escaped" do
+ string = "\\" + l
+ @scope.strinterp(string).should == string
+ end
+ end
+ end
+
+ def test_strinterp
+ # Make and evaluate our classes so the qualified lookups work
+ parser = mkparser
+ klass = parser.newclass("")
+ scope = mkscope(:parser => parser)
+ Puppet::Parser::Resource.new(:type => "class", :title => :main, :scope => scope, :source => mock('source')).evaluate
+
+ assert_nothing_raised {
+ scope.setvar("test","value")
+ }
+
+ scopes = {"" => scope}
+
+ %w{one one::two one::two::three}.each do |name|
+ klass = parser.newclass(name)
+ Puppet::Parser::Resource.new(:type => "class", :title => name, :scope => scope, :source => mock('source')).evaluate
+ scopes[name] = scope.compiler.class_scope(klass)
+ scopes[name].setvar("test", "value-%s" % name.sub(/.+::/,''))
+ end
+
+ assert_equal("value", scope.lookupvar("::test"), "did not look up qualified value correctly")
+ tests.each do |input, output|
+ assert_nothing_raised("Failed to scan %s" % input.inspect) do
+ assert_equal(output, scope.strinterp(input),
+ 'did not parserret %s correctly' % input.inspect)
+ end
+ end
+
+ logs = []
+ Puppet::Util::Log.close
+ Puppet::Util::Log.newdestination(logs)
+
+ # #523
+ %w{d f h l w z}.each do |l|
+ string = "\\" + l
+ assert_nothing_raised do
+ assert_equal(string, scope.strinterp(string),
+ 'did not parserret %s correctly' % string)
+ end
+
+ assert(logs.detect { |m| m.message =~ /Unrecognised escape/ },
+ "Did not get warning about escape sequence with %s" % string)
+ logs.clear
+ end
end
describe "when setting ephemeral vars from matches" do
before :each do
@match = stub 'match', :is_a? => true
@match.stubs(:[]).with(0).returns("this is a string")
@match.stubs(:captures).returns([])
@scope.stubs(:setvar)
end
it "should accept only MatchData" do
lambda { @scope.ephemeral_from("match") }.should raise_error
end
it "should set $0 with the full match" do
@scope.expects(:setvar).with { |*arg| arg[0] == "0" and arg[1] == "this is a string" and arg[2][:ephemeral] }
@scope.ephemeral_from(@match)
end
it "should set every capture as ephemeral var" do
@match.stubs(:captures).returns([:capture1,:capture2])
@scope.expects(:setvar).with { |*arg| arg[0] == "1" and arg[1] == :capture1 and arg[2][:ephemeral] }
@scope.expects(:setvar).with { |*arg| arg[0] == "2" and arg[1] == :capture2 and arg[2][:ephemeral] }
@scope.ephemeral_from(@match)
end
end
describe "when unsetting variables" do
it "should be able to unset normal variables" do
@scope.setvar("foo", "bar")
@scope.unsetvar("foo")
@scope.lookupvar("foo").should == ""
end
it "should be able to unset ephemeral variables" do
@scope.setvar("foo", "bar", :ephemeral => true)
@scope.unsetvar("foo")
@scope.lookupvar("foo").should == ""
end
end
+
+ it "should use its namespaces to find hostclasses" do
+ klass = @scope.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "a::b::c")
+ @scope.add_namespace "a::b"
+ @scope.find_hostclass("c").should equal(klass)
+ end
+
+ it "should use its namespaces to find definitions" do
+ define = @scope.known_resource_types.add Puppet::Resource::Type.new(:definition, "a::b::c")
+ @scope.add_namespace "a::b"
+ @scope.find_definition("c").should equal(define)
+ end
+
+ describe "when managing defaults" do
+ it "should be able to set and lookup defaults" do
+ param = Puppet::Parser::Resource::Param.new(:name => :myparam, :value => "myvalue", :source => stub("source"))
+ @scope.setdefaults(:mytype, param)
+ @scope.lookupdefaults(:mytype).should == {:myparam => param}
+ end
+
+ it "should fail if a default is already defined and a new default is being defined" do
+ param = Puppet::Parser::Resource::Param.new(:name => :myparam, :value => "myvalue", :source => stub("source"))
+ @scope.setdefaults(:mytype, param)
+ lambda { @scope.setdefaults(:mytype, param) }.should raise_error(Puppet::ParseError)
+ end
+
+ it "should return multiple defaults at once" do
+ param1 = Puppet::Parser::Resource::Param.new(:name => :myparam, :value => "myvalue", :source => stub("source"))
+ @scope.setdefaults(:mytype, param1)
+ param2 = Puppet::Parser::Resource::Param.new(:name => :other, :value => "myvalue", :source => stub("source"))
+ @scope.setdefaults(:mytype, param2)
+
+ @scope.lookupdefaults(:mytype).should == {:myparam => param1, :other => param2}
+ end
+
+ it "should look up defaults defined in parent scopes" do
+ param1 = Puppet::Parser::Resource::Param.new(:name => :myparam, :value => "myvalue", :source => stub("source"))
+ @scope.setdefaults(:mytype, param1)
+
+ child_scope = @scope.newscope
+ param2 = Puppet::Parser::Resource::Param.new(:name => :other, :value => "myvalue", :source => stub("source"))
+ child_scope.setdefaults(:mytype, param2)
+
+ child_scope.lookupdefaults(:mytype).should == {:myparam => param1, :other => param2}
+ end
+ end
end
diff --git a/spec/unit/rails/param_value.rb b/spec/unit/rails/param_value.rb
index 26e87fec2..070ac82ea 100755
--- a/spec/unit/rails/param_value.rb
+++ b/spec/unit/rails/param_value.rb
@@ -1,49 +1,49 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
describe "Puppet::Rails::ParamValue" do
confine "Cannot test without ActiveRecord" => Puppet.features.rails?
def column(name, type)
ActiveRecord::ConnectionAdapters::Column.new(name, nil, type, false)
end
before do
require 'puppet/rails/param_value'
name = stub 'param_name', :name => "foo"
# Stub this so we don't need access to the DB.
Puppet::Rails::ParamValue.stubs(:columns).returns([column("value", "string")])
Puppet::Rails::ParamName.stubs(:find_or_create_by_name).returns(name)
end
describe "when creating initial parameter values" do
it "should return an array of hashes" do
Puppet::Rails::ParamValue.from_parser_param(:myparam, %w{a b})[0].should be_instance_of(Hash)
end
it "should return hashes for each value with the parameter name set as the ParamName instance" do
name = stub 'param_name', :name => "foo"
Puppet::Rails::ParamName.expects(:find_or_create_by_name).returns(name)
result = Puppet::Rails::ParamValue.from_parser_param(:myparam, "a")[0]
result[:value].should == "a"
result[:param_name].should == name
end
it "should return an array of hashes even when only one parameter is provided" do
Puppet::Rails::ParamValue.from_parser_param(:myparam, "a")[0].should be_instance_of(Hash)
end
it "should convert all arguments into strings" do
Puppet::Rails::ParamValue.from_parser_param(:myparam, 50)[0][:value].should == "50"
end
it "should not convert Resource References into strings" do
- ref = Puppet::Resource::Reference.new(:file, "/file")
+ ref = Puppet::Resource.new(:file, "/file")
Puppet::Rails::ParamValue.from_parser_param(:myparam, ref)[0][:value].should == ref
end
end
end
diff --git a/spec/unit/resource.rb b/spec/unit/resource.rb
index 73bbfd865..c97cd634f 100755
--- a/spec/unit/resource.rb
+++ b/spec/unit/resource.rb
@@ -1,621 +1,676 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../spec_helper'
require 'puppet/resource'
describe Puppet::Resource do
[:catalog, :file, :line].each do |attr|
it "should have an #{attr} attribute" do
resource = Puppet::Resource.new("file", "/my/file")
resource.should respond_to(attr)
resource.should respond_to(attr.to_s + "=")
end
end
- describe "when initializing" do
- it "should require the type and title" do
- lambda { Puppet::Resource.new }.should raise_error(ArgumentError)
- end
+ it "should have a :title attribute" do
+ Puppet::Resource.new(:file, "foo").title.should == "foo"
+ end
- it "should create a resource reference with its type and title" do
- ref = Puppet::Resource::Reference.new("file", "/f")
- Puppet::Resource::Reference.expects(:new).with("file", "/f").returns ref
- Puppet::Resource.new("file", "/f")
- end
+ it "should require the type and title" do
+ lambda { Puppet::Resource.new }.should raise_error(ArgumentError)
+ end
- it "should tag itself with its type" do
- Puppet::Resource.new("file", "/f").should be_tagged("file")
- end
+ it "should canonize types to capitalized strings" do
+ Puppet::Resource.new(:file, "foo").type.should == "File"
+ end
- it "should tag itself with its title if the title is a valid tag" do
- Puppet::Resource.new("file", "bar").should be_tagged("bar")
- end
+ it "should canonize qualified types so all strings are capitalized" do
+ Puppet::Resource.new("foo::bar", "foo").type.should == "Foo::Bar"
+ end
- it "should not tag itself with its title if the title is a not valid tag" do
- Puppet::Resource.new("file", "/bar").should_not be_tagged("/bar")
- end
+ it "should tag itself with its type" do
+ Puppet::Resource.new("file", "/f").should be_tagged("file")
+ end
- it "should allow setting of attributes" do
- Puppet::Resource.new("file", "/bar", :file => "/foo").file.should == "/foo"
- Puppet::Resource.new("file", "/bar", :exported => true).should be_exported
- end
+ it "should tag itself with its title if the title is a valid tag" do
+ Puppet::Resource.new("file", "bar").should be_tagged("bar")
end
- it "should use the resource reference to determine its type" do
- ref = Puppet::Resource::Reference.new("file", "/f")
- Puppet::Resource::Reference.expects(:new).returns ref
- resource = Puppet::Resource.new("file", "/f")
- ref.expects(:type).returns "mytype"
- resource.type.should == "mytype"
+ it "should not tag itself with its title if the title is a not valid tag" do
+ Puppet::Resource.new("file", "/bar").should_not be_tagged("/bar")
end
- it "should use its resource reference to determine its title" do
- ref = Puppet::Resource::Reference.new("file", "/f")
- Puppet::Resource::Reference.expects(:new).returns ref
- resource = Puppet::Resource.new("file", "/f")
- ref.expects(:title).returns "mytitle"
- resource.title.should == "mytitle"
+ it "should allow setting of attributes" do
+ Puppet::Resource.new("file", "/bar", :file => "/foo").file.should == "/foo"
+ Puppet::Resource.new("file", "/bar", :exported => true).should be_exported
end
- it "should use its resource reference to determine whether it is builtin" do
- ref = Puppet::Resource::Reference.new("file", "/f")
- Puppet::Resource::Reference.expects(:new).returns ref
- resource = Puppet::Resource.new("file", "/f")
- ref.expects(:builtin_type?).returns "yep"
- resource.builtin_type?.should == "yep"
+ it "should set its type to 'Class' and its title to the passed title if the passed type is :component and the title has no square brackets in it" do
+ ref = Puppet::Resource.new(:component, "foo")
+ ref.type.should == "Class"
+ ref.title.should == "foo"
end
- it "should call its builtin_type? method when 'builtin?' is called" do
- resource = Puppet::Resource.new("file", "/f")
- resource.expects(:builtin_type?).returns "foo"
- resource.builtin?.should == "foo"
+ it "should interpret the title as a reference and assign appropriately if the type is :component and the title contains square brackets" do
+ ref = Puppet::Resource.new(:component, "foo::bar[yay]")
+ ref.type.should == "Foo::Bar"
+ ref.title.should == "yay"
end
- it "should use its resource reference to produce its canonical reference string" do
- ref = Puppet::Resource::Reference.new("file", "/f")
- Puppet::Resource::Reference.expects(:new).returns ref
- resource = Puppet::Resource.new("file", "/f")
- ref.expects(:to_s).returns "Foo[bar]"
- resource.ref.should == "Foo[bar]"
+ it "should set the type to 'Class' if it is nil and the title contains no square brackets" do
+ ref = Puppet::Resource.new(nil, "yay")
+ ref.type.should == "Class"
+ ref.title.should == "yay"
+ end
+
+ it "should interpret the title as a reference and assign appropriately if the type is nil and the title contains square brackets" do
+ ref = Puppet::Resource.new(nil, "foo::bar[yay]")
+ ref.type.should == "Foo::Bar"
+ ref.title.should == "yay"
+ end
+
+ it "should interpret the title as a reference and assign appropriately if the type is nil and the title contains nested square brackets" do
+ ref = Puppet::Resource.new(nil, "foo::bar[baz[yay]]")
+ ref.type.should == "Foo::Bar"
+ ref.title.should =="baz[yay]"
+ end
+
+ it "should interpret the type as a reference and assign appropriately if the title is nil and the type contains square brackets" do
+ ref = Puppet::Resource.new("foo::bar[baz]")
+ ref.type.should == "Foo::Bar"
+ ref.title.should =="baz"
+ end
+
+ it "should be able to extract its information from a Puppet::Type instance" do
+ ral = Puppet::Type.type(:file).new :path => "/foo"
+ ref = Puppet::Resource.new(ral)
+ ref.type.should == "File"
+ ref.title.should == "/foo"
+ end
+
+
+ it "should fail if the title is nil and the type is not a valid resource reference string" do
+ lambda { Puppet::Resource.new("foo") }.should raise_error(ArgumentError)
+ end
+
+ it "should be able to produce a backward-compatible reference array" do
+ Puppet::Resource.new("foobar", "/f").to_trans_ref.should == %w{Foobar /f}
end
it "should be taggable" do
Puppet::Resource.ancestors.should be_include(Puppet::Util::Tagging)
end
it "should have an 'exported' attribute" do
resource = Puppet::Resource.new("file", "/f")
resource.exported = true
resource.exported.should == true
resource.should be_exported
end
it "should support an environment attribute" do
Puppet::Resource.new("file", "/my/file", :environment => :foo).environment.name.should == :foo
end
- it "should support a namespace attribute" do
- Puppet::Resource.new("file", "/my/file", :namespace => :foo).namespace.should == :foo
+ it "should support specifying namespaces" do
+ Puppet::Resource.new("file", "/my/file", :namespaces => [:foo]).namespaces.should == [:foo]
+ end
+
+ it "should convert namespaces to an array if not specified as one" do
+ Puppet::Resource.new("file", "/my/file", :namespaces => :foo).namespaces.should == [:foo]
end
- it "should default to a namespace of an empty string" do
- Puppet::Resource.new("file", "/my/file").namespace.should == ""
+ it "should default to a single amespace of an empty string" do
+ Puppet::Resource.new("file", "/my/file").namespaces.should == [""]
end
it "should be able to look up its resource type when the type is a builtin resource" do
Puppet::Resource.new("file", "/my/file").resource_type.should equal(Puppet::Type.type(:file))
end
it "should be able to look up its resource type via its environment when the type is a defined resource type" do
resource = Puppet::Resource.new("foobar", "/my/file")
type = Puppet::Resource::Type.new(:definition, "foobar")
resource.environment.known_resource_types.add type
resource.resource_type.should equal(type)
end
it "should be able to look up its resource type via its environment when the type is a node" do
resource = Puppet::Resource.new("node", "foobar")
node = Puppet::Resource::Type.new(:node, "foobar")
resource.environment.known_resource_types.add node
resource.resource_type.should equal(node)
end
it "should be able to look up its resource type via its environment when the type is a class" do
resource = Puppet::Resource.new("class", "foobar")
klass = Puppet::Resource::Type.new(:hostclass, "foobar")
resource.environment.known_resource_types.add klass
resource.resource_type.should equal(klass)
end
- it "should use its namespace when looking up defined resource types" do
- resource = Puppet::Resource.new("bar", "/my/file", :namespace => "foo")
+ it "should use its namespaces when looking up defined resource types" do
+ resource = Puppet::Resource.new("bar", "/my/file", :namespaces => ["foo"])
type = Puppet::Resource::Type.new(:definition, "foo::bar")
resource.environment.known_resource_types.add type
resource.resource_type.should equal(type)
end
- it "should use its namespace when looking up host classes" do
- resource = Puppet::Resource.new("class", "bar", :namespace => "foo")
+ it "should use its namespaces when looking up host classes" do
+ resource = Puppet::Resource.new("class", "bar", :namespaces => ["foo"])
type = Puppet::Resource::Type.new(:hostclass, "foo::bar")
resource.environment.known_resource_types.add type
resource.resource_type.should equal(type)
end
it "should return nil when looking up resource types that don't exist" do
Puppet::Resource.new("foobar", "bar").resource_type.should be_nil
end
it "should fail when an invalid parameter is used and parameter validation is enabled" do
type = Puppet::Resource::Type.new(:definition, "foobar")
Puppet::Node::Environment.new.known_resource_types.add type
resource = Puppet::Resource.new("foobar", "/my/file", :validate_parameters => true)
lambda { resource[:yay] = true }.should raise_error(ArgumentError)
end
it "should not fail when an invalid parameter is used and parameter validation is disabled" do
type = Puppet::Resource::Type.new(:definition, "foobar")
Puppet::Node::Environment.new.known_resource_types.add type
resource = Puppet::Resource.new("foobar", "/my/file")
resource[:yay] = true
end
it "should not fail when a valid parameter is used and parameter validation is enabled" do
type = Puppet::Resource::Type.new(:definition, "foobar", :arguments => {"yay" => nil})
Puppet::Node::Environment.new.known_resource_types.add type
resource = Puppet::Resource.new("foobar", "/my/file", :validate_parameters => true)
resource[:yay] = true
end
+ it "should be considered equivalent to another resource if their type and title match and no parameters are set" do
+ Puppet::Resource.new("file", "/f").should == Puppet::Resource.new("file", "/f")
+ end
+
+ it "should be considered equivalent to another resource if their type, title, and parameters are equal" do
+ Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"}).should == Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"})
+ end
+
+ it "should not be considered equivalent to another resource if their type and title match but parameters are different" do
+ Puppet::Resource.new("file", "/f", :parameters => {:fee => "baz"}).should_not == Puppet::Resource.new("file", "/f", :parameters => {:foo => "bar"})
+ end
+
+ it "should not be considered equivalent to a non-resource" do
+ Puppet::Resource.new("file", "/f").should_not == "foo"
+ end
+
+ it "should not be considered equivalent to another resource if their types do not match" do
+ Puppet::Resource.new("file", "/f").should_not == Puppet::Resource.new("exec", "/f")
+ end
+
+ it "should not be considered equivalent to another resource if their titles do not match" do
+ Puppet::Resource.new("file", "/foo").should_not == Puppet::Resource.new("file", "/f")
+ end
+
describe "when managing parameters" do
before do
@resource = Puppet::Resource.new("file", "/my/file")
end
it "should correctly detect when provided parameters are not valid for builtin types" do
Puppet::Resource.new("file", "/my/file").should_not be_valid_parameter("foobar")
end
it "should correctly detect when provided parameters are valid for builtin types" do
Puppet::Resource.new("file", "/my/file").should be_valid_parameter("mode")
end
it "should correctly detect when provided parameters are not valid for defined resource types" do
type = Puppet::Resource::Type.new(:definition, "foobar")
Puppet::Node::Environment.new.known_resource_types.add type
Puppet::Resource.new("foobar", "/my/file").should_not be_valid_parameter("myparam")
end
it "should correctly detect when provided parameters are valid for defined resource types" do
type = Puppet::Resource::Type.new(:definition, "foobar", :arguments => {"myparam" => nil})
Puppet::Node::Environment.new.known_resource_types.add type
Puppet::Resource.new("foobar", "/my/file").should be_valid_parameter("myparam")
end
it "should allow setting and retrieving of parameters" do
@resource[:foo] = "bar"
@resource[:foo].should == "bar"
end
it "should allow setting of parameters at initialization" do
Puppet::Resource.new("file", "/my/file", :parameters => {:foo => "bar"})[:foo].should == "bar"
end
it "should canonicalize retrieved parameter names to treat symbols and strings equivalently" do
@resource[:foo] = "bar"
@resource["foo"].should == "bar"
end
it "should canonicalize set parameter names to treat symbols and strings equivalently" do
@resource["foo"] = "bar"
@resource[:foo].should == "bar"
end
it "should set the namevar when asked to set the name" do
Puppet::Type.type(:file).stubs(:namevar).returns :myvar
@resource[:name] = "/foo"
@resource[:myvar].should == "/foo"
end
it "should return the namevar when asked to return the name" do
Puppet::Type.type(:file).stubs(:namevar).returns :myvar
@resource[:myvar] = "/foo"
@resource[:name].should == "/foo"
end
it "should be able to set the name for non-builtin types" do
resource = Puppet::Resource.new(:foo, "bar")
resource[:name] = "eh"
lambda { resource[:name] = "eh" }.should_not raise_error
end
it "should be able to return the name for non-builtin types" do
resource = Puppet::Resource.new(:foo, "bar")
resource[:name] = "eh"
resource[:name].should == "eh"
end
it "should be able to iterate over parameters" do
@resource[:foo] = "bar"
@resource[:fee] = "bare"
params = {}
@resource.each do |key, value|
params[key] = value
end
params.should == {:foo => "bar", :fee => "bare"}
end
it "should include Enumerable" do
@resource.class.ancestors.should be_include(Enumerable)
end
it "should have a method for testing whether a parameter is included" do
@resource[:foo] = "bar"
@resource.should be_has_key(:foo)
@resource.should_not be_has_key(:eh)
end
it "should have a method for providing the list of parameters" do
@resource[:foo] = "bar"
@resource[:bar] = "foo"
keys = @resource.keys
keys.should be_include(:foo)
keys.should be_include(:bar)
end
it "should have a method for providing the number of parameters" do
@resource[:foo] = "bar"
@resource.length.should == 1
end
it "should have a method for deleting parameters" do
@resource[:foo] = "bar"
@resource.delete(:foo)
@resource[:foo].should be_nil
end
it "should have a method for testing whether the parameter list is empty" do
@resource.should be_empty
@resource[:foo] = "bar"
@resource.should_not be_empty
end
it "should be able to produce a hash of all existing parameters" do
@resource[:foo] = "bar"
@resource[:fee] = "yay"
hash = @resource.to_hash
hash[:foo].should == "bar"
hash[:fee].should == "yay"
end
it "should not provide direct access to the internal parameters hash when producing a hash" do
hash = @resource.to_hash
hash[:foo] = "bar"
@resource[:foo].should be_nil
end
it "should use the title as the namevar to the hash if no namevar is present" do
Puppet::Type.type(:file).stubs(:namevar).returns :myvar
@resource.to_hash[:myvar].should == "/my/file"
end
it "should set :name to the title if :name is not present for non-builtin types" do
+ krt = Puppet::Resource::TypeCollection.new("myenv")
+ krt.add Puppet::Resource::Type.new(:definition, :foo)
resource = Puppet::Resource.new :foo, "bar"
+ resource.stubs(:known_resource_types).returns krt
resource.to_hash[:name].should == "bar"
end
end
describe "when serializing" do
before do
@resource = Puppet::Resource.new("file", "/my/file")
@resource["one"] = "test"
@resource["two"] = "other"
end
it "should be able to be dumped to yaml" do
proc { YAML.dump(@resource) }.should_not raise_error
end
it "should produce an equivalent yaml object" do
text = YAML.dump(@resource)
newresource = YAML.load(text)
newresource.title.should == @resource.title
newresource.type.should == @resource.type
%w{one two}.each do |param|
newresource[param].should == @resource[param]
end
end
end
describe "when converting to a RAL resource" do
- before do
- @resource = Puppet::Resource.new("file", "/my/file")
- @resource["one"] = "test"
- @resource["two"] = "other"
- end
-
- it "should use the resource type's :create method to create the resource if the resource is of a builtin type" do
- type = mock 'resource type'
- type.expects(:new).with(@resource).returns(:myresource)
- Puppet::Type.expects(:type).with(@resource.type).returns(type)
- @resource.to_ral.should == :myresource
+ it "should use the resource type's :new method to create the resource if the resource is of a builtin type" do
+ resource = Puppet::Resource.new("file", "/my/file")
+ result = resource.to_ral
+ result.should be_instance_of(Puppet::Type.type(:file))
+ result[:path].should == "/my/file"
end
it "should convert to a component instance if the resource type is not of a builtin type" do
- component = mock 'component type'
- Puppet::Type::Component.expects(:new).with(@resource).returns "meh"
+ resource = Puppet::Resource.new("foobar", "somename")
+ result = resource.to_ral
- Puppet::Type.expects(:type).with(@resource.type).returns(nil)
- @resource.to_ral.should == "meh"
+ result.should be_instance_of(Puppet::Type.type(:component))
+ result.title.should == "Foobar[somename]"
end
end
it "should be able to convert itself to Puppet code" do
Puppet::Resource.new("one::two", "/my/file").should respond_to(:to_manifest)
end
describe "when converting to puppet code" do
before do
@resource = Puppet::Resource.new("one::two", "/my/file", :parameters => {:noop => true, :foo => %w{one two}})
end
it "should print the type and title" do
@resource.to_manifest.should be_include("one::two { '/my/file':\n")
end
it "should print each parameter, with the value single-quoted" do
@resource.to_manifest.should be_include(" noop => 'true'")
end
it "should print array values appropriately" do
@resource.to_manifest.should be_include(" foo => ['one','two']")
end
end
it "should be able to convert itself to a TransObject instance" do
Puppet::Resource.new("one::two", "/my/file").should respond_to(:to_trans)
end
describe "when converting to a TransObject" do
describe "and the resource is not an instance of a builtin type" do
before do
@resource = Puppet::Resource.new("foo", "bar")
end
it "should return a simple TransBucket if it is not an instance of a builtin type" do
bucket = @resource.to_trans
bucket.should be_instance_of(Puppet::TransBucket)
bucket.type.should == @resource.type
bucket.name.should == @resource.title
end
it "should copy over the resource's file" do
@resource.file = "/foo/bar"
@resource.to_trans.file.should == "/foo/bar"
end
it "should copy over the resource's line" do
@resource.line = 50
@resource.to_trans.line.should == 50
end
end
describe "and the resource is an instance of a builtin type" do
before do
@resource = Puppet::Resource.new("file", "bar")
end
it "should return a TransObject if it is an instance of a builtin resource type" do
trans = @resource.to_trans
trans.should be_instance_of(Puppet::TransObject)
trans.type.should == "file"
trans.name.should == @resource.title
end
it "should copy over the resource's file" do
@resource.file = "/foo/bar"
@resource.to_trans.file.should == "/foo/bar"
end
it "should copy over the resource's line" do
@resource.line = 50
@resource.to_trans.line.should == 50
end
# Only TransObjects support tags, annoyingly
it "should copy over the resource's tags" do
@resource.tag "foo"
@resource.to_trans.tags.should == @resource.tags
end
it "should copy the resource's parameters into the transobject and convert the parameter name to a string" do
@resource[:foo] = "bar"
@resource.to_trans["foo"].should == "bar"
end
it "should be able to copy arrays of values" do
@resource[:foo] = %w{yay fee}
@resource.to_trans["foo"].should == %w{yay fee}
end
it "should reduce single-value arrays to just a value" do
@resource[:foo] = %w{yay}
@resource.to_trans["foo"].should == "yay"
end
it "should convert resource references into the backward-compatible form" do
- @resource[:foo] = Puppet::Resource::Reference.new(:file, "/f")
- @resource.to_trans["foo"].should == %w{file /f}
+ @resource[:foo] = Puppet::Resource.new(:file, "/f")
+ @resource.to_trans["foo"].should == %w{File /f}
end
it "should convert resource references into the backward-compatible form even when within arrays" do
- @resource[:foo] = ["a", Puppet::Resource::Reference.new(:file, "/f")]
- @resource.to_trans["foo"].should == ["a", %w{file /f}]
+ @resource[:foo] = ["a", Puppet::Resource.new(:file, "/f")]
+ @resource.to_trans["foo"].should == ["a", %w{File /f}]
end
end
end
describe "when converting to pson" do
confine "Missing 'pson' library" => Puppet.features.pson?
def pson_output_should
@resource.class.expects(:pson_create).with { |hash| yield hash }
end
it "should include the pson util module" do
Puppet::Resource.metaclass.ancestors.should be_include(Puppet::Util::Pson)
end
# LAK:NOTE For all of these tests, we convert back to the resource so we can
# trap the actual data structure then.
it "should set its type to the provided type" do
Puppet::Resource.from_pson(PSON.parse(Puppet::Resource.new("File", "/foo").to_pson)).type.should == "File"
end
it "should set its title to the provided title" do
Puppet::Resource.from_pson(PSON.parse(Puppet::Resource.new("File", "/foo").to_pson)).title.should == "/foo"
end
it "should include all tags from the resource" do
resource = Puppet::Resource.new("File", "/foo")
resource.tag("yay")
Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).tags.should == resource.tags
end
it "should include the file if one is set" do
resource = Puppet::Resource.new("File", "/foo")
resource.file = "/my/file"
Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).file.should == "/my/file"
end
it "should include the line if one is set" do
resource = Puppet::Resource.new("File", "/foo")
resource.line = 50
Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).line.should == 50
end
it "should include the 'exported' value if one is set" do
resource = Puppet::Resource.new("File", "/foo")
resource.exported = true
Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).exported.should be_true
end
it "should set 'exported' to false if no value is set" do
resource = Puppet::Resource.new("File", "/foo")
Puppet::Resource.from_pson(PSON.parse(resource.to_pson)).exported.should be_false
end
it "should set all of its parameters as the 'parameters' entry" do
resource = Puppet::Resource.new("File", "/foo")
resource[:foo] = %w{bar eh}
resource[:fee] = %w{baz}
result = Puppet::Resource.from_pson(PSON.parse(resource.to_pson))
result["foo"].should == %w{bar eh}
result["fee"].should == %w{baz}
end
end
describe "when converting from pson" do
confine "Missing 'pson' library" => Puppet.features.pson?
def pson_result_should
Puppet::Resource.expects(:new).with { |hash| yield hash }
end
before do
@data = {
'type' => "file",
'title' => "yay",
}
end
it "should set its type to the provided type" do
Puppet::Resource.from_pson(@data).type.should == "File"
end
it "should set its title to the provided title" do
Puppet::Resource.from_pson(@data).title.should == "yay"
end
it "should tag the resource with any provided tags" do
@data['tags'] = %w{foo bar}
resource = Puppet::Resource.from_pson(@data)
resource.tags.should be_include("foo")
resource.tags.should be_include("bar")
end
it "should set its file to the provided file" do
@data['file'] = "/foo/bar"
Puppet::Resource.from_pson(@data).file.should == "/foo/bar"
end
it "should set its line to the provided line" do
@data['line'] = 50
Puppet::Resource.from_pson(@data).line.should == 50
end
it "should 'exported' to true if set in the pson data" do
@data['exported'] = true
Puppet::Resource.from_pson(@data).exported.should be_true
end
it "should 'exported' to false if not set in the pson data" do
Puppet::Resource.from_pson(@data).exported.should be_false
end
it "should fail if no title is provided" do
@data.delete('title')
lambda { Puppet::Resource.from_pson(@data) }.should raise_error(ArgumentError)
end
it "should fail if no type is provided" do
@data.delete('type')
lambda { Puppet::Resource.from_pson(@data) }.should raise_error(ArgumentError)
end
it "should set each of the provided parameters" do
@data['parameters'] = {'foo' => %w{one two}, 'fee' => %w{three four}}
resource = Puppet::Resource.from_pson(@data)
resource['foo'].should == %w{one two}
resource['fee'].should == %w{three four}
end
it "should convert single-value array parameters to normal values" do
@data['parameters'] = {'foo' => %w{one}}
resource = Puppet::Resource.from_pson(@data)
resource['foo'].should == %w{one}
end
end
describe "it should implement to_resource" do
resource = Puppet::Resource.new("file", "/my/file")
resource.to_resource.should == resource
end
describe "because it is an indirector model" do
it "should include Puppet::Indirector" do
Puppet::Resource.should be_is_a(Puppet::Indirector)
end
it "should have a default terminus" do
Puppet::Resource.indirection.terminus_class.should == :ral
end
it "should have a name" do
Puppet::Resource.new("file", "/my/file").name.should == "File//my/file"
end
end
+
+ describe "when resolving resources with a catalog" do
+ it "should resolve all resources using the catalog" do
+ catalog = mock 'catalog'
+ resource = Puppet::Resource.new("foo::bar", "yay")
+ resource.catalog = catalog
+
+ catalog.expects(:resource).with("Foo::Bar[yay]").returns(:myresource)
+
+ resource.resolve.should == :myresource
+ end
+ end
end
diff --git a/spec/unit/resource/catalog.rb b/spec/unit/resource/catalog.rb
index e2fe72489..6c6af24ce 100755
--- a/spec/unit/resource/catalog.rb
+++ b/spec/unit/resource/catalog.rb
@@ -1,1063 +1,1063 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
describe Puppet::Resource::Catalog, "when compiling" do
it "should be an Expirer" do
Puppet::Resource::Catalog.ancestors.should be_include(Puppet::Util::Cacher::Expirer)
end
it "should always be expired if it's not applying" do
@catalog = Puppet::Resource::Catalog.new("host")
@catalog.expects(:applying?).returns false
@catalog.should be_dependent_data_expired(Time.now)
end
it "should not be expired if it's applying and the timestamp is late enough" do
@catalog = Puppet::Resource::Catalog.new("host")
@catalog.expire
@catalog.expects(:applying?).returns true
@catalog.should_not be_dependent_data_expired(Time.now)
end
it "should be able to write its list of classes to the class file" do
@catalog = Puppet::Resource::Catalog.new("host")
@catalog.add_class "foo", "bar"
Puppet.settings.expects(:value).with(:classfile).returns "/class/file"
fh = mock 'filehandle'
File.expects(:open).with("/class/file", "w").yields fh
fh.expects(:puts).with "foo\nbar"
@catalog.write_class_file
end
it "should have a client_version attribute" do
@catalog = Puppet::Resource::Catalog.new("host")
@catalog.client_version = 5
@catalog.client_version.should == 5
end
it "should have a server_version attribute" do
@catalog = Puppet::Resource::Catalog.new("host")
@catalog.server_version = 5
@catalog.server_version.should == 5
end
describe "when compiling" do
it "should accept tags" do
config = Puppet::Resource::Catalog.new("mynode")
config.tag("one")
config.tags.should == %w{one}
end
it "should accept multiple tags at once" do
config = Puppet::Resource::Catalog.new("mynode")
config.tag("one", "two")
config.tags.should == %w{one two}
end
it "should convert all tags to strings" do
config = Puppet::Resource::Catalog.new("mynode")
config.tag("one", :two)
config.tags.should == %w{one two}
end
it "should tag with both the qualified name and the split name" do
config = Puppet::Resource::Catalog.new("mynode")
config.tag("one::two")
config.tags.include?("one").should be_true
config.tags.include?("one::two").should be_true
end
it "should accept classes" do
config = Puppet::Resource::Catalog.new("mynode")
config.add_class("one")
config.classes.should == %w{one}
config.add_class("two", "three")
config.classes.should == %w{one two three}
end
it "should tag itself with passed class names" do
config = Puppet::Resource::Catalog.new("mynode")
config.add_class("one")
config.tags.should == %w{one}
end
end
describe "when extracting" do
it "should return extraction result as the method result" do
config = Puppet::Resource::Catalog.new("mynode")
config.expects(:extraction_format).returns(:whatever)
config.expects(:extract_to_whatever).returns(:result)
config.extract.should == :result
end
end
describe "when extracting transobjects" do
def mkscope
@node = Puppet::Node.new("mynode")
@compiler = Puppet::Parser::Compiler.new(@node)
# XXX This is ridiculous.
@compiler.send(:evaluate_main)
@scope = @compiler.topscope
end
def mkresource(type, name)
- Puppet::Parser::Resource.new(:type => type, :title => name, :source => @source, :scope => @scope)
+ Puppet::Parser::Resource.new(type, name, :source => @source, :scope => @scope)
end
it "should always create a TransBucket for the 'main' class" do
config = Puppet::Resource::Catalog.new("mynode")
@scope = mkscope
@source = mock 'source'
main = mkresource("class", :main)
config.add_vertex(main)
bucket = stub 'bucket', :file= => nil, :line= => nil, :classes= => nil
bucket.expects(:type=).with("Class")
bucket.expects(:name=).with(:main)
main.stubs(:builtin?).returns(false)
Puppet::TransBucket.expects(:new).returns bucket
config.extract_to_transportable.should equal(bucket)
end
# Now try it with a more complicated graph -- a three tier graph, each tier
it "should transform arbitrarily deep graphs into isomorphic trees" do
config = Puppet::Resource::Catalog.new("mynode")
@scope = mkscope
@scope.stubs(:tags).returns([])
@source = mock 'source'
# Create our scopes.
top = mkresource "class", :main
topbucket = []
topbucket.expects(:classes=).with([])
top.expects(:to_trans).returns(topbucket)
topres = mkresource "file", "/top"
topres.expects(:to_trans).returns(:topres)
config.add_edge top, topres
middle = mkresource "class", "middle"
middle.expects(:to_trans).returns([])
config.add_edge top, middle
midres = mkresource "file", "/mid"
midres.expects(:to_trans).returns(:midres)
config.add_edge middle, midres
bottom = mkresource "class", "bottom"
bottom.expects(:to_trans).returns([])
config.add_edge middle, bottom
botres = mkresource "file", "/bot"
botres.expects(:to_trans).returns(:botres)
config.add_edge bottom, botres
toparray = config.extract_to_transportable
# This is annoying; it should look like:
# [[[:botres], :midres], :topres]
# but we can't guarantee sort order.
toparray.include?(:topres).should be_true
midarray = toparray.find { |t| t.is_a?(Array) }
midarray.include?(:midres).should be_true
botarray = midarray.find { |t| t.is_a?(Array) }
botarray.include?(:botres).should be_true
end
end
describe " when converting to a Puppet::Resource catalog" do
before do
@original = Puppet::Resource::Catalog.new("mynode")
@original.tag(*%w{one two three})
@original.add_class *%w{four five six}
@top = Puppet::TransObject.new 'top', "class"
@topobject = Puppet::TransObject.new '/topobject', "file"
@middle = Puppet::TransObject.new 'middle', "class"
@middleobject = Puppet::TransObject.new '/middleobject', "file"
@bottom = Puppet::TransObject.new 'bottom', "class"
@bottomobject = Puppet::TransObject.new '/bottomobject', "file"
@resources = [@top, @topobject, @middle, @middleobject, @bottom, @bottomobject]
@original.add_resource(*@resources)
@original.add_edge(@top, @topobject)
@original.add_edge(@top, @middle)
@original.add_edge(@middle, @middleobject)
@original.add_edge(@middle, @bottom)
@original.add_edge(@bottom, @bottomobject)
@catalog = @original.to_resource
end
it "should copy over the version" do
@original.version = "foo"
@original.to_resource.version.should == "foo"
end
it "should add all resources as Puppet::Resource instances" do
@resources.each { |resource| @catalog.resource(resource.ref).should be_instance_of(Puppet::Resource) }
end
it "should copy the tag list to the new catalog" do
@catalog.tags.sort.should == @original.tags.sort
end
it "should copy the class list to the new catalog" do
@catalog.classes.should == @original.classes
end
it "should duplicate the original edges" do
@original.edges.each do |edge|
@catalog.edge?(@catalog.resource(edge.source.ref), @catalog.resource(edge.target.ref)).should be_true
end
end
it "should set itself as the catalog for each converted resource" do
@catalog.vertices.each { |v| v.catalog.object_id.should equal(@catalog.object_id) }
end
end
describe "when converting to a RAL catalog" do
before do
@original = Puppet::Resource::Catalog.new("mynode")
@original.tag(*%w{one two three})
@original.add_class *%w{four five six}
@top = Puppet::Resource.new :class, 'top'
@topobject = Puppet::Resource.new :file, '/topobject'
@middle = Puppet::Resource.new :class, 'middle'
@middleobject = Puppet::Resource.new :file, '/middleobject'
@bottom = Puppet::Resource.new :class, 'bottom'
@bottomobject = Puppet::Resource.new :file, '/bottomobject'
@resources = [@top, @topobject, @middle, @middleobject, @bottom, @bottomobject]
@original.add_resource(*@resources)
@original.add_edge(@top, @topobject)
@original.add_edge(@top, @middle)
@original.add_edge(@middle, @middleobject)
@original.add_edge(@middle, @bottom)
@original.add_edge(@bottom, @bottomobject)
@catalog = @original.to_ral
end
it "should add all resources as RAL instances" do
@resources.each { |resource| @catalog.resource(resource.ref).should be_instance_of(Puppet::Type) }
end
it "should copy the tag list to the new catalog" do
@catalog.tags.sort.should == @original.tags.sort
end
it "should copy the class list to the new catalog" do
@catalog.classes.should == @original.classes
end
it "should duplicate the original edges" do
@original.edges.each do |edge|
@catalog.edge?(@catalog.resource(edge.source.ref), @catalog.resource(edge.target.ref)).should be_true
end
end
it "should set itself as the catalog for each converted resource" do
@catalog.vertices.each { |v| v.catalog.object_id.should equal(@catalog.object_id) }
end
# This tests #931.
it "should not lose track of resources whose names vary" do
changer = Puppet::TransObject.new 'changer', 'test'
config = Puppet::Resource::Catalog.new('test')
config.add_resource(changer)
config.add_resource(@top)
config.add_edge(@top, changer)
resource = stub 'resource', :name => "changer2", :title => "changer2", :ref => "Test[changer2]", :catalog= => nil, :remove => nil
#changer is going to get duplicated as part of a fix for aliases 1094
changer.expects(:dup).returns(changer)
changer.expects(:to_ral).returns(resource)
newconfig = nil
proc { @catalog = config.to_ral }.should_not raise_error
@catalog.resource("Test[changer2]").should equal(resource)
end
after do
# Remove all resource instances.
@catalog.clear(true)
end
end
describe "when filtering" do
before :each do
@original = Puppet::Resource::Catalog.new("mynode")
@original.tag(*%w{one two three})
@original.add_class *%w{four five six}
@r1 = stub_everything 'r1', :ref => "File[/a]"
@r1.stubs(:respond_to?).with(:ref).returns(true)
@r1.stubs(:dup).returns(@r1)
@r1.stubs(:is_a?).returns(Puppet::Resource).returns(true)
@r2 = stub_everything 'r2', :ref => "File[/b]"
@r2.stubs(:respond_to?).with(:ref).returns(true)
@r2.stubs(:dup).returns(@r2)
@r2.stubs(:is_a?).returns(Puppet::Resource).returns(true)
@resources = [@r1,@r2]
@original.add_resource(@r1,@r2)
end
it "should transform the catalog to a resource catalog" do
@original.expects(:to_catalog).with { |h,b| h == :to_resource }
@original.filter
end
it "should scan each catalog resource in turn and apply filtering block" do
@resources.each { |r| r.expects(:test?) }
@original.filter do |r|
r.test?
end
end
it "should filter out resources which produce true when the filter block is evaluated" do
@original.filter do |r|
r == @r1
end.resource("File[/a]").should be_nil
end
it "should not consider edges against resources that were filtered out" do
@original.add_edge(@r1,@r2)
@original.filter do |r|
r == @r1
end.edge(@r1,@r2).should be_empty
end
end
describe "when functioning as a resource container" do
before do
@catalog = Puppet::Resource::Catalog.new("host")
@one = Puppet::Type.type(:notify).new :name => "one"
@two = Puppet::Type.type(:notify).new :name => "two"
@dupe = Puppet::Type.type(:notify).new :name => "one"
end
it "should provide a method to add one or more resources" do
@catalog.add_resource @one, @two
@catalog.resource(@one.ref).should equal(@one)
@catalog.resource(@two.ref).should equal(@two)
end
it "should add resources to the relationship graph if it exists" do
relgraph = @catalog.relationship_graph
@catalog.add_resource @one
relgraph.should be_vertex(@one)
end
it "should yield added resources if a block is provided" do
yielded = []
@catalog.add_resource(@one, @two) { |r| yielded << r }
yielded.length.should == 2
end
it "should set itself as the resource's catalog if it is not a relationship graph" do
@one.expects(:catalog=).with(@catalog)
@catalog.add_resource @one
end
it "should make all vertices available by resource reference" do
@catalog.add_resource(@one)
@catalog.resource(@one.ref).should equal(@one)
@catalog.vertices.find { |r| r.ref == @one.ref }.should equal(@one)
end
it "should canonize how resources are referred to during retrieval when both type and title are provided" do
@catalog.add_resource(@one)
@catalog.resource("notify", "one").should equal(@one)
end
it "should canonize how resources are referred to during retrieval when just the title is provided" do
@catalog.add_resource(@one)
@catalog.resource("notify[one]", nil).should equal(@one)
end
it "should not allow two resources with the same resource reference" do
@catalog.add_resource(@one)
proc { @catalog.add_resource(@dupe) }.should raise_error(Puppet::Resource::Catalog::DuplicateResourceError)
end
it "should not store objects that do not respond to :ref" do
proc { @catalog.add_resource("thing") }.should raise_error(ArgumentError)
end
it "should remove all resources when asked" do
@catalog.add_resource @one
@catalog.add_resource @two
@one.expects :remove
@two.expects :remove
@catalog.clear(true)
end
it "should support a mechanism for finishing resources" do
@one.expects :finish
@two.expects :finish
@catalog.add_resource @one
@catalog.add_resource @two
@catalog.finalize
end
it "should make default resources when finalizing" do
@catalog.expects(:make_default_resources)
@catalog.finalize
end
it "should add default resources to the catalog upon creation" do
@catalog.make_default_resources
@catalog.resource(:schedule, "daily").should_not be_nil
end
it "should optionally support an initialization block and should finalize after such blocks" do
@one.expects :finish
@two.expects :finish
config = Puppet::Resource::Catalog.new("host") do |conf|
conf.add_resource @one
conf.add_resource @two
end
end
it "should inform the resource that it is the resource's catalog" do
@one.expects(:catalog=).with(@catalog)
@catalog.add_resource @one
end
it "should be able to find resources by reference" do
@catalog.add_resource @one
@catalog.resource(@one.ref).should equal(@one)
end
it "should be able to find resources by reference or by type/title tuple" do
@catalog.add_resource @one
@catalog.resource("notify", "one").should equal(@one)
end
it "should have a mechanism for removing resources" do
@catalog.add_resource @one
@one.expects :remove
@catalog.remove_resource(@one)
@catalog.resource(@one.ref).should be_nil
@catalog.vertex?(@one).should be_false
end
it "should have a method for creating aliases for resources" do
@catalog.add_resource @one
@catalog.alias(@one, "other")
@catalog.resource("notify", "other").should equal(@one)
end
it "should ignore conflicting aliases that point to the aliased resource" do
@catalog.alias(@one, "other")
lambda { @catalog.alias(@one, "other") }.should_not raise_error
end
it "should create aliases for resources isomorphic resources whose names do not match their titles" do
resource = Puppet::Type::File.new(:title => "testing", :path => "/something")
@catalog.add_resource(resource)
@catalog.resource(:file, "/something").should equal(resource)
end
it "should not create aliases for resources non-isomorphic resources whose names do not match their titles" do
resource = Puppet::Type.type(:exec).new(:title => "testing", :command => "echo", :path => %w{/bin /usr/bin /usr/local/bin})
@catalog.add_resource(resource)
# Yay, I've already got a 'should' method
@catalog.resource(:exec, "echo").object_id.should == nil.object_id
end
# This test is the same as the previous, but the behaviour should be explicit.
it "should alias using the class name from the resource reference, not the resource class name" do
@catalog.add_resource @one
@catalog.alias(@one, "other")
@catalog.resource("notify", "other").should equal(@one)
end
it "should ignore conflicting aliases that point to the aliased resource" do
@catalog.alias(@one, "other")
lambda { @catalog.alias(@one, "other") }.should_not raise_error
end
it "should fail to add an alias if the aliased name already exists" do
@catalog.add_resource @one
proc { @catalog.alias @two, "one" }.should raise_error(ArgumentError)
end
it "should not fail when a resource has duplicate aliases created" do
@catalog.add_resource @one
proc { @catalog.alias @one, "one" }.should_not raise_error
end
it "should not create aliases that point back to the resource" do
@catalog.alias(@one, "one")
@catalog.resource(:notify, "one").should be_nil
end
it "should be able to look resources up by their aliases" do
@catalog.add_resource @one
@catalog.alias @one, "two"
@catalog.resource(:notify, "two").should equal(@one)
end
it "should remove resource aliases when the target resource is removed" do
@catalog.add_resource @one
@catalog.alias(@one, "other")
@one.expects :remove
@catalog.remove_resource(@one)
@catalog.resource("notify", "other").should be_nil
end
it "should add an alias for the namevar when the title and name differ on isomorphic resource types" do
resource = Puppet::Type.type(:file).new :path => "/something", :title => "other", :content => "blah"
resource.expects(:isomorphic?).returns(true)
@catalog.add_resource(resource)
@catalog.resource(:file, "other").should equal(resource)
@catalog.resource(:file, "/something").ref.should == resource.ref
end
it "should not add an alias for the namevar when the title and name differ on non-isomorphic resource types" do
resource = Puppet::Type.type(:file).new :path => "/something", :title => "other", :content => "blah"
resource.expects(:isomorphic?).returns(false)
@catalog.add_resource(resource)
@catalog.resource(:file, resource.title).should equal(resource)
# We can't use .should here, because the resources respond to that method.
if @catalog.resource(:file, resource.name)
raise "Aliased non-isomorphic resource"
end
end
it "should provide a method to create additional resources that also registers the resource" do
args = {:name => "/yay", :ensure => :file}
resource = stub 'file', :ref => "File[/yay]", :catalog= => @catalog, :title => "/yay", :[] => "/yay"
Puppet::Type.type(:file).expects(:new).with(args).returns(resource)
@catalog.create_resource :file, args
@catalog.resource("File[/yay]").should equal(resource)
end
end
describe "when applying" do
before :each do
@catalog = Puppet::Resource::Catalog.new("host")
@transaction = mock 'transaction'
Puppet::Transaction.stubs(:new).returns(@transaction)
@transaction.stubs(:evaluate)
@transaction.stubs(:cleanup)
@transaction.stubs(:add_times)
end
it "should create and evaluate a transaction" do
@transaction.expects(:evaluate)
@catalog.apply
end
it "should provide the catalog retrieval time to the transaction" do
@catalog.retrieval_duration = 5
@transaction.expects(:add_times).with(:config_retrieval => 5)
@catalog.apply
end
it "should use a retrieval time of 0 if none is set in the catalog" do
@catalog.retrieval_duration = nil
@transaction.expects(:add_times).with(:config_retrieval => 0)
@catalog.apply
end
it "should clean up the transaction" do
@transaction.expects :cleanup
@catalog.apply
end
it "should return the transaction" do
@catalog.apply.should equal(@transaction)
end
it "should yield the transaction if a block is provided" do
@catalog.apply do |trans|
trans.should equal(@transaction)
end
end
it "should default to not being a host catalog" do
@catalog.host_config.should be_nil
end
it "should pass supplied tags on to the transaction" do
@transaction.expects(:tags=).with(%w{one two})
@catalog.apply(:tags => %w{one two})
end
it "should set ignoreschedules on the transaction if specified in apply()" do
@transaction.expects(:ignoreschedules=).with(true)
@catalog.apply(:ignoreschedules => true)
end
it "should remove resources created mid-transaction" do
args = {:name => "/yay", :ensure => :file}
resource = stub 'file', :ref => "File[/yay]", :catalog= => @catalog, :title => "/yay", :[] => "/yay"
@transaction = mock 'transaction'
Puppet::Transaction.stubs(:new).returns(@transaction)
@transaction.stubs(:evaluate)
@transaction.stubs(:cleanup)
@transaction.stubs(:add_times)
Puppet::Type.type(:file).expects(:new).with(args).returns(resource)
resource.expects :remove
@catalog.apply do |trans|
@catalog.create_resource :file, args
@catalog.resource("File[/yay]").should equal(resource)
end
@catalog.resource("File[/yay]").should be_nil
end
it "should remove resources added mid-transaction" do
@transaction = mock 'transaction'
Puppet::Transaction.stubs(:new).returns(@transaction)
@transaction.stubs(:evaluate)
@transaction.stubs(:cleanup)
@transaction.stubs(:add_times)
file = Puppet::Type.type(:file).new(:name => "/yay", :ensure => :file)
@catalog.apply do |trans|
@catalog.add_resource file
@catalog.resource("File[/yay]").should_not be_nil
end
@catalog.resource("File[/yay]").should be_nil
end
it "should expire cached data in the resources both before and after the transaction" do
@catalog.expects(:expire).times(2)
@catalog.apply
end
describe "host catalogs" do
# super() doesn't work in the setup method for some reason
before do
@catalog.host_config = true
Puppet::Util::Storage.stubs(:store)
end
it "should initialize the state database before applying a catalog" do
Puppet::Util::Storage.expects(:load)
# Short-circuit the apply, so we know we're loading before the transaction
Puppet::Transaction.expects(:new).raises ArgumentError
proc { @catalog.apply }.should raise_error(ArgumentError)
end
it "should sync the state database after applying" do
Puppet::Util::Storage.expects(:store)
@transaction.stubs :any_failed? => false
@catalog.apply
end
after { Puppet.settings.clear }
end
describe "non-host catalogs" do
before do
@catalog.host_config = false
end
it "should never send reports" do
Puppet[:report] = true
Puppet[:summarize] = true
@catalog.apply
end
it "should never modify the state database" do
Puppet::Util::Storage.expects(:load).never
Puppet::Util::Storage.expects(:store).never
@catalog.apply
end
after { Puppet.settings.clear }
end
end
describe "when creating a relationship graph" do
before do
Puppet::Type.type(:component)
@catalog = Puppet::Resource::Catalog.new("host")
@compone = Puppet::Type::Component.new :name => "one"
@comptwo = Puppet::Type::Component.new :name => "two", :require => "Class[one]"
@file = Puppet::Type.type(:file)
@one = @file.new :path => "/one"
@two = @file.new :path => "/two"
@sub = @file.new :path => "/two/subdir"
@catalog.add_edge @compone, @one
@catalog.add_edge @comptwo, @two
@three = @file.new :path => "/three"
@four = @file.new :path => "/four", :require => "File[/three]"
@five = @file.new :path => "/five"
@catalog.add_resource @compone, @comptwo, @one, @two, @three, @four, @five, @sub
@relationships = @catalog.relationship_graph
end
it "should be able to create a relationship graph" do
@relationships.should be_instance_of(Puppet::SimpleGraph)
end
it "should not have any components" do
@relationships.vertices.find { |r| r.instance_of?(Puppet::Type::Component) }.should be_nil
end
it "should have all non-component resources from the catalog" do
# The failures print out too much info, so i just do a class comparison
@relationships.vertex?(@five).should be_true
end
it "should have all resource relationships set as edges" do
@relationships.edge?(@three, @four).should be_true
end
it "should copy component relationships to all contained resources" do
@relationships.edge?(@one, @two).should be_true
end
it "should add automatic relationships to the relationship graph" do
@relationships.edge?(@two, @sub).should be_true
end
it "should get removed when the catalog is cleaned up" do
@relationships.expects(:clear)
@catalog.clear
@catalog.instance_variable_get("@relationship_graph").should be_nil
end
it "should write :relationships and :expanded_relationships graph files if the catalog is a host catalog" do
@catalog.clear
graph = Puppet::SimpleGraph.new
Puppet::SimpleGraph.expects(:new).returns graph
graph.expects(:write_graph).with(:relationships)
graph.expects(:write_graph).with(:expanded_relationships)
@catalog.host_config = true
@catalog.relationship_graph
end
it "should not write graph files if the catalog is not a host catalog" do
@catalog.clear
graph = Puppet::SimpleGraph.new
Puppet::SimpleGraph.expects(:new).returns graph
graph.expects(:write_graph).never
@catalog.host_config = false
@catalog.relationship_graph
end
it "should create a new relationship graph after clearing the old one" do
@relationships.expects(:clear)
@catalog.clear
@catalog.relationship_graph.should be_instance_of(Puppet::SimpleGraph)
end
it "should remove removed resources from the relationship graph if it exists" do
@catalog.remove_resource(@one)
@catalog.relationship_graph.vertex?(@one).should be_false
end
end
describe "when writing dot files" do
before do
@catalog = Puppet::Resource::Catalog.new("host")
@name = :test
@file = File.join(Puppet[:graphdir], @name.to_s + ".dot")
end
it "should only write when it is a host catalog" do
File.expects(:open).with(@file).never
@catalog.host_config = false
Puppet[:graph] = true
@catalog.write_graph(@name)
end
after do
Puppet.settings.clear
end
end
describe "when indirecting" do
before do
@indirection = stub 'indirection', :name => :catalog
Puppet::Util::Cacher.expire
end
it "should redirect to the indirection for retrieval" do
Puppet::Resource::Catalog.stubs(:indirection).returns(@indirection)
@indirection.expects(:find)
Puppet::Resource::Catalog.find(:myconfig)
end
it "should default to the 'compiler' terminus" do
Puppet::Resource::Catalog.indirection.terminus_class.should == :compiler
end
after do
Puppet::Util::Cacher.expire
end
end
describe "when converting to yaml" do
before do
@catalog = Puppet::Resource::Catalog.new("me")
@catalog.add_edge("one", "two")
end
it "should be able to be dumped to yaml" do
YAML.dump(@catalog).should be_instance_of(String)
end
end
describe "when converting from yaml" do
before do
@catalog = Puppet::Resource::Catalog.new("me")
@catalog.add_edge("one", "two")
text = YAML.dump(@catalog)
@newcatalog = YAML.load(text)
end
it "should get converted back to a catalog" do
@newcatalog.should be_instance_of(Puppet::Resource::Catalog)
end
it "should have all vertices" do
@newcatalog.vertex?("one").should be_true
@newcatalog.vertex?("two").should be_true
end
it "should have all edges" do
@newcatalog.edge?("one", "two").should be_true
end
end
end
describe Puppet::Resource::Catalog, "when converting to pson" do
confine "Missing 'pson' library" => Puppet.features.pson?
before do
@catalog = Puppet::Resource::Catalog.new("myhost")
end
def pson_output_should
@catalog.class.expects(:pson_create).with { |hash| yield hash }.returns(:something)
end
# LAK:NOTE For all of these tests, we convert back to the resource so we can
# trap the actual data structure then.
it "should set its document_type to 'Catalog'" do
pson_output_should { |hash| hash['document_type'] == "Catalog" }
PSON.parse @catalog.to_pson
end
it "should set its data as a hash" do
pson_output_should { |hash| hash['data'].is_a?(Hash) }
PSON.parse @catalog.to_pson
end
[:name, :version, :tags, :classes].each do |param|
it "should set its #{param} to the #{param} of the resource" do
@catalog.send(param.to_s + "=", "testing") unless @catalog.send(param)
pson_output_should { |hash| hash['data'][param.to_s] == @catalog.send(param) }
PSON.parse @catalog.to_pson
end
end
it "should convert its resources to a PSON-encoded array and store it as the 'resources' data" do
one = stub 'one', :to_pson_data_hash => "one_resource", :ref => "Foo[one]"
two = stub 'two', :to_pson_data_hash => "two_resource", :ref => "Foo[two]"
@catalog.add_resource(one)
@catalog.add_resource(two)
# TODO this should really guarantee sort order
PSON.parse(@catalog.to_pson,:create_additions => false)['data']['resources'].sort.should == ["one_resource", "two_resource"].sort
end
it "should convert its edges to a PSON-encoded array and store it as the 'edges' data" do
one = stub 'one', :to_pson_data_hash => "one_resource", :ref => 'Foo[one]'
two = stub 'two', :to_pson_data_hash => "two_resource", :ref => 'Foo[two]'
three = stub 'three', :to_pson_data_hash => "three_resource", :ref => 'Foo[three]'
@catalog.add_edge(one, two)
@catalog.add_edge(two, three)
@catalog.edge(one, two ).expects(:to_pson_data_hash).returns "one_two_pson"
@catalog.edge(two, three).expects(:to_pson_data_hash).returns "two_three_pson"
PSON.parse(@catalog.to_pson,:create_additions => false)['data']['edges'].sort.should == %w{one_two_pson two_three_pson}.sort
end
end
describe Puppet::Resource::Catalog, "when converting from pson" do
confine "Missing 'pson' library" => Puppet.features.pson?
def pson_result_should
Puppet::Resource::Catalog.expects(:new).with { |hash| yield hash }
end
before do
@data = {
'name' => "myhost"
}
@pson = {
'document_type' => 'Puppet::Resource::Catalog',
'data' => @data,
'metadata' => {}
}
@catalog = Puppet::Resource::Catalog.new("myhost")
Puppet::Resource::Catalog.stubs(:new).returns @catalog
end
it "should be extended with the PSON utility module" do
Puppet::Resource::Catalog.metaclass.ancestors.should be_include(Puppet::Util::Pson)
end
it "should create it with the provided name" do
Puppet::Resource::Catalog.expects(:new).with('myhost').returns @catalog
PSON.parse @pson.to_pson
end
it "should set the provided version on the catalog if one is set" do
@data['version'] = 50
PSON.parse @pson.to_pson
@catalog.version.should == @data['version']
end
it "should set any provided tags on the catalog" do
@data['tags'] = %w{one two}
PSON.parse @pson.to_pson
@catalog.tags.should == @data['tags']
end
it "should set any provided classes on the catalog" do
@data['classes'] = %w{one two}
PSON.parse @pson.to_pson
@catalog.classes.should == @data['classes']
end
it 'should convert the resources list into resources and add each of them' do
@data['resources'] = [Puppet::Resource.new(:file, "/foo"), Puppet::Resource.new(:file, "/bar")]
@catalog.expects(:add_resource).times(2).with { |res| res.type == "File" }
PSON.parse @pson.to_pson
end
it 'should convert resources even if they do not include "type" information' do
@data['resources'] = [Puppet::Resource.new(:file, "/foo")]
@data['resources'][0].expects(:to_pson).returns '{"title":"/foo","tags":["file"],"type":"File"}'
@catalog.expects(:add_resource).with { |res| res.type == "File" }
PSON.parse @pson.to_pson
end
it 'should convert the edges list into edges and add each of them' do
one = Puppet::Relationship.new("osource", "otarget", :event => "one", :callback => "refresh")
two = Puppet::Relationship.new("tsource", "ttarget", :event => "two", :callback => "refresh")
@data['edges'] = [one, two]
@catalog.stubs(:resource).returns("eh")
@catalog.expects(:add_edge).with { |edge| edge.event == "one" }
@catalog.expects(:add_edge).with { |edge| edge.event == "two" }
PSON.parse @pson.to_pson
end
it "should be able to convert relationships that do not include 'type' information" do
one = Puppet::Relationship.new("osource", "otarget", :event => "one", :callback => "refresh")
one.expects(:to_pson).returns "{\"event\":\"one\",\"callback\":\"refresh\",\"source\":\"osource\",\"target\":\"otarget\"}"
@data['edges'] = [one]
@catalog.stubs(:resource).returns("eh")
@catalog.expects(:add_edge).with { |edge| edge.event == "one" }
PSON.parse @pson.to_pson
end
it "should set the source and target for each edge to the actual resource" do
edge = Puppet::Relationship.new("source", "target")
@data['edges'] = [edge]
@catalog.expects(:resource).with("source").returns("source_resource")
@catalog.expects(:resource).with("target").returns("target_resource")
@catalog.expects(:add_edge).with { |edge| edge.source == "source_resource" and edge.target == "target_resource" }
PSON.parse @pson.to_pson
end
it "should fail if the source resource cannot be found" do
edge = Puppet::Relationship.new("source", "target")
@data['edges'] = [edge]
@catalog.expects(:resource).with("source").returns(nil)
@catalog.stubs(:resource).with("target").returns("target_resource")
lambda { PSON.parse @pson.to_pson }.should raise_error(ArgumentError)
end
it "should fail if the target resource cannot be found" do
edge = Puppet::Relationship.new("source", "target")
@data['edges'] = [edge]
@catalog.stubs(:resource).with("source").returns("source_resource")
@catalog.expects(:resource).with("target").returns(nil)
lambda { PSON.parse @pson.to_pson }.should raise_error(ArgumentError)
end
end
diff --git a/spec/unit/resource/reference.rb b/spec/unit/resource/reference.rb
deleted file mode 100755
index 7a9704508..000000000
--- a/spec/unit/resource/reference.rb
+++ /dev/null
@@ -1,111 +0,0 @@
-#!/usr/bin/env ruby
-
-require File.dirname(__FILE__) + '/../../spec_helper'
-
-require 'puppet/resource/reference'
-
-describe Puppet::Resource::Reference do
- it "should have a :title attribute" do
- Puppet::Resource::Reference.new(:file, "foo").title.should == "foo"
- end
-
- it "should canonize types to capitalized strings" do
- Puppet::Resource::Reference.new(:file, "foo").type.should == "File"
- end
-
- it "should canonize qualified types so all strings are capitalized" do
- Puppet::Resource::Reference.new("foo::bar", "foo").type.should == "Foo::Bar"
- end
-
- it "should set its type to 'Class' and its title to the passed title if the passed type is :component and the title has no square brackets in it" do
- ref = Puppet::Resource::Reference.new(:component, "foo")
- ref.type.should == "Class"
- ref.title.should == "foo"
- end
-
- it "should interpret the title as a reference and assign appropriately if the type is :component and the title contains square brackets" do
- ref = Puppet::Resource::Reference.new(:component, "foo::bar[yay]")
- ref.type.should == "Foo::Bar"
- ref.title.should == "yay"
- end
-
- it "should set the type to 'Class' if it is nil and the title contains no square brackets" do
- ref = Puppet::Resource::Reference.new(nil, "yay")
- ref.type.should == "Class"
- ref.title.should == "yay"
- end
-
- it "should interpret the title as a reference and assign appropriately if the type is nil and the title contains square brackets" do
- ref = Puppet::Resource::Reference.new(nil, "foo::bar[yay]")
- ref.type.should == "Foo::Bar"
- ref.title.should == "yay"
- end
-
- it "should interpret the title as a reference and assign appropriately if the type is nil and the title contains nested square brackets" do
- ref = Puppet::Resource::Reference.new(nil, "foo::bar[baz[yay]]")
- ref.type.should == "Foo::Bar"
- ref.title.should =="baz[yay]"
- end
-
- it "should interpret the type as a reference and assign appropriately if the title is nil and the type contains square brackets" do
- ref = Puppet::Resource::Reference.new("foo::bar[baz]")
- ref.type.should == "Foo::Bar"
- ref.title.should =="baz"
- end
-
- it "should be able to extract its information from a Puppet::Type instance" do
- ral = Puppet::Type.type(:file).new :path => "/foo"
- ref = Puppet::Resource::Reference.new(ral)
- ref.type.should == "File"
- ref.title.should == "/foo"
- end
-
-
- it "should fail if the title is nil and the type is not a valid resource reference string" do
- lambda { Puppet::Resource::Reference.new("foo") }.should raise_error(ArgumentError)
- end
-
- it "should be considered builtin if an existing resource type matches the type" do
- Puppet::Resource::Reference.new("file", "/f").should be_builtin_type
- end
-
- it "should be not considered builtin if an existing resource type does not match the type" do
- Puppet::Resource::Reference.new("foobar", "/f").should_not be_builtin_type
- end
-
- it "should be able to produce a backward-compatible reference array" do
- Puppet::Resource::Reference.new("foobar", "/f").to_trans_ref.should == %w{Foobar /f}
- end
-
- it "should downcase resource types when producing a backward-compatible reference array for builtin resource types" do
- Puppet::Resource::Reference.new("file", "/f").to_trans_ref.should == %w{file /f}
- end
-
- it "should be considered equivalent to another reference if their type and title match" do
- Puppet::Resource::Reference.new("file", "/f").should == Puppet::Resource::Reference.new("file", "/f")
- end
-
- it "should not be considered equivalent to a non-reference" do
- Puppet::Resource::Reference.new("file", "/f").should_not == "foo"
- end
-
- it "should not be considered equivalent to another reference if their types do not match" do
- Puppet::Resource::Reference.new("file", "/f").should_not == Puppet::Resource::Reference.new("exec", "/f")
- end
-
- it "should not be considered equivalent to another reference if their titles do not match" do
- Puppet::Resource::Reference.new("file", "/foo").should_not == Puppet::Resource::Reference.new("file", "/f")
- end
-
- describe "when resolving resources with a catalog" do
- it "should resolve all resources using the catalog" do
- config = mock 'catalog'
- ref = Puppet::Resource::Reference.new("foo::bar", "yay")
- ref.catalog = config
-
- config.expects(:resource).with("Foo::Bar[yay]").returns(:myresource)
-
- ref.resolve.should == :myresource
- end
- end
-end
diff --git a/spec/unit/resource/type.rb b/spec/unit/resource/type.rb
index 0a2f447ef..1a19cf4f0 100755
--- a/spec/unit/resource/type.rb
+++ b/spec/unit/resource/type.rb
@@ -1,533 +1,533 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/resource/type'
describe Puppet::Resource::Type do
it "should have a 'name' attribute" do
Puppet::Resource::Type.new(:hostclass, "foo").name.should == "foo"
end
[:code, :doc, :line, :file, :code_collection].each do |attr|
it "should have a '#{attr}' attribute" do
type = Puppet::Resource::Type.new(:hostclass, "foo")
type.send(attr.to_s + "=", "yay")
type.send(attr).should == "yay"
end
end
describe "when a node" do
it "should allow a regex as its name" do
lambda { Puppet::Resource::Type.new(:node, /foo/) }.should_not raise_error
end
it "should allow a AST::HostName instance as its name" do
regex = Puppet::Parser::AST::Regex.new(:value => /foo/)
name = Puppet::Parser::AST::HostName.new(:value => regex)
lambda { Puppet::Resource::Type.new(:node, name) }.should_not raise_error
end
it "should match against the regexp in the AST::HostName when a HostName instance is provided" do
regex = Puppet::Parser::AST::Regex.new(:value => /\w/)
name = Puppet::Parser::AST::HostName.new(:value => regex)
node = Puppet::Resource::Type.new(:node, name)
node.match("foo").should be_true
end
it "should return the value of the hostname if provided a string-form AST::HostName instance as the name" do
name = Puppet::Parser::AST::HostName.new(:value => "foo")
node = Puppet::Resource::Type.new(:node, name)
node.name.should == "foo"
end
describe "and the name is a regex" do
it "should have a method that indicates that this is the case" do
Puppet::Resource::Type.new(:node, /w/).should be_name_is_regex
end
it "should set its namespace to ''" do
Puppet::Resource::Type.new(:node, /w/).namespace.should == ""
end
it "should return the regex converted to a string when asked for its name" do
Puppet::Resource::Type.new(:node, /ww/).name.should == "ww"
end
it "should downcase the regex when returning the name as a string" do
Puppet::Resource::Type.new(:node, /W/).name.should == "w"
end
it "should remove non-alpha characters when returning the name as a string" do
Puppet::Resource::Type.new(:node, /w*w/).name.should_not include("*")
end
it "should remove leading dots when returning the name as a string" do
Puppet::Resource::Type.new(:node, /.ww/).name.should_not =~ /^\./
end
it "should have a method for matching its regex name against a provided name" do
Puppet::Resource::Type.new(:node, /.ww/).should respond_to(:match)
end
it "should return true when its regex matches the provided name" do
Puppet::Resource::Type.new(:node, /\w/).match("foo").should be_true
end
it "should return false when its regex does not match the provided name" do
(!!Puppet::Resource::Type.new(:node, /\d/).match("foo")).should be_false
end
it "should return true when its name, as a string, is matched against an equal string" do
Puppet::Resource::Type.new(:node, "foo").match("foo").should be_true
end
it "should return false when its name is matched against an unequal string" do
Puppet::Resource::Type.new(:node, "foo").match("bar").should be_false
end
it "should match names insensitive to case" do
Puppet::Resource::Type.new(:node, "fOo").match("foO").should be_true
end
end
it "should return the name converted to a string when the name is not a regex" do
pending "Need to define ResourceTypeCollection behaviour first"
name = Puppet::Parser::AST::HostName.new(:value => "foo")
Puppet::Resource::Type.new(:node, name).name.should == "foo"
end
it "should return the name converted to a string when the name is a regex" do
pending "Need to define ResourceTypeCollection behaviour first"
name = Puppet::Parser::AST::HostName.new(:value => /regex/)
Puppet::Resource::Type.new(:node, name).name.should == /regex/.to_s
end
it "should mark any created scopes as a node scope" do
pending "Need to define ResourceTypeCollection behaviour first"
name = Puppet::Parser::AST::HostName.new(:value => /regex/)
Puppet::Resource::Type.new(:node, name).name.should == /regex/.to_s
end
end
describe "when initializing" do
it "should require a resource super type" do
Puppet::Resource::Type.new(:hostclass, "foo").type.should == :hostclass
end
it "should fail if provided an invalid resource super type" do
lambda { Puppet::Resource::Type.new(:nope, "foo") }.should raise_error(ArgumentError)
end
it "should set its name to the downcased, stringified provided name" do
Puppet::Resource::Type.new(:hostclass, "Foo::Bar".intern).name.should == "foo::bar"
end
it "should set its namespace to the downcased, stringified qualified portion of the name" do
Puppet::Resource::Type.new(:hostclass, "Foo::Bar::Baz".intern).namespace.should == "foo::bar"
end
%w{code line file doc}.each do |arg|
it "should set #{arg} if provided" do
type = Puppet::Resource::Type.new(:hostclass, "foo", arg.to_sym => "something")
type.send(arg).should == "something"
end
end
it "should set any provided arguments with the keys as symbols" do
type = Puppet::Resource::Type.new(:hostclass, "foo", :arguments => {:foo => "bar", :baz => "biz"})
type.should be_valid_parameter("foo")
type.should be_valid_parameter("baz")
end
it "should set any provided arguments with they keys as strings" do
type = Puppet::Resource::Type.new(:hostclass, "foo", :arguments => {"foo" => "bar", "baz" => "biz"})
type.should be_valid_parameter(:foo)
type.should be_valid_parameter(:baz)
end
it "should function if provided no arguments" do
type = Puppet::Resource::Type.new(:hostclass, "foo")
type.should_not be_valid_parameter(:foo)
end
end
describe "when testing the validity of an attribute" do
it "should return true if the parameter was typed at initialization" do
Puppet::Resource::Type.new(:hostclass, "foo", :arguments => {"foo" => "bar"}).should be_valid_parameter("foo")
end
it "should return true if it is a metaparam" do
Puppet::Resource::Type.new(:hostclass, "foo").should be_valid_parameter("require")
end
it "should return true if the parameter is named 'name'" do
Puppet::Resource::Type.new(:hostclass, "foo").should be_valid_parameter("name")
end
it "should return false if it is not a metaparam and was not provided at initialization" do
Puppet::Resource::Type.new(:hostclass, "foo").should_not be_valid_parameter("yayness")
end
end
describe "when creating a subscope" do
before do
@scope = stub 'scope', :newscope => nil
@resource = stub 'resource'
@type = Puppet::Resource::Type.new(:hostclass, "foo")
end
it "should return a new scope created with the provided scope as the parent" do
@scope.expects(:newscope).returns "foo"
@type.subscope(@scope, @resource).should == "foo"
end
it "should set the source as itself" do
@scope.expects(:newscope).with { |args| args[:source] == @type }
@type.subscope(@scope, @resource)
end
it "should set the scope's namespace to its namespace" do
@type.expects(:namespace).returns "yayness"
@scope.expects(:newscope).with { |args| args[:namespace] == "yayness" }
@type.subscope(@scope, @resource)
end
it "should set the scope's resource to the provided resource" do
@scope.expects(:newscope).with { |args| args[:resource] == @resource }
@type.subscope(@scope, @resource)
end
end
describe "when setting its parameters in the scope" do
before do
@scope = stub 'scope', :newscope => nil, :setvar => nil
@resource = stub 'resource', :title => "yay", :name => "yea", :ref => "Foo[bar]"
@type = Puppet::Resource::Type.new(:hostclass, "foo")
end
it "should set each of the resource's parameters as variables in the scope" do
@type.set_arguments :foo => nil, :boo => nil
@resource.expects(:to_hash).returns(:foo => "bar", :boo => "baz")
@scope.expects(:setvar).with("foo", "bar")
@scope.expects(:setvar).with("boo", "baz")
@scope.stubs(:class_set).with("foo",@scope)
@type.set_resource_parameters(@resource, @scope)
end
it "should set the variables as strings" do
@type.set_arguments :foo => nil
@resource.expects(:to_hash).returns(:foo => "bar")
@scope.expects(:setvar).with("foo", "bar")
@scope.stubs(:class_set).with("foo",@scope)
@type.set_resource_parameters(@resource, @scope)
end
it "should fail if any of the resource's parameters are not valid attributes" do
@type.set_arguments :foo => nil
@resource.expects(:to_hash).returns(:boo => "baz")
lambda { @type.set_resource_parameters(@resource, @scope) }.should raise_error(Puppet::ParseError)
end
it "should evaluate and set its default values as variables for parameters not provided by the resource" do
@type.set_arguments :foo => stub("value", :safeevaluate => "something")
@resource.expects(:to_hash).returns({})
@scope.expects(:setvar).with("foo", "something")
@scope.stubs(:class_set).with("foo",@scope)
@type.set_resource_parameters(@resource, @scope)
end
it "should fail if the resource does not provide a value for a required argument" do
@type.set_arguments :foo => nil
@resource.expects(:to_hash).returns({})
lambda { @type.set_resource_parameters(@resource, @scope) }.should raise_error(Puppet::ParseError)
end
it "should set the resource's title as a variable if not otherwise provided" do
@resource.expects(:to_hash).returns({})
@resource.expects(:title).returns 'teetle'
@scope.expects(:setvar).with("title", "teetle")
@scope.stubs(:class_set).with("foo",@scope)
@type.set_resource_parameters(@resource, @scope)
end
it "should set the resource's name as a variable if not otherwise provided" do
@resource.expects(:to_hash).returns({})
@resource.expects(:name).returns 'nombre'
@scope.expects(:setvar).with("name", "nombre")
@scope.stubs(:class_set).with("foo",@scope)
@type.set_resource_parameters(@resource, @scope)
end
end
describe "when describing and managing parent classes" do
before do
@code = Puppet::Resource::TypeCollection.new("env")
@parent = Puppet::Resource::Type.new(:hostclass, "bar")
@code.add @parent
@child = Puppet::Resource::Type.new(:hostclass, "foo", :parent => "bar")
@code.add @child
end
it "should be able to define a parent" do
Puppet::Resource::Type.new(:hostclass, "foo", :parent => "bar")
end
it "should use the code collection to find the parent resource type" do
@child.parent_type.should equal(@parent)
end
it "should be able to find parent nodes" do
parent = Puppet::Resource::Type.new(:node, "bar")
@code.add parent
child = Puppet::Resource::Type.new(:node, "foo", :parent => "bar")
@code.add child
child.parent_type.should equal(parent)
end
it "should cache a reference to the parent type" do
@code.expects(:hostclass).once.with("bar").returns @parent
@child.parent_type
@child.parent_type
end
it "should correctly state when it is another type's child" do
@child.should be_child_of(@parent)
end
it "should be considered the child of a parent's parent" do
@grandchild = Puppet::Resource::Type.new(:hostclass, "baz", :parent => "foo")
@code.add @grandchild
@grandchild.should be_child_of(@parent)
end
it "should correctly state when it is not another type's child" do
@notchild = Puppet::Resource::Type.new(:hostclass, "baz")
@code.add @notchild
@notchild.should_not be_child_of(@parent)
end
end
describe "when evaluating its code" do
before do
@compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("mynode"))
@scope = Puppet::Parser::Scope.new :compiler => @compiler
@resource = stub 'resource', :title => "yay", :name => "yea", :ref => "Foo[bar]", :scope => @scope
@type = Puppet::Resource::Type.new(:hostclass, "foo")
@type.stubs(:set_resource_parameters)
end
it "should set all of its parameters in a subscope" do
subscope = stub 'subscope', :compiler => @compiler
@type.expects(:subscope).with(@scope, @resource).returns subscope
@type.expects(:set_resource_parameters).with(@resource, subscope)
@type.evaluate_code(@resource)
end
it "should store the class scope" do
subscope = stub 'subscope'
subscope.expects(:class_set).with('foo',subscope)
@type.expects(:subscope).with(@scope, @resource).returns subscope
@type.evaluate_code(@resource)
end
it "should evaluate the code if any is provided" do
code = stub 'code'
@type.stubs(:code).returns code
@type.stubs(:subscope).returns stub("subscope", :compiler => @compiler)
code.expects(:safeevaluate).with @type.subscope
@type.evaluate_code(@resource)
end
it "should noop if there is no code" do
@type.expects(:code).returns nil
@type.evaluate_code(@resource)
end
end
describe "when creating a resource" do
before do
@node = Puppet::Node.new("foo")
@compiler = Puppet::Parser::Compiler.new(@node)
@scope = Puppet::Parser::Scope.new(:compiler => @compiler)
@top = Puppet::Resource::Type.new :hostclass, "top"
@middle = Puppet::Resource::Type.new :hostclass, "middle", :parent => "top"
@code = Puppet::Resource::TypeCollection.new("env")
@code.add @top
@code.add @middle
end
it "should create a resource instance" do
@top.mk_plain_resource(@scope).should be_instance_of(Puppet::Parser::Resource)
end
it "should set its resource type to 'class' when it is a hostclass" do
Puppet::Resource::Type.new(:hostclass, "top").mk_plain_resource(@scope).type.should == "Class"
end
it "should set its resource type to 'node' when it is a node" do
Puppet::Resource::Type.new(:node, "top").mk_plain_resource(@scope).type.should == "Node"
end
it "should fail when it is a definition" do
lambda { Puppet::Resource::Type.new(:definition, "top").mk_plain_resource(@scope) }.should raise_error(ArgumentError)
end
it "should add the created resource to the scope's catalog" do
@top.mk_plain_resource(@scope)
@compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource)
end
it "should evaluate the parent class if one exists" do
@middle.mk_plain_resource(@scope)
@compiler.catalog.resource(:class, "top").should be_instance_of(Puppet::Parser::Resource)
end
it "should fail to evaluate if a parent class is defined but cannot be found" do
othertop = Puppet::Resource::Type.new :hostclass, "something", :parent => "yay"
@code.add othertop
lambda { othertop.mk_plain_resource(@scope) }.should raise_error(Puppet::ParseError)
end
it "should not create a new resource if one already exists" do
@compiler.catalog.expects(:resource).with(:class, "top").returns("something")
@compiler.catalog.expects(:add_resource).never
@top.mk_plain_resource(@scope)
end
it "should return the existing resource when not creating a new one" do
@compiler.catalog.expects(:resource).with(:class, "top").returns("something")
@compiler.catalog.expects(:add_resource).never
@top.mk_plain_resource(@scope).should == "something"
end
it "should not create a new parent resource if one already exists and it has a parent class" do
@top.mk_plain_resource(@scope)
top_resource = @compiler.catalog.resource(:class, "top")
@middle.mk_plain_resource(@scope)
@compiler.catalog.resource(:class, "top").should equal(top_resource)
end
# #795 - tag before evaluation.
it "should tag the catalog with the resource tags when it is evaluated" do
@middle.mk_plain_resource(@scope)
@compiler.catalog.should be_tagged("middle")
end
it "should tag the catalog with the parent class tags when it is evaluated" do
@middle.mk_plain_resource(@scope)
@compiler.catalog.should be_tagged("top")
end
end
describe "when merging code from another instance" do
def code(str)
Puppet::Parser::AST::Leaf.new :value => str
end
it "should fail unless it is a class" do
- lambda { Puppet::Resource::Type.new(:node, "bar").merge("foo") }.should raise_error(ArgumentError)
+ lambda { Puppet::Resource::Type.new(:node, "bar").merge("foo") }.should raise_error(Puppet::Error)
end
it "should fail unless the source instance is a class" do
dest = Puppet::Resource::Type.new(:hostclass, "bar")
source = Puppet::Resource::Type.new(:node, "foo")
- lambda { dest.merge(source) }.should raise_error(ArgumentError)
+ lambda { dest.merge(source) }.should raise_error(Puppet::Error)
end
it "should fail if both classes have different parent classes" do
code = Puppet::Resource::TypeCollection.new("env")
{"a" => "b", "c" => "d"}.each do |parent, child|
code.add Puppet::Resource::Type.new(:hostclass, parent)
code.add Puppet::Resource::Type.new(:hostclass, child, :parent => parent)
end
- lambda { code.hostclass("b").merge(code.hostclass("d")) }.should raise_error(ArgumentError)
+ lambda { code.hostclass("b").merge(code.hostclass("d")) }.should raise_error(Puppet::Error)
end
it "should copy the other class's parent if it has not parent" do
dest = Puppet::Resource::Type.new(:hostclass, "bar")
parent = Puppet::Resource::Type.new(:hostclass, "parent")
source = Puppet::Resource::Type.new(:hostclass, "foo", :parent => "parent")
dest.merge(source)
dest.parent.should == "parent"
end
it "should copy the other class's documentation as its docs if it has no docs" do
dest = Puppet::Resource::Type.new(:hostclass, "bar")
source = Puppet::Resource::Type.new(:hostclass, "foo", :doc => "yayness")
dest.merge(source)
dest.doc.should == "yayness"
end
it "should append the other class's docs to its docs if it has any" do
dest = Puppet::Resource::Type.new(:hostclass, "bar", :doc => "fooness")
source = Puppet::Resource::Type.new(:hostclass, "foo", :doc => "yayness")
dest.merge(source)
dest.doc.should == "foonessyayness"
end
it "should turn its code into an ASTArray if necessary" do
dest = Puppet::Resource::Type.new(:hostclass, "bar", :code => code("foo"))
source = Puppet::Resource::Type.new(:hostclass, "foo", :code => code("bar"))
dest.merge(source)
dest.code.should be_instance_of(Puppet::Parser::AST::ASTArray)
end
it "should set the other class's code as its code if it has none" do
dest = Puppet::Resource::Type.new(:hostclass, "bar")
source = Puppet::Resource::Type.new(:hostclass, "foo", :code => code("bar"))
dest.merge(source)
dest.code.value.should == "bar"
end
it "should append the other class's code to its code if it has any" do
dcode = Puppet::Parser::AST::ASTArray.new :children => [code("dest")]
dest = Puppet::Resource::Type.new(:hostclass, "bar", :code => dcode)
scode = Puppet::Parser::AST::ASTArray.new :children => [code("source")]
source = Puppet::Resource::Type.new(:hostclass, "foo", :code => scode)
dest.merge(source)
dest.code.children.collect { |l| l.value }.should == %w{dest source}
end
end
end
diff --git a/spec/unit/resource/type_collection.rb b/spec/unit/resource/type_collection.rb
index 3de5e504b..a2a213f5a 100644
--- a/spec/unit/resource/type_collection.rb
+++ b/spec/unit/resource/type_collection.rb
@@ -1,347 +1,381 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/resource/type_collection'
require 'puppet/resource/type'
describe Puppet::Resource::TypeCollection do
before do
@instance = Puppet::Resource::Type.new(:hostclass, "foo")
@code = Puppet::Resource::TypeCollection.new("env")
end
it "should require an environment at initialization" do
env = Puppet::Node::Environment.new("testing")
Puppet::Resource::TypeCollection.new(env).environment.should equal(env)
end
it "should convert the environment into an environment instance if a string is provided" do
env = Puppet::Node::Environment.new("testing")
Puppet::Resource::TypeCollection.new("testing").environment.should equal(env)
end
it "should be able to add a resource type" do
Puppet::Resource::TypeCollection.new("env").should respond_to(:add)
end
it "should consider '<<' to be an alias to 'add' but should return self" do
loader = Puppet::Resource::TypeCollection.new("env")
loader.expects(:add).with "foo"
loader.expects(:add).with "bar"
loader << "foo" << "bar"
end
it "should set itself as the code collection for added resource types" do
loader = Puppet::Resource::TypeCollection.new("env")
node = Puppet::Resource::Type.new(:node, "foo")
@code.add(node)
@code.node("foo").should equal(node)
node.code_collection.should equal(@code)
end
it "should store node resource types as nodes" do
node = Puppet::Resource::Type.new(:node, "foo")
@code.add(node)
@code.node("foo").should equal(node)
end
it "should store hostclasses as hostclasses" do
klass = Puppet::Resource::Type.new(:hostclass, "foo")
@code.add(klass)
@code.hostclass("foo").should equal(klass)
end
it "should store definitions as definitions" do
define = Puppet::Resource::Type.new(:definition, "foo")
@code.add(define)
@code.definition("foo").should equal(define)
end
+ it "should merge new classes with existing classes of the same name" do
+ loader = Puppet::Resource::TypeCollection.new("env")
+ first = Puppet::Resource::Type.new(:hostclass, "foo")
+ second = Puppet::Resource::Type.new(:hostclass, "foo")
+ loader.add first
+ first.expects(:merge).with(second)
+ loader.add(second)
+ end
+
+ it "should remove all nodes, classes, and definitions when cleared" do
+ loader = Puppet::Resource::TypeCollection.new("env")
+ loader.add Puppet::Resource::Type.new(:hostclass, "class")
+ loader.add Puppet::Resource::Type.new(:definition, "define")
+ loader.add Puppet::Resource::Type.new(:node, "node")
+
+ loader.clear
+ loader.hostclass("class").should be_nil
+ loader.definition("define").should be_nil
+ loader.node("node").should be_nil
+ end
+
%w{hostclass node definition}.each do |data|
+ before do
+ @instance = Puppet::Resource::Type.new(data, "foo")
+ end
+
it "should have a method for adding a #{data}" do
Puppet::Resource::TypeCollection.new("env").should respond_to("add_" + data)
end
it "should use the name of the instance to add it" do
loader = Puppet::Resource::TypeCollection.new("env")
loader.send("add_#{data}", @instance)
loader.send(data, @instance.name).should equal(@instance)
end
- it "should fail to add a #{data} when one already exists" do
- loader = Puppet::Resource::TypeCollection.new("env")
- loader.add @instance
- lambda { loader.add(@instance) }.should raise_error(Puppet::ParseError)
+ unless data == "hostclass"
+ it "should fail to add a #{data} when one already exists" do
+ loader = Puppet::Resource::TypeCollection.new("env")
+ loader.add @instance
+ lambda { loader.add(@instance) }.should raise_error(Puppet::ParseError)
+ end
end
it "should return the added #{data}" do
loader = Puppet::Resource::TypeCollection.new("env")
loader.add(@instance).should equal(@instance)
end
it "should be able to retrieve #{data} by name" do
loader = Puppet::Resource::TypeCollection.new("env")
instance = Puppet::Resource::Type.new(data, "bar")
loader.add instance
loader.send(data, "bar").should equal(instance)
end
it "should retrieve #{data} insensitive to case" do
loader = Puppet::Resource::TypeCollection.new("env")
instance = Puppet::Resource::Type.new(data, "Bar")
loader.add instance
loader.send(data, "bAr").should equal(instance)
end
it "should return nil when asked for a #{data} that has not been added" do
Puppet::Resource::TypeCollection.new("env").send(data, "foo").should be_nil
end
it "should be able to retrieve all #{data}s" do
plurals = { "hostclass" => "hostclasses", "node" => "nodes", "definition" => "definitions" }
loader = Puppet::Resource::TypeCollection.new("env")
instance = Puppet::Resource::Type.new(data, "foo")
loader.add instance
loader.send(plurals[data]).should == { "foo" => instance }
end
end
describe "when finding a qualified instance" do
it "should return any found instance if the instance name is fully qualified" do
loader = Puppet::Resource::TypeCollection.new("env")
instance = Puppet::Resource::Type.new(:hostclass, "foo::bar")
loader.add instance
loader.find("namespace", "::foo::bar", :hostclass).should equal(instance)
end
it "should return nil if the instance name is fully qualified and no such instance exists" do
loader = Puppet::Resource::TypeCollection.new("env")
loader.find("namespace", "::foo::bar", :hostclass).should be_nil
end
+ it "should be able to find classes in the base namespace" do
+ loader = Puppet::Resource::TypeCollection.new("env")
+ instance = Puppet::Resource::Type.new(:hostclass, "foo")
+ loader.add instance
+ loader.find("", "foo", :hostclass).should equal(instance)
+ end
+
it "should return the partially qualified object if it exists in a provided namespace" do
loader = Puppet::Resource::TypeCollection.new("env")
instance = Puppet::Resource::Type.new(:hostclass, "foo::bar::baz")
loader.add instance
loader.find("foo", "bar::baz", :hostclass).should equal(instance)
end
it "should be able to find partially qualified objects in any of the provided namespaces" do
loader = Puppet::Resource::TypeCollection.new("env")
instance = Puppet::Resource::Type.new(:hostclass, "foo::bar::baz")
loader.add instance
loader.find(["nons", "foo", "otherns"], "bar::baz", :hostclass).should equal(instance)
end
it "should return the unqualified object if it exists in a provided namespace" do
loader = Puppet::Resource::TypeCollection.new("env")
instance = Puppet::Resource::Type.new(:hostclass, "foo::bar")
loader.add instance
loader.find("foo", "bar", :hostclass).should equal(instance)
end
it "should return the unqualified object if it exists in the parent namespace" do
loader = Puppet::Resource::TypeCollection.new("env")
instance = Puppet::Resource::Type.new(:hostclass, "foo::bar")
loader.add instance
loader.find("foo::bar::baz", "bar", :hostclass).should equal(instance)
end
it "should should return the partially qualified object if it exists in the parent namespace" do
loader = Puppet::Resource::TypeCollection.new("env")
instance = Puppet::Resource::Type.new(:hostclass, "foo::bar::baz")
loader.add instance
loader.find("foo::bar", "bar::baz", :hostclass).should equal(instance)
end
it "should return the qualified object if it exists in the root namespace" do
loader = Puppet::Resource::TypeCollection.new("env")
instance = Puppet::Resource::Type.new(:hostclass, "foo::bar::baz")
loader.add instance
loader.find("foo::bar", "foo::bar::baz", :hostclass).should equal(instance)
end
it "should return nil if the object cannot be found" do
loader = Puppet::Resource::TypeCollection.new("env")
instance = Puppet::Resource::Type.new(:hostclass, "foo::bar::baz")
loader.add instance
loader.find("foo::bar", "eh", :hostclass).should be_nil
end
end
it "should use the generic 'find' method with an empty namespace to find nodes" do
loader = Puppet::Resource::TypeCollection.new("env")
loader.expects(:find).with("", "bar", :node)
loader.find_node("bar")
end
it "should use the generic 'find' method to find hostclasses" do
loader = Puppet::Resource::TypeCollection.new("env")
loader.expects(:find).with("foo", "bar", :hostclass)
loader.find_hostclass("foo", "bar")
end
it "should use the generic 'find' method to find definitions" do
loader = Puppet::Resource::TypeCollection.new("env")
loader.expects(:find).with("foo", "bar", :definition)
loader.find_definition("foo", "bar")
end
it "should indicate whether any nodes are defined" do
loader = Puppet::Resource::TypeCollection.new("env")
loader.add_node(Puppet::Resource::Type.new(:node, "foo"))
loader.should be_nodes
end
it "should indicate whether no nodes are defined" do
Puppet::Resource::TypeCollection.new("env").should_not be_nodes
end
describe "when finding nodes" do
before :each do
@loader = Puppet::Resource::TypeCollection.new("env")
end
it "should return any node whose name exactly matches the provided node name" do
node = Puppet::Resource::Type.new(:node, "foo")
@loader << node
@loader.node("foo").should equal(node)
end
it "should return the first regex node whose regex matches the provided node name" do
node1 = Puppet::Resource::Type.new(:node, /\w/)
node2 = Puppet::Resource::Type.new(:node, /\d/)
@loader << node1 << node2
@loader.node("foo10").should equal(node1)
end
it "should preferentially return a node whose name is string-equal over returning a node whose regex matches a provided name" do
node1 = Puppet::Resource::Type.new(:node, /\w/)
node2 = Puppet::Resource::Type.new(:node, "foo")
@loader << node1 << node2
@loader.node("foo").should equal(node2)
end
end
describe "when managing files" do
before do
@loader = Puppet::Resource::TypeCollection.new("env")
Puppet::Util::LoadedFile.stubs(:new).returns stub("watched_file")
end
it "should have a method for specifying a file should be watched" do
@loader.should respond_to(:watch_file)
end
it "should have a method for determining if a file is being watched" do
@loader.watch_file("/foo/bar")
@loader.should be_watching_file("/foo/bar")
end
it "should use LoadedFile to watch files" do
Puppet::Util::LoadedFile.expects(:new).with("/foo/bar").returns stub("watched_file")
@loader.watch_file("/foo/bar")
end
it "should be considered stale if any files have changed" do
file1 = stub 'file1', :changed? => false
file2 = stub 'file2', :changed? => true
Puppet::Util::LoadedFile.expects(:new).times(2).returns(file1).then.returns(file2)
@loader.watch_file("/foo/bar")
@loader.watch_file("/other/bar")
@loader.should be_stale
end
it "should not be considered stable if no files have changed" do
file1 = stub 'file1', :changed? => false
file2 = stub 'file2', :changed? => false
Puppet::Util::LoadedFile.expects(:new).times(2).returns(file1).then.returns(file2)
@loader.watch_file("/foo/bar")
@loader.watch_file("/other/bar")
@loader.should_not be_stale
end
end
describe "when performing initial import" do
before do
@parser = stub 'parser', :file= => nil, :string => nil, :parse => nil
Puppet::Parser::Parser.stubs(:new).returns @parser
@code = Puppet::Resource::TypeCollection.new("env")
end
it "should create a new parser instance" do
Puppet::Parser::Parser.expects(:new).returns @parser
@code.perform_initial_import
end
it "should set the parser's string to the 'code' setting and parse if code is available" do
Puppet.settings[:code] = "my code"
@parser.expects(:string=).with "my code"
@parser.expects(:parse)
@code.perform_initial_import
end
it "should set the parser's file to the 'manifest' setting and parse if no code is available and the manifest is available" do
File.expects(:exist?).with("/my/file").returns true
Puppet.settings[:manifest] = "/my/file"
@parser.expects(:file=).with "/my/file"
@parser.expects(:parse)
@code.perform_initial_import
end
it "should not attempt to load a manifest if none is present" do
File.expects(:exist?).with("/my/file").returns false
Puppet.settings[:manifest] = "/my/file"
@parser.expects(:file=).never
@parser.expects(:parse).never
@code.perform_initial_import
end
it "should fail helpfully if there is an error importing" do
File.stubs(:exist?).returns true
@parser.expects(:parse).raises ArgumentError
lambda { @code.perform_initial_import }.should raise_error(Puppet::Error)
end
end
describe "when determining the configuration version" do
before do
@code = Puppet::Resource::TypeCollection.new("env")
end
it "should default to the current time" do
time = Time.now
Time.stubs(:now).returns time
@code.version.should == time.to_i
end
it "should use the output of the environment's config_version setting if one is provided" do
@code.environment.stubs(:[]).with(:config_version).returns("/my/foo")
Puppet::Util.expects(:execute).with(["/my/foo"]).returns "output\n"
@code.version.should == "output"
end
it "should raise a puppet parser error if executing config_version fails" do
@code.environment.stubs(:[]).with(:config_version).returns("test")
Puppet::Util.expects(:execute).raises(Puppet::ExecutionFailure.new("msg"))
lambda { @code.version }.should raise_error(Puppet::ParseError)
end
end
end
diff --git a/spec/unit/transportable.rb b/spec/unit/transportable.rb
new file mode 100644
index 000000000..e69de29bb
diff --git a/spec/unit/type.rb b/spec/unit/type.rb
index 73f249faa..b2f4bcbd4 100755
--- a/spec/unit/type.rb
+++ b/spec/unit/type.rb
@@ -1,494 +1,494 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../spec_helper'
describe Puppet::Type do
it "should include the Cacher module" do
Puppet::Type.ancestors.should be_include(Puppet::Util::Cacher)
end
it "should consider a parameter to be valid if it is a valid parameter" do
Puppet::Type.type(:mount).should be_valid_parameter(:path)
end
it "should consider a parameter to be valid if it is a valid property" do
Puppet::Type.type(:mount).should be_valid_parameter(:fstype)
end
it "should consider a parameter to be valid if it is a valid metaparam" do
Puppet::Type.type(:mount).should be_valid_parameter(:noop)
end
it "should use its catalog as its expirer" do
catalog = Puppet::Resource::Catalog.new
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
resource.catalog = catalog
resource.expirer.should equal(catalog)
end
it "should do nothing when asked to expire when it has no catalog" do
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
lambda { resource.expire }.should_not raise_error
end
it "should be able to retrieve a property by name" do
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
resource.property(:fstype).must be_instance_of(Puppet::Type.type(:mount).attrclass(:fstype))
end
it "should be able to retrieve a parameter by name" do
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
resource.parameter(:name).must be_instance_of(Puppet::Type.type(:mount).attrclass(:name))
end
it "should be able to retrieve a property by name using the :parameter method" do
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
resource.parameter(:fstype).must be_instance_of(Puppet::Type.type(:mount).attrclass(:fstype))
end
it "should be able to retrieve all set properties" do
resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
props = resource.properties
props.should_not be_include(nil)
[:fstype, :ensure, :pass].each do |name|
props.should be_include(resource.parameter(name))
end
end
it "should have a method for setting default values for resources" do
Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:set_default)
end
it "should do nothing for attributes that have no defaults and no specified value" do
Puppet::Type.type(:mount).new(:name => "foo").parameter(:noop).should be_nil
end
it "should have a method for adding tags" do
Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:tags)
end
it "should use the tagging module" do
Puppet::Type.type(:mount).ancestors.should be_include(Puppet::Util::Tagging)
end
it "should delegate to the tagging module when tags are added" do
resource = Puppet::Type.type(:mount).new(:name => "foo")
resource.stubs(:tag).with(:mount)
resource.expects(:tag).with(:tag1, :tag2)
resource.tags = [:tag1,:tag2]
end
it "should add the current type as tag" do
resource = Puppet::Type.type(:mount).new(:name => "foo")
resource.stubs(:tag)
resource.expects(:tag).with(:mount)
resource.tags = [:tag1,:tag2]
end
it "should have a method to know if the resource is exported" do
Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:exported?)
end
it "should have a method to know if the resource is virtual" do
Puppet::Type.type(:mount).new(:name => "foo").should respond_to(:virtual?)
end
it "should consider its version to be its catalog version" do
resource = Puppet::Type.type(:mount).new(:name => "foo")
catalog = Puppet::Resource::Catalog.new
catalog.version = 50
catalog.add_resource resource
resource.version.should == 50
end
it "should consider its version to be zero if it has no catalog" do
Puppet::Type.type(:mount).new(:name => "foo").version.should == 0
end
it "should provide source_descriptors" do
resource = Puppet::Type.type(:mount).new(:name => "foo")
catalog = Puppet::Resource::Catalog.new
catalog.version = 50
catalog.add_resource resource
resource.source_descriptors.should == {:version=>50, :tags=>["mount", "foo"], :path=>"/Mount[foo]"}
end
it "should consider its type to be the name of its class" do
Puppet::Type.type(:mount).new(:name => "foo").type.should == :mount
end
describe "when creating an event" do
before do
@resource = Puppet::Type.type(:mount).new :name => "foo"
end
it "should have the resource's reference as the resource" do
@resource.event.resource.should == "Mount[foo]"
end
it "should have the resource's log level as the default log level" do
@resource[:loglevel] = :warning
@resource.event.default_log_level.should == :warning
end
{:file => "/my/file", :line => 50, :tags => %{foo bar}, :version => 50}.each do |attr, value|
it "should set the #{attr}" do
@resource.stubs(attr).returns value
@resource.event.send(attr).should == value
end
end
it "should allow specification of event attributes" do
@resource.event(:status => "noop").status.should == "noop"
end
end
describe "when choosing a default provider" do
it "should choose the provider with the highest specificity" do
# Make a fake type
type = Puppet::Type.newtype(:defaultprovidertest) do
newparam(:name) do end
end
basic = type.provide(:basic) {}
greater = type.provide(:greater) {}
basic.stubs(:specificity).returns 1
greater.stubs(:specificity).returns 2
type.defaultprovider.should equal(greater)
end
end
describe "when initializing" do
describe "and passed a TransObject" do
it "should fail" do
trans = Puppet::TransObject.new("/foo", :mount)
lambda { Puppet::Type.type(:mount).new(trans) }.should raise_error(Puppet::DevError)
end
end
describe "and passed a Puppet::Resource instance" do
it "should set its title to the title of the resource if the resource type is equal to the current type" do
resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "/other"})
Puppet::Type.type(:mount).new(resource).title.should == "/foo"
end
it "should set its title to the resource reference if the resource type is not equal to the current type" do
resource = Puppet::Resource.new(:user, "foo")
Puppet::Type.type(:mount).new(resource).title.should == "User[foo]"
end
[:line, :file, :catalog, :exported, :virtual].each do |param|
it "should copy '#{param}' from the resource if present" do
resource = Puppet::Resource.new(:mount, "/foo")
resource.send(param.to_s + "=", "foo")
resource.send(param.to_s + "=", "foo")
Puppet::Type.type(:mount).new(resource).send(param).should == "foo"
end
end
it "should copy any tags from the resource" do
resource = Puppet::Resource.new(:mount, "/foo")
resource.tag "one", "two"
tags = Puppet::Type.type(:mount).new(resource).tags
tags.should be_include("one")
tags.should be_include("two")
end
it "should copy the resource's parameters as its own" do
resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:atboot => true, :fstype => "boo"})
params = Puppet::Type.type(:mount).new(resource).to_hash
params[:fstype].should == "boo"
params[:atboot].should == true
end
end
describe "and passed a Hash" do
it "should extract the title from the hash" do
Puppet::Type.type(:mount).new(:title => "/yay").title.should == "/yay"
end
it "should work when hash keys are provided as strings" do
Puppet::Type.type(:mount).new("title" => "/yay").title.should == "/yay"
end
it "should work when hash keys are provided as symbols" do
Puppet::Type.type(:mount).new(:title => "/yay").title.should == "/yay"
end
it "should use the name from the hash as the title if no explicit title is provided" do
Puppet::Type.type(:mount).new(:name => "/yay").title.should == "/yay"
end
it "should use the Resource Type's namevar to determine how to find the name in the hash" do
Puppet::Type.type(:file).new(:path => "/yay").title.should == "/yay"
end
it "should fail if the namevar is not equal to :name and both :name and the namevar are provided" do
lambda { Puppet::Type.type(:file).new(:path => "/yay", :name => "/foo") }.should raise_error(Puppet::Error)
@type.stubs(:namevar).returns :myname
end
[:catalog].each do |param|
it "should extract '#{param}' from the hash if present" do
Puppet::Type.type(:mount).new(:name => "/yay", param => "foo").send(param).should == "foo"
end
end
it "should use any remaining hash keys as its parameters" do
resource = Puppet::Type.type(:mount).new(:title => "/foo", :catalog => "foo", :atboot => true, :fstype => "boo")
resource[:fstype].must == "boo"
resource[:atboot].must == true
end
end
it "should fail if any invalid attributes have been provided" do
lambda { Puppet::Type.type(:mount).new(:title => "/foo", :nosuchattr => "whatever") }.should raise_error(Puppet::Error)
end
it "should set its name to the resource's title if the resource does not have a :name or namevar parameter set" do
resource = Puppet::Resource.new(:mount, "/foo")
Puppet::Type.type(:mount).new(resource).name.should == "/foo"
end
it "should fail if no title, name, or namevar are provided" do
lambda { Puppet::Type.type(:file).new(:atboot => true) }.should raise_error(Puppet::Error)
end
it "should set the attributes in the order returned by the class's :allattrs method" do
Puppet::Type.type(:mount).stubs(:allattrs).returns([:name, :atboot, :noop])
resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "myname", :atboot => "myboot", :noop => "whatever"})
set = []
Puppet::Type.type(:mount).any_instance.stubs(:newattr).with do |param, hash|
set << param
true
end
Puppet::Type.type(:mount).new(resource)
set[-1].should == :noop
set[-2].should == :atboot
end
it "should always set the name and then default provider before anything else" do
Puppet::Type.type(:mount).stubs(:allattrs).returns([:provider, :name, :atboot])
resource = Puppet::Resource.new(:mount, "/foo", :parameters => {:name => "myname", :atboot => "myboot"})
set = []
Puppet::Type.type(:mount).any_instance.stubs(:newattr).with do |param, hash|
set << param
true
end
Puppet::Type.type(:mount).new(resource)
set[0].should == :name
set[1].should == :provider
end
# This one is really hard to test :/
it "should each default immediately if no value is provided" do
defaults = []
Puppet::Type.type(:package).any_instance.stubs(:set_default).with { |value| defaults << value; true }
Puppet::Type.type(:package).new :name => "whatever"
defaults[0].should == :provider
end
it "should retain a copy of the originally provided parameters" do
Puppet::Type.type(:mount).new(:name => "foo", :atboot => true, :noop => false).original_parameters.should == {:atboot => true, :noop => false}
end
it "should delete the name via the namevar from the originally provided parameters" do
Puppet::Type.type(:file).new(:name => "/foo").original_parameters[:path].should be_nil
end
end
it "should have a class method for converting a hash into a Puppet::Resource instance" do
Puppet::Type.type(:mount).must respond_to(:hash2resource)
end
describe "when converting a hash to a Puppet::Resource instance" do
before do
@type = Puppet::Type.type(:mount)
end
it "should treat a :title key as the title of the resource" do
@type.hash2resource(:name => "/foo", :title => "foo").title.should == "foo"
end
it "should use the name from the hash as the title if no explicit title is provided" do
@type.hash2resource(:name => "foo").title.should == "foo"
end
it "should use the Resource Type's namevar to determine how to find the name in the hash" do
@type.stubs(:namevar).returns :myname
@type.hash2resource(:myname => "foo").title.should == "foo"
end
it "should fail if the namevar is not equal to :name and both :name and the namevar are provided" do
@type.stubs(:namevar).returns :myname
lambda { @type.hash2resource(:myname => "foo", :name => 'bar') }.should raise_error(Puppet::Error)
end
[:catalog].each do |attr|
it "should use any provided #{attr}" do
@type.hash2resource(:name => "foo", attr => "eh").send(attr).should == "eh"
end
end
it "should set all provided parameters on the resource" do
@type.hash2resource(:name => "foo", :fstype => "boo", :boot => "fee").to_hash.should == {:name => "foo", :fstype => "boo", :boot => "fee"}
end
it "should not set the title as a parameter on the resource" do
@type.hash2resource(:name => "foo", :title => "eh")[:title].should be_nil
end
it "should not set the catalog as a parameter on the resource" do
@type.hash2resource(:name => "foo", :catalog => "eh")[:catalog].should be_nil
end
it "should treat hash keys equivalently whether provided as strings or symbols" do
resource = @type.hash2resource("name" => "foo", "title" => "eh", "fstype" => "boo")
resource.title.should == "eh"
resource[:name].should == "foo"
resource[:fstype].should == "boo"
end
end
describe "when retrieving current property values" do
before do
@resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
@resource.property(:ensure).stubs(:retrieve).returns :absent
end
it "should fail if its provider is unsuitable" do
@resource = Puppet::Type.type(:mount).new(:name => "foo", :fstype => "bar", :pass => 1, :ensure => :present)
@resource.provider.class.expects(:suitable?).returns false
lambda { @resource.retrieve }.should raise_error(Puppet::Error)
end
it "should return a Puppet::Resource instance with its type and title set appropriately" do
result = @resource.retrieve
result.should be_instance_of(Puppet::Resource)
result.type.should == "Mount"
result.title.should == "foo"
end
it "should set the name of the returned resource if its own name and title differ" do
@resource[:name] = "my name"
@resource.title = "other name"
@resource.retrieve[:name].should == "my name"
end
it "should provide a value for all set properties" do
values = @resource.retrieve
[:ensure, :fstype, :pass].each { |property| values[property].should_not be_nil }
end
it "should provide a value for 'ensure' even if no desired value is provided" do
@resource = Puppet::Type.type(:file).new(:path => "/my/file/that/can't/exist")
end
it "should not call retrieve on non-ensure properties if the resource is absent and should consider the property absent" do
@resource.property(:ensure).expects(:retrieve).returns :absent
@resource.property(:fstype).expects(:retrieve).never
@resource.retrieve[:fstype].should == :absent
end
it "should include the result of retrieving each property's current value if the resource is present" do
@resource.property(:ensure).expects(:retrieve).returns :present
@resource.property(:fstype).expects(:retrieve).returns 15
@resource.retrieve[:fstype] == 15
end
end
describe "when in a catalog" do
before do
@catalog = Puppet::Resource::Catalog.new
@container = Puppet::Type.type(:component).new(:name => "container")
@one = Puppet::Type.type(:file).new(:path => "/file/one")
@two = Puppet::Type.type(:file).new(:path => "/file/two")
@catalog.add_resource @container
@catalog.add_resource @one
@catalog.add_resource @two
@catalog.add_edge @container, @one
@catalog.add_edge @container, @two
end
it "should have no parent if there is no in edge" do
@container.parent.should be_nil
end
it "should set its parent to its in edge" do
@one.parent.ref.should == @container.ref
end
after do
@catalog.clear(true)
end
end
describe "when managing relationships" do
end
end
describe Puppet::Type::RelationshipMetaparam do
it "should be a subclass of Puppet::Parameter" do
Puppet::Type::RelationshipMetaparam.superclass.should equal(Puppet::Parameter)
end
it "should be able to produce a list of subclasses" do
Puppet::Type::RelationshipMetaparam.should respond_to(:subclasses)
end
describe "when munging relationships" do
before do
@resource = Puppet::Type.type(:mount).new :name => "/foo"
@metaparam = Puppet::Type.metaparamclass(:require).new :resource => @resource
end
- it "should accept Puppet::Resource::Reference instances" do
- ref = Puppet::Resource::Reference.new(:file, "/foo")
+ it "should accept Puppet::Resource instances" do
+ ref = Puppet::Resource.new(:file, "/foo")
@metaparam.munge(ref)[0].should equal(ref)
end
- it "should turn any string into a Puppet::Resource::Reference" do
- @metaparam.munge("File[/ref]")[0].should be_instance_of(Puppet::Resource::Reference)
+ it "should turn any string into a Puppet::Resource" do
+ @metaparam.munge("File[/ref]")[0].should be_instance_of(Puppet::Resource)
end
end
it "should be able to validate relationships" do
Puppet::Type.metaparamclass(:require).new(:resource => mock("resource")).should respond_to(:validate_relationship)
end
it "should fail if any specified resource is not found in the catalog" do
catalog = mock 'catalog'
resource = stub 'resource', :catalog => catalog, :ref => "resource"
param = Puppet::Type.metaparamclass(:require).new(:resource => resource, :value => %w{Foo[bar] Class[test]})
catalog.expects(:resource).with("Foo[bar]").returns "something"
catalog.expects(:resource).with("Class[test]").returns nil
param.expects(:fail).with { |string| string.include?("Class[test]") }
param.validate_relationship
end
end
diff --git a/spec/unit/type/tidy.rb b/spec/unit/type/tidy.rb
index ccec9ed7c..9bee7d700 100755
--- a/spec/unit/type/tidy.rb
+++ b/spec/unit/type/tidy.rb
@@ -1,399 +1,399 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
tidy = Puppet::Type.type(:tidy)
describe tidy do
before do
Puppet.settings.stubs(:use)
# for an unknown reason some of these specs fails when run individually
# with a failed expectation on File.lstat in the autoloader.
File.stubs(:lstat)
end
it "should use :lstat when stating a file" do
resource = tidy.new :path => "/foo/bar", :age => "1d"
stat = mock 'stat'
File.expects(:lstat).with("/foo/bar").returns stat
resource.stat("/foo/bar").should == stat
end
[:age, :size, :path, :matches, :type, :recurse, :rmdirs].each do |param|
it "should have a %s parameter" % param do
Puppet::Type.type(:tidy).attrclass(param).ancestors.should be_include(Puppet::Parameter)
end
it "should have documentation for its %s param" % param do
Puppet::Type.type(:tidy).attrclass(param).doc.should be_instance_of(String)
end
end
describe "when validating parameter values" do
describe "for 'recurse'" do
before do
@tidy = Puppet::Type.type(:tidy).new :path => "/tmp", :age => "100d"
end
it "should allow 'true'" do
lambda { @tidy[:recurse] = true }.should_not raise_error
end
it "should allow 'false'" do
lambda { @tidy[:recurse] = false }.should_not raise_error
end
it "should allow integers" do
lambda { @tidy[:recurse] = 10 }.should_not raise_error
end
it "should allow string representations of integers" do
lambda { @tidy[:recurse] = "10" }.should_not raise_error
end
it "should allow 'inf'" do
lambda { @tidy[:recurse] = "inf" }.should_not raise_error
end
it "should not allow arbitrary values" do
lambda { @tidy[:recurse] = "whatever" }.should raise_error
end
end
end
describe "when matching files by age" do
convertors = {
:second => 1,
:minute => 60
}
convertors[:hour] = convertors[:minute] * 60
convertors[:day] = convertors[:hour] * 24
convertors[:week] = convertors[:day] * 7
convertors.each do |unit, multiple|
it "should consider a %s to be %s seconds" % [unit, multiple] do
tidy = Puppet::Type.type(:tidy).new :path => "/what/ever", :age => "5%s" % unit.to_s[0..0]
tidy[:age].should == 5 * multiple
end
end
end
describe "when matching files by size" do
convertors = {
:b => 0,
:kb => 1,
:mb => 2,
:gb => 3
}
convertors.each do |unit, multiple|
it "should consider a %s to be 1024^%s bytes" % [unit, multiple] do
tidy = Puppet::Type.type(:tidy).new :path => "/what/ever", :size => "5%s" % unit
total = 5
multiple.times { total *= 1024 }
tidy[:size].should == total
end
end
end
describe "when tidying" do
before do
@tidy = Puppet::Type.type(:tidy).new :path => "/what/ever"
@stat = stub 'stat', :ftype => "directory"
File.stubs(:lstat).with("/what/ever").returns @stat
end
describe "and generating files" do
it "should set the backup on the file if backup is set on the tidy instance" do
@tidy[:backup] = "whatever"
Puppet::Type.type(:file).expects(:new).with { |args| args[:backup] == "whatever" }
@tidy.mkfile("/what/ever")
end
it "should set the file's path to the tidy's path" do
Puppet::Type.type(:file).expects(:new).with { |args| args[:path] == "/what/ever" }
@tidy.mkfile("/what/ever")
end
it "should configure the file for deletion" do
Puppet::Type.type(:file).expects(:new).with { |args| args[:ensure] == :absent }
@tidy.mkfile("/what/ever")
end
it "should force deletion on the file" do
Puppet::Type.type(:file).expects(:new).with { |args| args[:force] == true }
@tidy.mkfile("/what/ever")
end
it "should do nothing if the targeted file does not exist" do
File.expects(:lstat).with("/what/ever").raises Errno::ENOENT
@tidy.generate.should == []
end
end
describe "and recursion is not used" do
it "should generate a file resource if the file should be tidied" do
@tidy.expects(:tidy?).with("/what/ever").returns true
file = Puppet::Type.type(:file).new(:path => "/eh")
@tidy.expects(:mkfile).with("/what/ever").returns file
@tidy.generate.should == [file]
end
it "should do nothing if the file should not be tidied" do
@tidy.expects(:tidy?).with("/what/ever").returns false
@tidy.expects(:mkfile).never
@tidy.generate.should == []
end
end
describe "and recursion is used" do
before do
@tidy[:recurse] = true
Puppet::FileServing::Fileset.any_instance.stubs(:stat).returns mock("stat")
@fileset = Puppet::FileServing::Fileset.new("/what/ever")
Puppet::FileServing::Fileset.stubs(:new).returns @fileset
end
it "should use a Fileset for infinite recursion" do
Puppet::FileServing::Fileset.expects(:new).with("/what/ever", :recurse => true).returns @fileset
@fileset.expects(:files).returns %w{. one two}
@tidy.stubs(:tidy?).returns false
@tidy.generate
end
it "should use a Fileset for limited recursion" do
@tidy[:recurse] = 42
Puppet::FileServing::Fileset.expects(:new).with("/what/ever", :recurse => true, :recurselimit => 42).returns @fileset
@fileset.expects(:files).returns %w{. one two}
@tidy.stubs(:tidy?).returns false
@tidy.generate
end
it "should generate a file resource for every file that should be tidied but not for files that should not be tidied" do
@fileset.expects(:files).returns %w{. one two}
@tidy.expects(:tidy?).with("/what/ever").returns true
@tidy.expects(:tidy?).with("/what/ever/one").returns true
@tidy.expects(:tidy?).with("/what/ever/two").returns false
file = Puppet::Type.type(:file).new(:path => "/eh")
@tidy.expects(:mkfile).with("/what/ever").returns file
@tidy.expects(:mkfile).with("/what/ever/one").returns file
@tidy.generate
end
end
describe "and determining whether a file matches provided glob patterns" do
before do
@tidy = Puppet::Type.type(:tidy).new :path => "/what/ever"
@tidy[:matches] = %w{*foo* *bar*}
@stat = mock 'stat'
@matcher = @tidy.parameter(:matches)
end
it "should always convert the globs to an array" do
@matcher.value = "*foo*"
@matcher.value.should == %w{*foo*}
end
it "should return true if any pattern matches the last part of the file" do
@matcher.value = %w{*foo* *bar*}
@matcher.must be_tidy("/file/yaybarness", @stat)
end
it "should return false if no pattern matches the last part of the file" do
@matcher.value = %w{*foo* *bar*}
@matcher.should_not be_tidy("/file/yayness", @stat)
end
end
describe "and determining whether a file is too old" do
before do
@tidy = Puppet::Type.type(:tidy).new :path => "/what/ever"
@stat = stub 'stat'
@tidy[:age] = "1s"
@tidy[:type] = "mtime"
@ager = @tidy.parameter(:age)
end
it "should use the age type specified" do
@tidy[:type] = :ctime
@stat.expects(:ctime).returns(Time.now)
@ager.tidy?("/what/ever", @stat)
end
it "should return false if the file is more recent than the specified age" do
@stat.expects(:mtime).returns(Time.now)
@ager.should_not be_tidy("/what/ever", @stat)
end
it "should return true if the file is older than the specified age" do
@stat.expects(:mtime).returns(Time.now - 10)
@ager.must be_tidy("/what/ever", @stat)
end
end
describe "and determining whether a file is too large" do
before do
@tidy = Puppet::Type.type(:tidy).new :path => "/what/ever"
@stat = stub 'stat', :ftype => "file"
@tidy[:size] = "1kb"
@sizer = @tidy.parameter(:size)
end
it "should return false if the file is smaller than the specified size" do
@stat.expects(:size).returns(4) # smaller than a kilobyte
@sizer.should_not be_tidy("/what/ever", @stat)
end
it "should return true if the file is larger than the specified size" do
@stat.expects(:size).returns(1500) # larger than a kilobyte
@sizer.must be_tidy("/what/ever", @stat)
end
it "should return true if the file is equal to the specified size" do
@stat.expects(:size).returns(1024)
@sizer.must be_tidy("/what/ever", @stat)
end
end
describe "and determining whether a file should be tidied" do
before do
@tidy = Puppet::Type.type(:tidy).new :path => "/what/ever"
@stat = stub 'stat', :ftype => "file"
File.stubs(:lstat).with("/what/ever").returns @stat
end
it "should not try to recurse if the file does not exist" do
@tidy[:recurse] = true
File.stubs(:lstat).with("/what/ever").returns nil
@tidy.generate.should == []
end
it "should not be tidied if the file does not exist" do
File.expects(:lstat).with("/what/ever").raises Errno::ENOENT
@tidy.should_not be_tidy("/what/ever")
end
it "should not be tidied if the user has no access to the file" do
File.expects(:lstat).with("/what/ever").raises Errno::EACCES
@tidy.should_not be_tidy("/what/ever")
end
it "should not be tidied if it is a directory and rmdirs is set to false" do
stat = mock 'stat', :ftype => "directory"
File.expects(:lstat).with("/what/ever").returns stat
@tidy.should_not be_tidy("/what/ever")
end
it "should return false if it does not match any provided globs" do
@tidy[:matches] = "globs"
matches = @tidy.parameter(:matches)
matches.expects(:tidy?).with("/what/ever", @stat).returns false
@tidy.should_not be_tidy("/what/ever")
end
it "should return false if it does not match aging requirements" do
@tidy[:age] = "1d"
ager = @tidy.parameter(:age)
ager.expects(:tidy?).with("/what/ever", @stat).returns false
@tidy.should_not be_tidy("/what/ever")
end
it "should return false if it does not match size requirements" do
@tidy[:size] = "1b"
sizer = @tidy.parameter(:size)
sizer.expects(:tidy?).with("/what/ever", @stat).returns false
@tidy.should_not be_tidy("/what/ever")
end
it "should tidy a file if age and size are set but only size matches" do
@tidy[:size] = "1b"
@tidy[:age] = "1d"
@tidy.parameter(:size).stubs(:tidy?).returns true
@tidy.parameter(:age).stubs(:tidy?).returns false
@tidy.should be_tidy("/what/ever")
end
it "should tidy a file if age and size are set but only age matches" do
@tidy[:size] = "1b"
@tidy[:age] = "1d"
@tidy.parameter(:size).stubs(:tidy?).returns false
@tidy.parameter(:age).stubs(:tidy?).returns true
@tidy.should be_tidy("/what/ever")
end
it "should tidy all files if neither age nor size is set" do
@tidy.must be_tidy("/what/ever")
end
it "should sort the results inversely by path length, so files are added to the catalog before their directories" do
@tidy[:recurse] = true
@tidy[:rmdirs] = true
fileset = Puppet::FileServing::Fileset.new("/what/ever")
Puppet::FileServing::Fileset.expects(:new).returns fileset
fileset.expects(:files).returns %w{. one one/two}
@tidy.stubs(:tidy?).returns true
@tidy.generate.collect { |r| r[:path] }.should == %w{/what/ever/one/two /what/ever/one /what/ever}
end
end
it "should configure directories to require their contained files if rmdirs is enabled, so the files will be deleted first" do
@tidy[:recurse] = true
@tidy[:rmdirs] = true
fileset = mock 'fileset'
Puppet::FileServing::Fileset.expects(:new).with("/what/ever", :recurse => true).returns fileset
fileset.expects(:files).returns %w{. one two one/subone two/subtwo one/subone/ssone}
@tidy.stubs(:tidy?).returns true
result = @tidy.generate.inject({}) { |hash, res| hash[res[:path]] = res; hash }
{
"/what/ever" => %w{/what/ever/one /what/ever/two},
"/what/ever/one" => ["/what/ever/one/subone"],
"/what/ever/two" => ["/what/ever/two/subtwo"],
"/what/ever/one/subone" => ["/what/ever/one/subone/ssone"]
}.each do |parent, children|
children.each do |child|
- ref = Puppet::Resource::Reference.new(:file, child)
+ ref = Puppet::Resource.new(:file, child)
result[parent][:require].find { |req| req.to_s == ref.to_s }.should_not be_nil
end
end
end
end
end
diff --git a/test/language/functions.rb b/test/language/functions.rb
index af0732559..70605f8f6 100755
--- a/test/language/functions.rb
+++ b/test/language/functions.rb
@@ -1,602 +1,504 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../lib/puppettest'
require 'puppet'
require 'puppet/parser/parser'
require 'puppet/network/client'
require 'puppettest'
require 'puppettest/resourcetesting'
class TestLangFunctions < Test::Unit::TestCase
include PuppetTest::ParserTesting
include PuppetTest::ResourceTesting
def test_functions
assert_nothing_raised do
Puppet::Parser::AST::Function.new(
:name => "fakefunction",
:arguments => AST::ASTArray.new(
:children => [nameobj("avalue")]
)
)
end
assert_raise(Puppet::ParseError) do
func = Puppet::Parser::AST::Function.new(
:name => "fakefunction",
:arguments => AST::ASTArray.new(
:children => [nameobj("avalue")]
)
)
func.evaluate(mkscope)
end
assert_nothing_raised do
Puppet::Parser::Functions.newfunction(:fakefunction, :type => :rvalue) do |input|
return "output %s" % input[0]
end
end
func = nil
assert_nothing_raised do
func = Puppet::Parser::AST::Function.new(
:name => "fakefunction",
:ftype => :rvalue,
:arguments => AST::ASTArray.new(
:children => [nameobj("avalue")]
)
)
end
scope = mkscope
val = nil
assert_nothing_raised do
val = func.evaluate(scope)
end
assert_equal("output avalue", val)
end
def test_taggedfunction
scope = mkscope
scope.resource.tag("yayness")
# Make sure the ast stuff does what it's supposed to
{"yayness" => true, "booness" => false}.each do |tag, retval|
func = taggedobj(tag, :rvalue)
val = nil
assert_nothing_raised do
val = func.evaluate(scope)
end
assert_equal(retval, val, "'tagged' returned %s for %s" % [val, tag])
end
# Now make sure we correctly get tags.
scope.resource.tag("resourcetag")
assert(scope.function_tagged("resourcetag"), "tagged function did not catch resource tags")
scope.compiler.catalog.tag("configtag")
assert(scope.function_tagged("configtag"), "tagged function did not catch catalog tags")
end
def test_failfunction
func = nil
assert_nothing_raised do
func = Puppet::Parser::AST::Function.new(
:name => "fail",
:ftype => :statement,
:arguments => AST::ASTArray.new(
:children => [stringobj("this is a failure"),
stringobj("and another")]
)
)
end
scope = mkscope
val = nil
assert_raise(Puppet::ParseError) do
val = func.evaluate(scope)
end
end
def test_multipletemplates
Dir.mkdir(Puppet[:templatedir])
onep = File.join(Puppet[:templatedir], "one")
twop = File.join(Puppet[:templatedir], "two")
File.open(onep, "w") do |f|
f.puts "<%- if @one.nil? then raise '@one undefined' end -%>" +
"template <%= @one %>"
end
File.open(twop, "w") do |f|
f.puts "template <%= @two %>"
end
func = nil
assert_nothing_raised do
func = Puppet::Parser::AST::Function.new(
:name => "template",
:ftype => :rvalue,
:arguments => AST::ASTArray.new(
:children => [stringobj("one"),
stringobj("two")]
)
)
end
ast = varobj("output", func)
scope = mkscope
# Test that our manual exception throw fails the parse
assert_raise(Puppet::ParseError) do
ast.evaluate(scope)
end
# Test that our use of an undefined instance variable does not throw
# an exception, but only safely continues.
scope.setvar("one", "One")
assert_nothing_raised do
ast.evaluate(scope)
end
# Ensure that we got the output we expected from that evaluation.
assert_equal("template One\ntemplate \n", scope.lookupvar("output"),
"Undefined template variables do not raise exceptions")
# Now, fill in the last variable and make sure the whole thing
# evaluates correctly.
scope.setvar("two", "Two")
scope.unsetvar("output")
assert_nothing_raised do
ast.evaluate(scope)
end
assert_equal("template One\ntemplate Two\n", scope.lookupvar("output"),
"Templates were not handled correctly")
end
# Now make sure we can fully qualify files, and specify just one
def test_singletemplates
template = tempfile()
File.open(template, "w") do |f|
f.puts "template <%= @yay.nil?() ? raise('yay undefined') : @yay %>"
end
func = nil
assert_nothing_raised do
func = Puppet::Parser::AST::Function.new(
:name => "template",
:ftype => :rvalue,
:arguments => AST::ASTArray.new(
:children => [stringobj(template)]
)
)
end
ast = varobj("output", func)
scope = mkscope
assert_raise(Puppet::ParseError) do
ast.evaluate(scope)
end
scope.setvar("yay", "this is yay")
assert_nothing_raised do
ast.evaluate(scope)
end
assert_equal("template this is yay\n", scope.lookupvar("output"),
"Templates were not handled correctly")
end
# Make sure that legacy template variable access works as expected.
def test_legacyvariables
template = tempfile()
File.open(template, "w") do |f|
f.puts "template <%= deprecated %>"
end
func = nil
assert_nothing_raised do
func = Puppet::Parser::AST::Function.new(
:name => "template",
:ftype => :rvalue,
:arguments => AST::ASTArray.new(
:children => [stringobj(template)]
)
)
end
ast = varobj("output", func)
# Verify that we get an exception using old-style accessors.
scope = mkscope
assert_raise(Puppet::ParseError) do
ast.evaluate(scope)
end
# Verify that we evaluate and return their value correctly.
scope.setvar("deprecated", "deprecated value")
assert_nothing_raised do
ast.evaluate(scope)
end
assert_equal("template deprecated value\n", scope.lookupvar("output"),
"Deprecated template variables were not handled correctly")
end
# Make sure that problems with kernel method visibility still exist.
def test_kernel_module_shadows_deprecated_var_lookup
template = tempfile()
File.open(template, "w").puts("<%= binding %>")
func = nil
assert_nothing_raised do
func = Puppet::Parser::AST::Function.new(
:name => "template",
:ftype => :rvalue,
:arguments => AST::ASTArray.new(
:children => [stringobj(template)]
)
)
end
ast = varobj("output", func)
# Verify that Kernel methods still shadow deprecated variable lookups.
scope = mkscope
assert_nothing_raised("No exception for Kernel shadowed variable names") do
ast.evaluate(scope)
end
end
def test_tempatefunction_cannot_see_scopes
template = tempfile()
File.open(template, "w") do |f|
f.puts "<%= lookupvar('myvar') %>"
end
func = nil
assert_nothing_raised do
func = Puppet::Parser::AST::Function.new(
:name => "template",
:ftype => :rvalue,
:arguments => AST::ASTArray.new(
:children => [stringobj(template)]
)
)
end
ast = varobj("output", func)
scope = mkscope
scope.setvar("myvar", "this is yayness")
assert_raise(Puppet::ParseError) do
ast.evaluate(scope)
end
end
def test_template_reparses
template = tempfile()
File.open(template, "w") do |f|
f.puts "original text"
end
file = tempfile()
Puppet[:code] = %{file { "#{file}": content => template("#{template}") }}
Puppet[:environment] = "yay"
- interp = Puppet::Parser::Interpreter.new
node = mknode
- node.stubs(:environment).returns("yay")
+ node.stubs(:environment).returns Puppet::Node::Environment.new
Puppet[:environment] = "yay"
- catalog = nil
- assert_nothing_raised {
- catalog = interp.compile(node)
- }
+ catalog = Puppet::Parser::Compiler.new(node).compile
version = catalog.version
fileobj = catalog.vertices.find { |r| r.title == file }
assert(fileobj, "File was not in catalog")
assert_equal("original text\n", fileobj["content"],
"Template did not work")
Puppet[:filetimeout] = -5
# Have to sleep because one second is the fs's time granularity.
sleep(1)
# Now modify the template
File.open(template, "w") do |f|
f.puts "new text"
end
- newversion = interp.compile(node).version
+ newversion = Puppet::Parser::Compiler.new(node).compile.version
assert(version != newversion, "Parse date did not change")
end
def test_template_defined_vars
template = tempfile()
File.open(template, "w") do |f|
f.puts "template <%= @yayness %>"
end
func = nil
assert_nothing_raised do
func = Puppet::Parser::AST::Function.new(
:name => "template",
:ftype => :rvalue,
:arguments => AST::ASTArray.new(
:children => [stringobj(template)]
)
)
end
ast = varobj("output", func)
{
"" => "",
false => "false",
}.each do |string, value|
scope = mkscope
scope.setvar("yayness", string)
assert_equal(string, scope.lookupvar("yayness", false))
assert_nothing_raised("An empty string was not a valid variable value") do
ast.evaluate(scope)
end
assert_equal("template #{value}\n", scope.lookupvar("output"),
"%s did not get evaluated correctly" % string.inspect)
end
end
def test_autoloading_functions
#assert_equal(false, Puppet::Parser::Functions.function(:autofunc),
# "Got told autofunc already exists")
dir = tempfile()
$: << dir
newpath = File.join(dir, "puppet", "parser", "functions")
FileUtils.mkdir_p(newpath)
File.open(File.join(newpath, "autofunc.rb"), "w") { |f|
f.puts %{
Puppet::Parser::Functions.newfunction(:autofunc, :type => :rvalue) do |vals|
Puppet.wanring vals.inspect
end
}
}
obj = nil
assert_nothing_raised {
obj = Puppet::Parser::Functions.function(:autofunc)
}
assert(obj, "Did not autoload function")
assert(Puppet::Parser::Scope.method_defined?(:function_autofunc),
"Did not set function correctly")
end
- def test_realize
- scope = mkscope
- parser = scope.compiler.parser
-
- realize = Puppet::Parser::Functions.function(:realize)
-
- # Make a definition
- parser.newdefine("mytype")
-
- [%w{file /tmp/virtual}, %w{mytype yay}].each do |type, title|
- # Make a virtual resource
- virtual = mkresource(:type => type, :title => title,
- :virtual => true, :params => {}, :scope => scope)
-
- scope.compiler.add_resource(scope, virtual)
-
- ref = Puppet::Parser::Resource::Reference.new(
- :type => type, :title => title,
- :scope => scope
- )
- # Now call the realize function
- assert_nothing_raised do
- scope.function_realize(ref)
- end
-
- # Make sure it created a collection
- assert_equal(1, scope.compiler.collections.length,
- "Did not set collection")
-
- assert_nothing_raised do
- scope.compiler.collections.each do |coll| coll.evaluate end
- end
- scope.compiler.collections.clear
-
- # Now make sure the virtual resource is no longer virtual
- assert(! virtual.virtual?, "Did not make virtual resource real")
- end
-
- # Make sure we puke on any resource that doesn't exist
- none = Puppet::Parser::Resource::Reference.new(
- :type => "file", :title => "/tmp/nosuchfile",
- :scope => scope
- )
-
- # The function works
- assert_nothing_raised do
- scope.function_realize(none.to_s)
- end
-
- # Make sure it created a collection
- assert_equal(1, scope.compiler.collections.length,
- "Did not set collection")
-
- # And the collection has our resource in it
- assert_equal([none.to_s], scope.compiler.collections[0].resources,
- "Did not set resources in collection")
- end
-
- def test_defined
- scope = mkscope
- parser = scope.compiler.parser
-
- defined = Puppet::Parser::Functions.function(:defined)
-
- parser.newclass("yayness")
- parser.newdefine("rahness")
-
- assert_nothing_raised do
- assert(scope.function_defined("yayness"), "yayness class was not considered defined")
- assert(scope.function_defined("rahness"), "rahness definition was not considered defined")
- assert(scope.function_defined("service"), "service type was not considered defined")
- assert(! scope.function_defined("fakness"), "fakeness was considered defined")
- end
-
- # Now make sure any match in a list will work
- assert(scope.function_defined(["booness", "yayness", "fakeness"]),
- "A single answer was not sufficient to return true")
-
- # and make sure multiple falses are still false
- assert(! scope.function_defined(%w{no otherno stillno}),
- "Multiple falses were somehow true")
-
- # Now make sure we can test resources
- scope.compiler.add_resource(scope, mkresource(:type => "file", :title => "/tmp/rahness",
- :scope => scope, :source => scope.source,
- :params => {:owner => "root"}))
-
- yep = Puppet::Parser::Resource::Reference.new(:type => "file", :title => "/tmp/rahness")
- nope = Puppet::Parser::Resource::Reference.new(:type => "file", :title => "/tmp/fooness")
-
- assert(scope.function_defined([yep]), "valid resource was not considered defined")
- assert(! scope.function_defined([nope]), "invalid resource was considered defined")
- end
-
def test_search
parser = mkparser
scope = mkscope(:parser => parser)
fun = parser.newdefine("yay::ness")
foo = parser.newdefine("foo::bar")
search = Puppet::Parser::Functions.function(:search)
assert_nothing_raised do
scope.function_search(["foo", "yay"])
end
ffun = ffoo = nil
assert_nothing_raised("Search path change did not work") do
ffun = scope.find_definition("ness")
ffoo = scope.find_definition('bar')
end
assert(ffun, "Could not find definition in 'fun' namespace")
assert(ffoo, "Could not find definition in 'foo' namespace")
end
def test_include
scope = mkscope
- parser = scope.compiler.parser
+ parser = mkparser
include = Puppet::Parser::Functions.function(:include)
assert_raise(Puppet::ParseError, "did not throw error on missing class") do
scope.function_include("nosuchclass")
end
parser.newclass("myclass")
scope.compiler.expects(:evaluate_classes).with(%w{myclass otherclass}, scope, false).returns(%w{myclass otherclass})
assert_nothing_raised do
scope.function_include(["myclass", "otherclass"])
end
end
def test_file
parser = mkparser
scope = mkscope(:parser => parser)
file = Puppet::Parser::Functions.function(:file)
file1 = tempfile
file2 = tempfile
file3 = tempfile
File.open(file2, "w") { |f| f.puts "yaytest" }
val = nil
assert_nothing_raised("Failed to call file with one arg") do
val = scope.function_file([file2])
end
assert_equal("yaytest\n", val, "file() failed")
assert_nothing_raised("Failed to call file with two args") do
val = scope.function_file([file1, file2])
end
assert_equal("yaytest\n", val, "file() failed")
assert_raise(Puppet::ParseError, "did not fail when files are missing") do
val = scope.function_file([file1, file3])
end
end
def test_generate
command = tempfile
sh = %x{which sh}
File.open(command, "w") do |f|
f.puts %{#!#{sh}
if [ -n "$1" ]; then
echo "yay-$1"
else
echo yay
fi
}
end
File.chmod(0755, command)
assert_equal("yay\n", %x{#{command}}, "command did not work")
assert_equal("yay-foo\n", %x{#{command} foo}, "command did not work")
generate = Puppet::Parser::Functions.function(:generate)
scope = mkscope
- parser = scope.compiler.parser
+ parser = mkparser
val = nil
assert_nothing_raised("Could not call generator with no args") do
val = scope.function_generate([command])
end
assert_equal("yay\n", val, "generator returned wrong results")
assert_nothing_raised("Could not call generator with args") do
val = scope.function_generate([command, "foo"])
end
assert_equal("yay-foo\n", val, "generator returned wrong results")
assert_raise(Puppet::ParseError, "Did not fail with an unqualified path") do
val = scope.function_generate([File.basename(command), "foo"])
end
assert_raise(Puppet::ParseError, "Did not fail when command failed") do
val = scope.function_generate([%x{which touch}.chomp, "/this/dir/does/not/exist"])
end
fake = File.join(File.dirname(command), "..")
dir = File.dirname(command)
dirname = File.basename(dir)
bad = File.join(dir, "..", dirname, File.basename(command))
assert_raise(Puppet::ParseError, "Did not fail when command failed") do
val = scope.function_generate([bad])
end
end
end
diff --git a/test/language/parser.rb b/test/language/parser.rb
index a3311f423..b721490c3 100755
--- a/test/language/parser.rb
+++ b/test/language/parser.rb
@@ -1,1215 +1,946 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../lib/puppettest'
require 'mocha'
require 'puppet'
require 'puppet/parser/parser'
require 'puppettest'
require 'puppettest/support/utils'
class TestParser < Test::Unit::TestCase
include PuppetTest
include PuppetTest::ParserTesting
include PuppetTest::Support::Utils
def setup
super
Puppet[:parseonly] = true
#@lexer = Puppet::Parser::Lexer.new()
end
+ def teardown
+ super
+ Puppet::Node::Environment.clear
+ end
+
def test_each_file
textfiles { |file|
+ Puppet::Node::Environment.clear
parser = mkparser
Puppet.debug("parsing %s" % file) if __FILE__ == $0
assert_nothing_raised() {
parser.file = file
parser.parse
}
}
end
def test_failers
failers { |file|
parser = mkparser
Puppet.debug("parsing failer %s" % file) if __FILE__ == $0
assert_raise(Puppet::ParseError, "Did not fail while parsing %s" % file) {
parser.file = file
ast = parser.parse
config = mkcompiler(parser)
config.compile
#ast.hostclass("").evaluate config.topscope
}
}
end
def test_arrayrvalues
parser = mkparser
ret = nil
file = tempfile()
assert_nothing_raised {
parser.string = "file { \"#{file}\": mode => [755, 640] }"
}
assert_nothing_raised {
ret = parser.parse
}
end
def test_arrayrvalueswithtrailingcomma
parser = mkparser
ret = nil
file = tempfile()
assert_nothing_raised {
parser.string = "file { \"#{file}\": mode => [755, 640,] }"
}
assert_nothing_raised {
ret = parser.parse
}
end
def mkmanifest(file)
name = File.join(tmpdir, "file%s" % rand(100))
@@tmpfiles << name
File.open(file, "w") { |f|
f.puts "file { \"%s\": ensure => file, mode => 755 }\n" %
name
}
end
def test_importglobbing
basedir = File.join(tmpdir(), "importesting")
@@tmpfiles << basedir
Dir.mkdir(basedir)
subdir = "subdir"
Dir.mkdir(File.join(basedir, subdir))
manifest = File.join(basedir, "manifest")
File.open(manifest, "w") { |f|
f.puts "import \"%s/*\"" % subdir
}
4.times { |i|
path = File.join(basedir, subdir, "subfile%s" % i)
mkmanifest(path)
}
assert_nothing_raised("Could not parse multiple files") {
parser = mkparser
parser.file = manifest
parser.parse
}
end
def test_nonexistent_import
basedir = File.join(tmpdir(), "importesting")
@@tmpfiles << basedir
Dir.mkdir(basedir)
manifest = File.join(basedir, "manifest")
File.open(manifest, "w") do |f|
f.puts "import \" no such file \""
end
assert_raise(Puppet::ParseError) {
parser = mkparser
parser.file = manifest
parser.parse
}
end
def test_trailingcomma
path = tempfile()
str = %{file { "#{path}": ensure => file, }
}
parser = mkparser
parser.string = str
assert_nothing_raised("Could not parse trailing comma") {
parser.parse
}
end
def test_importedclasses
imported = tempfile()
importer = tempfile()
made = tempfile()
File.open(imported, "w") do |f|
f.puts %{class foo { file { "#{made}": ensure => file }}}
end
File.open(importer, "w") do |f|
f.puts %{import "#{imported}"\ninclude foo}
end
parser = mkparser
parser.file = importer
# Make sure it parses fine
assert_nothing_raised {
parser.parse
}
# Now make sure it actually does the work
assert_creates(importer, made)
end
# Make sure fully qualified and unqualified files can be imported
def test_fqfilesandlocalfiles
dir = tempfile()
Dir.mkdir(dir)
importer = File.join(dir, "site.pp")
fullfile = File.join(dir, "full.pp")
localfile = File.join(dir, "local.pp")
files = []
File.open(importer, "w") do |f|
f.puts %{import "#{fullfile}"\ninclude full\nimport "local.pp"\ninclude local}
end
fullmaker = tempfile()
files << fullmaker
File.open(fullfile, "w") do |f|
f.puts %{class full { file { "#{fullmaker}": ensure => file }}}
end
localmaker = tempfile()
files << localmaker
File.open(localfile, "w") do |f|
f.puts %{class local { file { "#{localmaker}": ensure => file }}}
end
parser = mkparser
parser.file = importer
# Make sure it parses
assert_nothing_raised {
parser.parse
}
# Now make sure it actually does the work
assert_creates(importer, *files)
end
# Make sure the parser adds '.pp' when necessary
def test_addingpp
dir = tempfile()
Dir.mkdir(dir)
importer = File.join(dir, "site.pp")
localfile = File.join(dir, "local.pp")
files = []
File.open(importer, "w") do |f|
f.puts %{import "local"\ninclude local}
end
file = tempfile()
files << file
File.open(localfile, "w") do |f|
f.puts %{class local { file { "#{file}": ensure => file }}}
end
parser = mkparser
parser.file = importer
assert_nothing_raised {
parser.parse
}
end
# Make sure that file importing changes file relative names.
def test_changingrelativenames
dir = tempfile()
Dir.mkdir(dir)
Dir.mkdir(File.join(dir, "subdir"))
top = File.join(dir, "site.pp")
subone = File.join(dir, "subdir/subone")
subtwo = File.join(dir, "subdir/subtwo")
files = []
file = tempfile()
files << file
File.open(subone + ".pp", "w") do |f|
f.puts %{class one { file { "#{file}": ensure => file }}}
end
otherfile = tempfile()
files << otherfile
File.open(subtwo + ".pp", "w") do |f|
f.puts %{import "subone"\n class two inherits one {
file { "#{otherfile}": ensure => file }
}}
end
File.open(top, "w") do |f|
f.puts %{import "subdir/subtwo"}
end
parser = mkparser
parser.file = top
assert_nothing_raised {
parser.parse
}
end
# Defaults are purely syntactical, so it doesn't make sense to be able to
# collect them.
def test_uncollectabledefaults
string = "@Port { protocols => tcp }"
assert_raise(Puppet::ParseError) {
mkparser.parse(string)
}
end
# Verify that we can parse collections
def test_collecting
text = "Port <| |>"
parser = mkparser
parser.string = text
ret = nil
assert_nothing_raised {
ret = parser.parse
}
ret.hostclass("").code.each do |obj|
assert_instance_of(AST::Collection, obj)
end
end
def test_emptyfile
file = tempfile()
File.open(file, "w") do |f|
f.puts %{}
end
parser = mkparser
parser.file = file
assert_nothing_raised {
parser.parse
}
end
def test_multiple_nodes_named
file = tempfile()
other = tempfile()
File.open(file, "w") do |f|
f.puts %{
node nodeA, nodeB {
file { "#{other}": ensure => file }
}
}
end
parser = mkparser
parser.file = file
ast = nil
assert_nothing_raised {
ast = parser.parse
}
end
def test_emptyarrays
str = %{$var = []\n}
parser = mkparser
parser.string = str
# Make sure it parses fine
assert_nothing_raised {
parser.parse
}
end
# Make sure function names aren't reserved words.
def test_functionnamecollision
str = %{tag yayness
tag(rahness)
file { "/tmp/yayness":
tag => "rahness",
ensure => exists
}
}
parser = mkparser
parser.string = str
# Make sure it parses fine
assert_nothing_raised {
parser.parse
}
end
def test_metaparams_in_definition_prototypes
parser = mkparser
assert_raise(Puppet::ParseError) {
parser.parse %{define mydef($schedule) {}}
}
assert_nothing_raised {
parser.parse %{define adef($schedule = false) {}}
parser.parse %{define mydef($schedule = daily) {}}
}
end
def test_parsingif
parser = mkparser
exec = proc do |val|
%{exec { "/bin/echo #{val}": logoutput => true }}
end
str1 = %{if true { #{exec.call("true")} }}
ret = nil
assert_nothing_raised {
ret = parser.parse(str1).hostclass("").code[0]
}
assert_instance_of(Puppet::Parser::AST::IfStatement, ret)
parser = mkparser
str2 = %{if true { #{exec.call("true")} } else { #{exec.call("false")} }}
- assert_nothing_raised {
- ret = parser.parse(str2).hostclass("").code[0]
- }
+ ret = parser.parse(str2).hostclass("").code[0]
assert_instance_of(Puppet::Parser::AST::IfStatement, ret)
assert_instance_of(Puppet::Parser::AST::Else, ret.else)
end
def test_hostclass
parser = mkparser
assert_nothing_raised {
parser.parse %{class myclass { class other {} }}
}
assert(parser.hostclass("myclass"), "Could not find myclass")
assert(parser.hostclass("myclass::other"), "Could not find myclass::other")
assert_nothing_raised {
parser.parse "class base {}
class container {
class deep::sub inherits base {}
}"
}
sub = parser.hostclass("container::deep::sub")
assert(sub, "Could not find sub")
# Now try it with a parent class being a fq class
assert_nothing_raised {
parser.parse "class container::one inherits container::deep::sub {}"
}
sub = parser.hostclass("container::one")
assert(sub, "Could not find one")
- assert_equal("container::deep::sub", sub.parentclass)
+ assert_equal("container::deep::sub", sub.parent)
# Finally, try including a qualified class
assert_nothing_raised("Could not include fully qualified class") {
parser.parse "include container::deep::sub"
}
end
def test_topnamespace
parser = mkparser
# Make sure we put the top-level code into a class called "" in
# the "" namespace
assert_nothing_raised do
out = parser.parse ""
assert_instance_of(Puppet::Resource::TypeCollection, out)
assert_nil(parser.hostclass(""), "Got a 'main' class when we had no code")
end
# Now try something a touch more complicated
parser.initvars
assert_nothing_raised do
out = parser.parse "Exec { path => '/usr/bin:/usr/sbin' }"
assert_instance_of(Puppet::Resource::TypeCollection, out)
- assert_equal("", parser.hostclass("").classname)
+ assert_equal("", parser.hostclass("").name)
assert_equal("", parser.hostclass("").namespace)
end
end
# Make sure virtual and exported resources work appropriately.
def test_virtualresources
tests = [:virtual]
if Puppet.features.rails?
catalog_cache_class = Puppet::Resource::Catalog.indirection.cache_class
facts_cache_class = Puppet::Node::Facts.indirection.cache_class
node_cache_class = Puppet::Node.indirection.cache_class
Puppet[:storeconfigs] = true
tests << :exported
end
tests.each do |form|
parser = mkparser
if form == :virtual
at = "@"
else
at = "@@"
end
check = proc do |res, msg|
if res.is_a?(Puppet::Parser::Resource)
txt = res.ref
else
txt = res.class
end
# Real resources get marked virtual when exported
if form == :virtual or res.is_a?(Puppet::Parser::Resource)
assert(res.virtual, "#{msg} #{at}#{txt} is not virtual")
end
if form == :virtual
assert(! res.exported, "#{msg} #{at}#{txt} is exported")
else
assert(res.exported, "#{msg} #{at}#{txt} is not exported")
end
end
ret = nil
assert_nothing_raised do
ret = parser.parse("#{at}file { '/tmp/testing': owner => root }")
end
assert_instance_of(AST::ASTArray, ret.hostclass("").code)
resdef = ret.hostclass("").code[0]
assert_instance_of(AST::Resource, resdef)
assert_equal("/tmp/testing", resdef.title.value)
# We always get an astarray back, so...
check.call(resdef, "simple resource")
# Now let's try it with multiple resources in the same spec
assert_nothing_raised do
ret = parser.parse("#{at}file { ['/tmp/1', '/tmp/2']: owner => root }")
end
ret.hostclass("").each do |res|
assert_instance_of(AST::Resource, res)
check.call(res, "multiresource")
end
end
ensure
if Puppet.features.rails?
Puppet[:storeconfigs] = false
Puppet::Resource::Catalog.cache_class = catalog_cache_class
Puppet::Node::Facts.cache_class = facts_cache_class
Puppet::Node.cache_class = node_cache_class
end
end
def test_collections
tests = [:virtual]
if Puppet.features.rails?
catalog_cache_class = Puppet::Resource::Catalog.indirection.cache_class
facts_cache_class = Puppet::Node::Facts.indirection.cache_class
node_cache_class = Puppet::Node.indirection.cache_class
Puppet[:storeconfigs] = true
tests << :exported
end
tests.each do |form|
+ Puppet::Node::Environment.clear
parser = mkparser
if form == :virtual
arrow = "<||>"
else
arrow = "<<||>>"
end
ret = nil
assert_nothing_raised do
ret = parser.parse("File #{arrow}")
end
coll = ret.hostclass("").code[0]
assert_instance_of(AST::Collection, coll)
assert_equal(form, coll.form)
end
ensure
if Puppet.features.rails?
Puppet[:storeconfigs] = false
Puppet::Resource::Catalog.cache_class = catalog_cache_class
Puppet::Node::Facts.cache_class = facts_cache_class
Puppet::Node.cache_class = node_cache_class
end
end
def test_collectionexpressions
%w{== !=}.each do |oper|
+ Puppet::Node::Environment.clear
str = "File <| title #{oper} '/tmp/testing' |>"
parser = mkparser
res = nil
assert_nothing_raised do
res = parser.parse(str).hostclass("").code[0]
end
assert_instance_of(AST::Collection, res)
query = res.query
assert_instance_of(AST::CollExpr, query)
assert_equal(:virtual, query.form)
assert_equal("title", query.test1.value)
assert_equal("/tmp/testing", query.test2.value)
assert_equal(oper, query.oper)
end
end
def test_collectionstatements
%w{and or}.each do |joiner|
str = "File <| title == '/tmp/testing' #{joiner} owner == root |>"
parser = mkparser
res = nil
assert_nothing_raised do
res = parser.parse(str).hostclass("").code[0]
end
assert_instance_of(AST::Collection, res)
query = res.query
assert_instance_of(AST::CollExpr, query)
assert_equal(joiner, query.oper)
assert_instance_of(AST::CollExpr, query.test1)
assert_instance_of(AST::CollExpr, query.test2)
end
end
def test_collectionstatements_with_parens
[
"(title == '/tmp/testing' and owner == root) or owner == wheel",
"(title == '/tmp/testing')"
].each do |test|
str = "File <| #{test} |>"
parser = mkparser
res = nil
assert_nothing_raised("Could not parse '#{test}'") do
res = parser.parse(str).hostclass("").code[0]
end
assert_instance_of(AST::Collection, res)
query = res.query
assert_instance_of(AST::CollExpr, query)
#assert_equal(joiner, query.oper)
#assert_instance_of(AST::CollExpr, query.test1)
#assert_instance_of(AST::CollExpr, query.test2)
end
end
- # We've had problems with files other than site.pp importing into main.
- def test_importing_into_main
- top = tempfile()
- other = tempfile()
- File.open(top, "w") do |f|
- f.puts "import '#{other}'"
- end
-
- file = tempfile()
- File.open(other, "w") do |f|
- f.puts "file { '#{file}': ensure => present }"
- end
-
- Puppet[:manifest] = top
- interp = Puppet::Parser::Interpreter.new
-
- code = nil
- assert_nothing_raised do
- code = interp.compile(mknode).extract.flatten
- end
- assert(code.length == 1, "Did not get the file")
- assert_instance_of(Puppet::TransObject, code[0])
- end
-
def test_fully_qualified_definitions
parser = mkparser
assert_nothing_raised("Could not parse fully-qualified definition") {
parser.parse %{define one::two { }}
}
assert(parser.definition("one::two"), "Could not find one::two with no namespace")
# Now try using the definition
assert_nothing_raised("Could not parse fully-qualified definition usage") {
parser.parse %{one::two { yayness: }}
}
end
# #524
def test_functions_with_no_arguments
parser = mkparser
assert_nothing_raised("Could not parse statement function with no args") {
parser.parse %{tag()}
}
assert_nothing_raised("Could not parse rvalue function with no args") {
parser.parse %{$testing = template()}
}
end
# #774
def test_fully_qualified_collection_statement
parser = mkparser
assert_nothing_raised("Could not parse fully qualified collection statement") {
parser.parse %{Foo::Bar <||>}
}
end
def test_module_import
basedir = File.join(tmpdir(), "module-import")
@@tmpfiles << basedir
Dir.mkdir(basedir)
modfiles = [ "init.pp", "mani1.pp", "mani2.pp",
"sub/smani1.pp", "sub/smani2.pp" ]
modpath = File.join(basedir, "modules")
Puppet[:modulepath] = modpath
modname = "amod"
manipath = File::join(modpath, modname, Puppet::Module::MANIFESTS)
FileUtils::mkdir_p(File::join(manipath, "sub"))
targets = []
modfiles.each do |fname|
target = File::join(basedir, File::basename(fname, '.pp'))
targets << target
txt = %[ file { '#{target}': content => "#{fname}" } ]
if fname == "init.pp"
txt = %[import 'mani1' \nimport '#{modname}/mani2'\nimport '#{modname}/sub/*.pp'\n ] + txt
end
File::open(File::join(manipath, fname), "w") do |f|
f.puts txt
end
end
manifest_texts = [ "import '#{modname}'",
"import '#{modname}/init'",
"import '#{modname}/init.pp'" ]
manifest = File.join(modpath, "manifest.pp")
manifest_texts.each do |txt|
File.open(manifest, "w") { |f| f.puts txt }
assert_nothing_raised {
parser = mkparser
parser.file = manifest
parser.parse
}
assert_creates(manifest, *targets)
end
end
# #544
def test_ignoreimports
parser = mkparser
assert(! Puppet[:ignoreimport], ":ignoreimport defaulted to true")
assert_raise(Puppet::ParseError, "Did not fail on missing import") do
parser.parse("import 'nosuchfile'")
end
assert_nothing_raised("could not set :ignoreimport") do
Puppet[:ignoreimport] = true
end
assert_nothing_raised("Parser did not follow :ignoreimports") do
parser.parse("import 'nosuchfile'")
end
end
def test_multiple_imports_on_one_line
one = tempfile
two = tempfile
base = tempfile
File.open(one, "w") { |f| f.puts "$var = value" }
File.open(two, "w") { |f| f.puts "$var = value" }
File.open(base, "w") { |f| f.puts "import '#{one}', '#{two}'" }
parser = mkparser
parser.file = base
# Importing is logged at debug time.
Puppet::Util::Log.level = :debug
assert_nothing_raised("Parser could not import multiple files at once") do
parser.parse
end
[one, two].each do |file|
assert(@logs.detect { |l| l.message =~ /importing '#{file}'/},
"did not import %s" % file)
end
end
def test_cannot_assign_qualified_variables
parser = mkparser
assert_raise(Puppet::ParseError, "successfully assigned a qualified variable") do
parser.parse("$one::two = yay")
end
end
# #588
def test_globbing_with_directories
dir = tempfile
Dir.mkdir(dir)
subdir = File.join(dir, "subdir")
Dir.mkdir(subdir)
file = File.join(dir, "file.pp")
maker = tempfile
File.open(file, "w") { |f| f.puts "file { '#{maker}': ensure => file }" }
parser = mkparser
assert_nothing_raised("Globbing failed when it matched a directory") do
parser.import("%s/*" % dir)
end
end
# #629 - undef keyword
def test_undef
parser = mkparser
result = nil
assert_nothing_raised("Could not parse assignment to undef") {
result = parser.parse %{$variable = undef}
}
main = result.hostclass("").code
children = main.children
assert_instance_of(AST::VarDef, main.children[0])
assert_instance_of(AST::Undef, main.children[0].value)
end
# Prompted by #729 -- parsing should not modify the interpreter.
def test_parse
parser = mkparser
str = "file { '/tmp/yay': ensure => file }\nclass yay {}\nnode foo {}\ndefine bar {}\n"
result = nil
assert_nothing_raised("Could not parse") do
result = parser.parse(str)
end
assert_instance_of(Puppet::Resource::TypeCollection, result, "Did not get a ASTSet back from parsing")
- assert_instance_of(AST::HostClass, result.hostclass("yay"), "Did not create 'yay' class")
- assert_instance_of(AST::HostClass, result.hostclass(""), "Did not create main class")
- assert_instance_of(AST::Definition, result.definition("bar"), "Did not create 'bar' definition")
- assert_instance_of(AST::Node, result.node("foo"), "Did not create 'foo' node")
- end
-
- # Make sure our node gets added to the node table.
- def test_newnode
- parser = mkparser
-
- # First just try calling it directly
- assert_nothing_raised {
- parser.newnode("mynode", :code => :yay)
- }
-
- assert_equal(:yay, parser.node("mynode").code)
-
- # Now make sure that trying to redefine it throws an error.
- assert_raise(Puppet::ParseError) {
- parser.newnode("mynode", {})
- }
-
- # Now try one with no code
- assert_nothing_raised {
- parser.newnode("simplenode", :parent => :foo)
- }
-
- # Now define the parent node
- parser.newnode(:foo)
-
- # And make sure we get things back correctly
- assert_equal(:foo, parser.node("simplenode").parentclass)
- assert_nil(parser.node("simplenode").code)
-
- # Now make sure that trying to redefine it throws an error.
- assert_raise(Puppet::ParseError) {
- parser.newnode("mynode", {})
- }
-
- # Test multiple names
- names = ["one", "two", "three"]
- assert_nothing_raised {
- parser.newnode(names, {:code => :yay, :parent => :foo})
- }
-
- names.each do |name|
- assert_equal(:yay, parser.node(name).code)
- assert_equal(:foo, parser.node(name).parentclass)
- # Now make sure that trying to redefine it throws an error.
- assert_raise(Puppet::ParseError) {
- parser.newnode(name, {})
- }
- end
- end
-
- def test_newdefine
- parser = mkparser
-
- assert_nothing_raised {
- parser.newdefine("mydefine", :code => :yay,
- :arguments => ["a", stringobj("b")])
- }
-
- mydefine = parser.definition("mydefine")
- assert(mydefine, "Could not find definition")
- assert_equal("", mydefine.namespace)
- assert_equal("mydefine", mydefine.classname)
-
- assert_raise(Puppet::ParseError) do
- parser.newdefine("mydefine", :code => :yay,
- :arguments => ["a", stringobj("b")])
- end
-
- # Now define the same thing in a different scope
- assert_nothing_raised {
- parser.newdefine("other::mydefine", :code => :other,
- :arguments => ["a", stringobj("b")])
- }
- other = parser.definition("other::mydefine")
- assert(other, "Could not find definition")
- assert(parser.definition("other::mydefine"),
- "Could not find other::mydefine")
- assert_equal(:other, other.code)
- assert_equal("other", other.namespace)
- assert_equal("other::mydefine", other.classname)
- end
-
- def test_newclass
- scope = mkscope
- parser = scope.compiler.parser
-
- mkcode = proc do |ary|
- classes = ary.collect do |string|
- AST::FlatString.new(:value => string)
- end
- AST::ASTArray.new(:children => classes)
- end
-
-
- # First make sure that code is being appended
- code = mkcode.call(%w{original code})
-
- klass = nil
- assert_nothing_raised {
- klass = parser.newclass("myclass", :code => code)
- }
-
- assert(klass, "Did not return class")
-
- assert(parser.hostclass("myclass"), "Could not find definition")
- assert_equal("myclass", parser.hostclass("myclass").classname)
- assert_equal(%w{original code},
- parser.hostclass("myclass").code.evaluate(scope))
-
- # Newclass behaves differently than the others -- it just appends
- # the code to the existing class.
- code = mkcode.call(%w{something new})
- assert_nothing_raised do
- klass = parser.newclass("myclass", :code => code)
- end
- assert(klass, "Did not return class when appending")
- assert_equal(%w{original code something new},
- parser.hostclass("myclass").code.evaluate(scope))
-
- # Now create the same class name in a different scope
- assert_nothing_raised {
- klass = parser.newclass("other::myclass",
- :code => mkcode.call(%w{something diff}))
- }
- assert(klass, "Did not return class")
- other = parser.hostclass("other::myclass")
- assert(other, "Could not find class")
- assert_equal("other::myclass", other.classname)
- assert_equal("other::myclass", other.namespace)
- assert_equal(%w{something diff},
- other.code.evaluate(scope))
-
- # Make sure newclass deals correctly with nodes with no code
- klass = parser.newclass("nocode")
- assert(klass, "Did not return class")
-
- assert_nothing_raised do
- klass = parser.newclass("nocode", :code => mkcode.call(%w{yay test}))
- end
- assert(klass, "Did not return class with no code")
- assert_equal(%w{yay test},
- parser.hostclass("nocode").code.evaluate(scope))
-
- # Then try merging something into nothing
- parser.newclass("nocode2", :code => mkcode.call(%w{foo test}))
- assert(klass, "Did not return class with no code")
-
- assert_nothing_raised do
- klass = parser.newclass("nocode2")
- end
- assert(klass, "Did not return class with no code")
- assert_equal(%w{foo test},
- parser.hostclass("nocode2").code.evaluate(scope))
-
- # And lastly, nothing and nothing
- klass = parser.newclass("nocode3")
- assert(klass, "Did not return class with no code")
-
- assert_nothing_raised do
- klass = parser.newclass("nocode3")
- end
- assert(klass, "Did not return class with no code")
- assert_nil(parser.hostclass("nocode3").code)
- end
-
- # Make sure you can't have classes and defines with the same name in the
- # same scope.
- def test_classes_beat_defines
- parser = mkparser
-
- assert_nothing_raised {
- parser.newclass("yay::funtest")
- }
-
- assert_raise(Puppet::ParseError) do
- parser.newdefine("yay::funtest")
- end
-
- assert_nothing_raised {
- parser.newdefine("yay::yaytest")
- }
-
- assert_raise(Puppet::ParseError) do
- parser.newclass("yay::yaytest")
- end
+ assert_instance_of(Puppet::Resource::Type, result.hostclass("yay"), "Did not create 'yay' class")
+ assert_instance_of(Puppet::Resource::Type, result.hostclass(""), "Did not create main class")
+ assert_instance_of(Puppet::Resource::Type, result.definition("bar"), "Did not create 'bar' definition")
+ assert_instance_of(Puppet::Resource::Type, result.node("foo"), "Did not create 'foo' node")
end
def test_namesplit
parser = mkparser
assert_nothing_raised do
{"base::sub" => %w{base sub},
"main" => ["", "main"],
"one::two::three::four" => ["one::two::three", "four"],
}.each do |name, ary|
result = parser.namesplit(name)
assert_equal(ary, result, "%s split to %s" % [name, result])
end
end
end
- # Now make sure we get appropriate behaviour with parent class conflicts.
- def test_newclass_parentage
- parser = mkparser
- parser.newclass("base1")
- parser.newclass("one::two::three")
-
- # First create it with no parentclass.
- assert_nothing_raised {
- parser.newclass("sub")
- }
- assert(parser.hostclass("sub"), "Could not find definition")
- assert_nil(parser.hostclass("sub").parentclass)
-
- # Make sure we can't set the parent class to ourself.
- assert_raise(Puppet::ParseError) {
- parser.newclass("sub", :parent => "sub")
- }
-
- # Now create another one, with a parentclass.
- assert_nothing_raised {
- parser.newclass("sub", :parent => "base1")
- }
-
- # Make sure we get the right parent class, and make sure it's not an object.
- assert_equal("base1",
- parser.hostclass("sub").parentclass)
-
- # Now make sure we get a failure if we try to conflict.
- assert_raise(Puppet::ParseError) {
- parser.newclass("sub", :parent => "one::two::three")
- }
-
- # Make sure that failure didn't screw us up in any way.
- assert_equal("base1",
- parser.hostclass("sub").parentclass)
- # But make sure we can create a class with a fq parent
- assert_nothing_raised {
- parser.newclass("another", :parent => "one::two::three")
- }
- assert_equal("one::two::three",
- parser.hostclass("another").parentclass)
-
- end
-
# Setup a module.
def mk_module(name, files = {})
mdir = File.join(@dir, name)
mandir = File.join(mdir, "manifests")
FileUtils.mkdir_p mandir
if defs = files[:define]
files.delete(:define)
end
Dir.chdir(mandir) do
files.each do |file, classes|
File.open("%s.pp" % file, "w") do |f|
classes.each { |klass|
if defs
f.puts "define %s {}" % klass
else
f.puts "class %s {}" % klass
end
}
end
end
end
end
# #596 - make sure classes and definitions load automatically if they're in modules, so we don't have to manually load each one.
def test_module_autoloading
@dir = tempfile
Puppet[:modulepath] = @dir
FileUtils.mkdir_p @dir
parser = mkparser
# Make sure we fail like normal for actually missing classes
assert_nil(parser.find_hostclass("", "nosuchclass"), "Did not return nil on missing classes")
# test the simple case -- the module class itself
name = "simple"
mk_module(name, :init => [name])
# Try to load the module automatically now
klass = parser.find_hostclass("", name)
- assert_instance_of(AST::HostClass, klass, "Did not autoload class from module init file")
- assert_equal(name, klass.classname, "Incorrect class was returned")
+ assert_instance_of(Puppet::Resource::Type, klass, "Did not autoload class from module init file")
+ assert_equal(name, klass.name, "Incorrect class was returned")
# Try loading the simple module when we're in something other than the base namespace.
parser = mkparser
klass = parser.find_hostclass("something::else", name)
- assert_instance_of(AST::HostClass, klass, "Did not autoload class from module init file")
- assert_equal(name, klass.classname, "Incorrect class was returned")
+ assert_instance_of(Puppet::Resource::Type, klass, "Did not autoload class from module init file")
+ assert_equal(name, klass.name, "Incorrect class was returned")
# Now try it with a definition as the base file
name = "simpdef"
mk_module(name, :define => true, :init => [name])
klass = parser.find_definition("", name)
- assert_instance_of(AST::Definition, klass, "Did not autoload class from module init file")
- assert_equal(name, klass.classname, "Incorrect class was returned")
+ assert_instance_of(Puppet::Resource::Type, klass, "Did not autoload class from module init file")
+ assert_equal(name, klass.name, "Incorrect class was returned")
# Now try it with namespace classes where both classes are in the init file
parser = mkparser
modname = "both"
name = "sub"
mk_module(modname, :init => %w{both both::sub})
# First try it with a namespace
klass = parser.find_hostclass("both", name)
- assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from module init file with a namespace")
- assert_equal("both::sub", klass.classname, "Incorrect class was returned")
+ assert_instance_of(Puppet::Resource::Type, klass, "Did not autoload sub class from module init file with a namespace")
+ assert_equal("both::sub", klass.name, "Incorrect class was returned")
# Now try it using the fully qualified name
parser = mkparser
klass = parser.find_hostclass("", "both::sub")
- assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from module init file with no namespace")
- assert_equal("both::sub", klass.classname, "Incorrect class was returned")
+ assert_instance_of(Puppet::Resource::Type, klass, "Did not autoload sub class from module init file with no namespace")
+ assert_equal("both::sub", klass.name, "Incorrect class was returned")
# Now try it with the class in a different file
parser = mkparser
modname = "separate"
name = "sub"
mk_module(modname, :init => %w{separate}, :sub => %w{separate::sub})
# First try it with a namespace
klass = parser.find_hostclass("separate", name)
- assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from separate file with a namespace")
- assert_equal("separate::sub", klass.classname, "Incorrect class was returned")
+ assert_instance_of(Puppet::Resource::Type, klass, "Did not autoload sub class from separate file with a namespace")
+ assert_equal("separate::sub", klass.name, "Incorrect class was returned")
# Now try it using the fully qualified name
parser = mkparser
klass = parser.find_hostclass("", "separate::sub")
- assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from separate file with no namespace")
- assert_equal("separate::sub", klass.classname, "Incorrect class was returned")
+ assert_instance_of(Puppet::Resource::Type, klass, "Did not autoload sub class from separate file with no namespace")
+ assert_equal("separate::sub", klass.name, "Incorrect class was returned")
# Now make sure we don't get a failure when there's no module file
parser = mkparser
modname = "alone"
name = "sub"
mk_module(modname, :sub => %w{alone::sub})
# First try it with a namespace
assert_nothing_raised("Could not autoload file when module file is missing") do
klass = parser.find_hostclass("alone", name)
end
- assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from alone file with a namespace")
- assert_equal("alone::sub", klass.classname, "Incorrect class was returned")
+ assert_instance_of(Puppet::Resource::Type, klass, "Did not autoload sub class from alone file with a namespace")
+ assert_equal("alone::sub", klass.name, "Incorrect class was returned")
# Now try it using the fully qualified name
parser = mkparser
klass = parser.find_hostclass("", "alone::sub")
- assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from alone file with no namespace")
- assert_equal("alone::sub", klass.classname, "Incorrect class was returned")
+ assert_instance_of(Puppet::Resource::Type, klass, "Did not autoload sub class from alone file with no namespace")
+ assert_equal("alone::sub", klass.name, "Incorrect class was returned")
# and with the definition in its own file
name = "mymod"
mk_module(name, :define => true, :mydefine => ["mymod::mydefine"])
klass = parser.find_definition("", "mymod::mydefine")
- assert_instance_of(AST::Definition, klass, "Did not autoload definition from its own file")
- assert_equal("mymod::mydefine", klass.classname, "Incorrect definition was returned")
+ assert_instance_of(Puppet::Resource::Type, klass, "Did not autoload definition from its own file")
+ assert_equal("mymod::mydefine", klass.name, "Incorrect definition was returned")
end
# Make sure class, node, and define methods are case-insensitive
def test_structure_case_insensitivity
parser = mkparser
result = nil
assert_nothing_raised do
result = parser.newclass "Yayness"
end
assert_equal(result, parser.find_hostclass("", "yayNess"))
assert_nothing_raised do
result = parser.newdefine "FunTest"
end
assert_equal(result, parser.find_definition("", "fUntEst"),
"%s was not matched" % "fUntEst")
end
-
- def test_manifests_with_multiple_environments
- parser = mkparser :environment => "something"
-
- # We use an exception to cut short the processing to simplify our stubbing
- #Puppet::Module.expects(:find_manifests).with("test", {:cwd => ".", :environment => "something"}).raises(Puppet::ParseError)
- Puppet::Parser::Files.expects(:find_manifests).with("test", {:cwd => ".", :environment => "something"}).returns([])
-
- assert_raise(Puppet::ImportError) do
- parser.import("test")
- end
- end
-
- def test_watch_file_only_once
- FileTest.stubs(:exists?).returns(true)
- parser = mkparser
- parser.watch_file("doh")
- parser.watch_file("doh")
- assert_equal(1, parser.files.select { |name, file| file.file == "doh" }.length, "Length of watched 'doh' files was not 1")
- end
end
-
diff --git a/test/language/resource.rb b/test/language/resource.rb
deleted file mode 100755
index c1d116f9e..000000000
--- a/test/language/resource.rb
+++ /dev/null
@@ -1,172 +0,0 @@
-#!/usr/bin/env ruby
-
-require File.dirname(__FILE__) + '/../lib/puppettest'
-
-require 'puppettest'
-require 'puppettest/resourcetesting'
-
-class TestResource < PuppetTest::TestCase
- include PuppetTest
- include PuppetTest::ParserTesting
- include PuppetTest::ResourceTesting
- Parser = Puppet::Parser
- AST = Parser::AST
- Resource = Puppet::Parser::Resource
- Reference = Puppet::Parser::Resource::Reference
-
- def setup
- super
- Puppet[:trace] = false
- end
-
- def teardown
- mocha_verify
- end
-
- # Make sure we paramcheck our params
- def test_validate
- res = mkresource
- params = res.instance_variable_get("@params")
- params[:one] = :two
- params[:three] = :four
- res.expects(:paramcheck).with(:one)
- res.expects(:paramcheck).with(:three)
- res.send(:validate)
- end
-
- def test_set_parameter
- res = mkresource
- params = res.instance_variable_get("@params")
-
- # First test the simple case: It's already a parameter
- param = stub('param', :name => "pname")
- param.expects(:is_a?).with(Resource::Param).returns(true)
- res.send(:set_parameter, param)
- assert_equal(param, params["pname"], "Parameter was not added to hash")
-
- # Now the case where there's no value but it's not a param
- param = mock('param')
- param.expects(:is_a?).with(Resource::Param).returns(false)
- assert_raise(ArgumentError, "Did not fail when a non-param was passed") do
- res.send(:set_parameter, param)
- end
-
- # and the case where a value is passed in
- param = stub :name => "pname", :value => "whatever"
- Resource::Param.expects(:new).with(:name => "pname", :value => "myvalue", :source => res.source).returns(param)
- res.send(:set_parameter, "pname", "myvalue")
- assert_equal(param, params["pname"], "Did not put param in hash")
- end
-
- def test_paramcheck
- # There are three cases here:
-
- # It's a valid parameter
- res = mkresource
- ref = mock('ref')
- res.instance_variable_set("@ref", ref)
- klass = mock("class")
- ref.expects(:typeclass).returns(klass).times(4)
- klass.expects(:valid_parameter?).with("good").returns(true)
- assert(res.send(:paramcheck, :good), "Did not allow valid param")
-
- # It's name or title
- klass.expects(:valid_parameter?).with("name").returns(false)
- assert(res.send(:paramcheck, :name), "Did not allow name")
- klass.expects(:valid_parameter?).with("title").returns(false)
- assert(res.send(:paramcheck, :title), "Did not allow title")
-
- # It's not actually allowed
- klass.expects(:valid_parameter?).with("other").returns(false)
- res.expects(:fail)
- ref.expects(:type)
- res.send(:paramcheck, :other)
- end
-
- def test_evaluate
- # First try the most common case, we're not a builtin type.
- res = mkresource
- ref = res.instance_variable_get("@ref")
- type = mock("type")
- ref.expects(:definedtype).returns(type)
- res.expects(:finish)
- res.scope = mock("scope")
-
- type.expects(:evaluate_code).with(res)
-
- res.evaluate
- end
-
- def test_proxymethods
- res = Parser::Resource.new :type => "evaltest", :title => "yay",
- :source => mock("source"), :scope => mkscope
-
- assert_equal("Evaltest", res.type)
- assert_equal("yay", res.title)
- assert_equal(false, res.builtin?)
- end
-
- # This is a bit of a weird one -- the user should not actually know
- # that components exist, so we want references to act like they're not
- # builtin
- def test_components_are_not_builtin
- ref = Parser::Resource::Reference.new(:type => "component", :title => "yay")
-
- assert_nil(ref.builtintype, "Definition was considered builtin")
- end
-
- # The second part of #539 - make sure resources pass the arguments
- # correctly.
- def test_title_with_definitions
- parser = mkparser
- define = parser.newdefine "yayness",
- :code => resourcedef("file", "/tmp",
- "owner" => varref("name"), "mode" => varref("title"))
-
-
- klass = parser.find_hostclass("", "")
- should = {:name => :owner, :title => :mode}
- [
- {:name => "one", :title => "two"},
- {:title => "three"},
- ].each do |hash|
- config = mkcompiler parser
- args = {:type => "yayness", :title => hash[:title],
- :source => klass, :scope => config.topscope}
- if hash[:name]
- args[:params] = {:name => hash[:name]}
- else
- args[:params] = {} # override the defaults
- end
-
- res = nil
- assert_nothing_raised("Could not create res with %s" % hash.inspect) do
- res = mkresource(args)
- end
- assert_nothing_raised("Could not eval res with %s" % hash.inspect) do
- res.evaluate
- end
-
- made = config.topscope.findresource("File[/tmp]")
- assert(made, "Did not create resource with %s" % hash.inspect)
- should.each do |orig, param|
- assert_equal(hash[orig] || hash[:title], made[param],
- "%s was not set correctly with %s" % [param, hash.inspect])
- end
- end
- end
-
- # part of #629 -- the undef keyword. Make sure 'undef' params get skipped.
- def test_undef_and_to_hash
- res = mkresource :type => "file", :title => "/tmp/testing",
- :source => mock("source"), :scope => mkscope,
- :params => {:owner => :undef, :mode => "755"}
-
- hash = nil
- assert_nothing_raised("Could not convert resource with undef to hash") do
- hash = res.to_hash
- end
-
- assert_nil(hash[:owner], "got a value for an undef parameter")
- end
-end
diff --git a/test/language/scope.rb b/test/language/scope.rb
index 8819de66e..b1ba63de4 100755
--- a/test/language/scope.rb
+++ b/test/language/scope.rb
@@ -1,482 +1,299 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../lib/puppettest'
require 'mocha'
require 'puppettest'
require 'puppettest/parsertesting'
require 'puppettest/resourcetesting'
# so, what kind of things do we want to test?
# we don't need to test function, since we're confident in the
# library tests. We do, however, need to test how things are actually
# working in the language.
# so really, we want to do things like test that our ast is correct
# and test whether we've got things in the right scopes
class TestScope < Test::Unit::TestCase
include PuppetTest::ParserTesting
include PuppetTest::ResourceTesting
+ def setup
+ Puppet::Node::Environment.clear
+ super
+ end
+
def to_ary(hash)
hash.collect { |key,value|
[key,value]
}
end
def test_variables
config = mkcompiler
topscope = config.topscope
midscope = config.newscope(topscope)
botscope = config.newscope(midscope)
scopes = {:top => topscope, :mid => midscope, :bot => botscope}
# Set a variable in the top and make sure all three can get it
topscope.setvar("first", "topval")
scopes.each do |name, scope|
assert_equal("topval", scope.lookupvar("first", false), "Could not find var in %s" % name)
end
# Now set a var in the midscope and make sure the mid and bottom can see it but not the top
midscope.setvar("second", "midval")
assert_equal(:undefined, scopes[:top].lookupvar("second", false), "Found child var in top scope")
[:mid, :bot].each do |name|
assert_equal("midval", scopes[name].lookupvar("second", false), "Could not find var in %s" % name)
end
# And set something in the bottom, and make sure we only find it there.
botscope.setvar("third", "botval")
[:top, :mid].each do |name|
assert_equal(:undefined, scopes[name].lookupvar("third", false), "Found child var in top scope")
end
assert_equal("botval", scopes[:bot].lookupvar("third", false), "Could not find var in bottom scope")
# Test that the scopes convert to hash structures correctly.
# For topscope recursive vs non-recursive should be identical
assert_equal(topscope.to_hash(false), topscope.to_hash(true),
"Recursive and non-recursive hash is identical for topscope")
# Check the variable we expect is present.
assert_equal({"first" => "topval"}, topscope.to_hash(),
"topscope returns the expected hash of variables")
# Now, check that midscope does the right thing in all cases.
assert_equal({"second" => "midval"},
midscope.to_hash(false),
"midscope non-recursive hash contains only midscope variable")
assert_equal({"first" => "topval", "second" => "midval"},
midscope.to_hash(true),
"midscope recursive hash contains topscope variable also")
# Finally, check the ability to shadow symbols by adding a shadow to
# bottomscope, then checking that we see the right stuff.
botscope.setvar("first", "shadowval")
assert_equal({"third" => "botval", "first" => "shadowval"},
botscope.to_hash(false),
"botscope has the right non-recursive hash")
assert_equal({"third" => "botval", "first" => "shadowval", "second" => "midval"},
botscope.to_hash(true),
"botscope values shadow parent scope values")
end
def test_declarative
# set to declarative
top = mkscope
sub = mkscope(:parent => top)
assert_nothing_raised {
top.setvar("test","value")
}
assert_raise(Puppet::ParseError) {
top.setvar("test","other")
}
assert_nothing_raised {
sub.setvar("test","later")
}
assert_raise(Puppet::ParseError) {
top.setvar("test","yeehaw")
}
end
- def test_setdefaults
- config = mkcompiler
-
- scope = config.topscope
-
- defaults = scope.instance_variable_get("@defaults")
-
- # First the case where there are no defaults and we pass a single param
- param = stub :name => "myparam", :file => "f", :line => "l"
- scope.setdefaults(:mytype, param)
- assert_equal({"myparam" => param}, defaults[:mytype], "Did not set default correctly")
-
- # Now the case where we pass in multiple parameters
- param1 = stub :name => "one", :file => "f", :line => "l"
- param2 = stub :name => "two", :file => "f", :line => "l"
- scope.setdefaults(:newtype, [param1, param2])
- assert_equal({"one" => param1, "two" => param2}, defaults[:newtype], "Did not set multiple defaults correctly")
-
- # And the case where there's actually a conflict. Use the first default for this.
- newparam = stub :name => "myparam", :file => "f", :line => "l"
- assert_raise(Puppet::ParseError, "Allowed resetting of defaults") do
- scope.setdefaults(:mytype, param)
- end
- assert_equal({"myparam" => param}, defaults[:mytype], "Replaced default even though there was a failure")
- end
-
- def test_lookupdefaults
- config = mkcompiler
- top = config.topscope
-
- # Make a subscope
- sub = config.newscope(top)
-
- topdefs = top.instance_variable_get("@defaults")
- subdefs = sub.instance_variable_get("@defaults")
-
- # First add some defaults to our top scope
- topdefs[:t1] = {:p1 => :p2, :p3 => :p4}
- topdefs[:t2] = {:p5 => :p6}
-
- # Then the sub scope
- subdefs[:t1] = {:p1 => :p7, :p8 => :p9}
- subdefs[:t2] = {:p5 => :p10, :p11 => :p12}
-
- # Now make sure we get the correct list back
- result = nil
- assert_nothing_raised("Could not get defaults") do
- result = sub.lookupdefaults(:t1)
- end
- assert_equal(:p9, result[:p8], "Did not get child defaults")
- assert_equal(:p4, result[:p3], "Did not override parent defaults with child default")
- assert_equal(:p7, result[:p1], "Did not get parent defaults")
- end
-
def test_parent
config = mkcompiler
top = config.topscope
# Make a subscope
sub = config.newscope(top)
assert_equal(top, sub.parent, "Did not find parent scope correctly")
assert_equal(top, sub.parent, "Did not find parent scope on second call")
end
- def test_strinterp
- # Make and evaluate our classes so the qualified lookups work
- parser = mkparser
- klass = parser.newclass("")
- scope = mkscope(:parser => parser)
- Puppet::Parser::Resource.new(:type => "class", :title => :main, :scope => scope, :source => mock('source')).evaluate
-
- assert_nothing_raised {
- scope.setvar("test","value")
- }
-
- scopes = {"" => scope}
-
- %w{one one::two one::two::three}.each do |name|
- klass = parser.newclass(name)
- Puppet::Parser::Resource.new(:type => "class", :title => name, :scope => scope, :source => mock('source')).evaluate
- scopes[name] = scope.compiler.class_scope(klass)
- scopes[name].setvar("test", "value-%s" % name.sub(/.+::/,''))
- end
-
- assert_equal("value", scope.lookupvar("::test"), "did not look up qualified value correctly")
- tests = {
- "string ${test}" => "string value",
- "string ${one::two::three::test}" => "string value-three",
- "string $one::two::three::test" => "string value-three",
- "string ${one::two::test}" => "string value-two",
- "string $one::two::test" => "string value-two",
- "string ${one::test}" => "string value-one",
- "string $one::test" => "string value-one",
- "string ${::test}" => "string value",
- "string $::test" => "string value",
- "string ${test} ${test} ${test}" => "string value value value",
- "string $test ${test} $test" => "string value value value",
- "string \\$test" => "string $test",
- '\\$test string' => "$test string",
- '$test string' => "value string",
- 'a testing $' => "a testing $",
- 'a testing \$' => "a testing $",
- "an escaped \\\n carriage return" => "an escaped carriage return",
- '\$' => "$",
- '\s' => "\s",
- '\t' => "\t",
- '\n' => "\n"
- }
-
- tests.each do |input, output|
- assert_nothing_raised("Failed to scan %s" % input.inspect) do
- assert_equal(output, scope.strinterp(input),
- 'did not parserret %s correctly' % input.inspect)
- end
- end
-
- logs = []
- Puppet::Util::Log.close
- Puppet::Util::Log.newdestination(logs)
-
- # #523
- %w{d f h l w z}.each do |l|
- string = "\\" + l
- assert_nothing_raised do
- assert_equal(string, scope.strinterp(string),
- 'did not parserret %s correctly' % string)
- end
-
- assert(logs.detect { |m| m.message =~ /Unrecognised escape/ },
- "Did not get warning about escape sequence with %s" % string)
- logs.clear
- end
- end
-
- def test_tagfunction
- Puppet::Parser::Functions.function(:tag)
- scope = mkscope
- resource = mock 'resource'
- scope.resource = resource
- resource.expects(:tag).with("yayness", "booness")
-
- scope.function_tag(%w{yayness booness})
- end
-
def test_includefunction
parser = mkparser
scope = mkscope :parser => parser
myclass = parser.newclass "myclass"
otherclass = parser.newclass "otherclass"
function = Puppet::Parser::AST::Function.new(
:name => "include",
:ftype => :statement,
:arguments => AST::ASTArray.new(
:children => [nameobj("myclass"), nameobj("otherclass")]
)
)
assert_nothing_raised do
function.evaluate scope
end
scope.compiler.send(:evaluate_generators)
[myclass, otherclass].each do |klass|
assert(scope.compiler.class_scope(klass),
- "%s was not set" % klass.classname)
+ "%s was not set" % klass.name)
end
end
def test_definedfunction
Puppet::Parser::Functions.function(:defined)
parser = mkparser
%w{one two}.each do |name|
parser.newdefine name
end
scope = mkscope :parser => parser
assert_nothing_raised {
%w{one two file user}.each do |type|
assert(scope.function_defined([type]),
"Class #{type} was not considered defined")
end
assert(!scope.function_defined(["nopeness"]),
"Class 'nopeness' was incorrectly considered defined")
}
end
# Make sure we know what we consider to be truth.
def test_truth
assert_equal(true, Puppet::Parser::Scope.true?("a string"),
"Strings not considered true")
assert_equal(true, Puppet::Parser::Scope.true?(true),
"True considered true")
assert_equal(false, Puppet::Parser::Scope.true?(""),
"Empty strings considered true")
assert_equal(false, Puppet::Parser::Scope.true?(false),
"false considered true")
assert_equal(false, Puppet::Parser::Scope.true?(:undef),
"undef considered true")
end
# Verify that we recursively mark as exported the results of collectable
# components.
def test_virtual_definitions_do_not_get_evaluated
config = mkcompiler
- parser = config.parser
+ parser = mkparser
# Create a default source
- config.topscope.source = parser.newclass ""
+ parser.newclass("")
+ config.topscope.source = parser.known_resource_types.hostclass("")
# And a scope resource
- scope_res = stub 'scope_resource', :virtual? => true, :exported? => false, :tags => [], :builtin? => true, :type => "eh", :title => "bee"
+ scope_res = Puppet::Parser::Resource.new(:file, "/file", :scope => "scope", :source => "source")
config.topscope.resource = scope_res
args = AST::ASTArray.new(
:file => tempfile(),
:line => rand(100),
:children => [nameobj("arg")]
)
# Create a top-level define
parser.newdefine "one", :arguments => [%w{arg}],
:code => AST::ASTArray.new(
:children => [
resourcedef("file", "/tmp", {"owner" => varref("arg")})
]
)
# create a resource that calls our third define
obj = resourcedef("one", "boo", {"arg" => "parentfoo"})
# And mark it as virtual
obj.virtual = true
# And then evaluate it
obj.evaluate config.topscope
# And run the loop.
config.send(:evaluate_generators)
%w{File}.each do |type|
objects = config.resources.find_all { |r| r.type == type and r.virtual }
assert(objects.empty?, "Virtual define got evaluated")
end
end
if defined? ::ActiveRecord
# Verify that we can both store and collect an object in the same
# run, whether it's in the same scope as a collection or a different
# scope.
def test_storeandcollect
catalog_cache_class = Puppet::Resource::Catalog.indirection.cache_class
facts_cache_class = Puppet::Node::Facts.indirection.cache_class
node_cache_class = Puppet::Node.indirection.cache_class
Puppet[:storeconfigs] = true
Puppet::Rails.init
sleep 1
children = []
Puppet[:code] = "
class yay {
@@host { myhost: ip => \"192.168.0.2\" }
}
include yay
@@host { puppet: ip => \"192.168.0.3\" }
Host <<||>>"
- interp = nil
- assert_nothing_raised {
- interp = Puppet::Parser::Interpreter.new
- }
-
config = nil
# We run it twice because we want to make sure there's no conflict
# if we pull it up from the database.
node = mknode
- node.parameters = {"hostname" => node.name}
+ node.merge "hostname" => node.name
2.times { |i|
- assert_nothing_raised {
- config = interp.compile(node)
- }
+ config = Puppet::Parser::Compiler.new(node).compile
flat = config.extract.flatten
%w{puppet myhost}.each do |name|
assert(flat.find{|o| o.name == name }, "Did not find #{name}")
end
}
ensure
Puppet[:storeconfigs] = false
Puppet::Resource::Catalog.cache_class = catalog_cache_class
Puppet::Node::Facts.cache_class = facts_cache_class
Puppet::Node.cache_class = node_cache_class
end
else
$stderr.puts "No ActiveRecord -- skipping collection tests"
end
def test_namespaces
scope = mkscope
assert_equal([""], scope.namespaces,
"Started out with incorrect namespaces")
assert_nothing_raised { scope.add_namespace("fun::test") }
assert_equal(["fun::test"], scope.namespaces,
"Did not add namespace correctly")
assert_nothing_raised { scope.add_namespace("yay::test") }
assert_equal(["fun::test", "yay::test"], scope.namespaces,
"Did not add extra namespace correctly")
end
- def test_find_hostclass_and_find_definition
- parser = mkparser
-
- # Make sure our scope calls the parser find_hostclass method with
- # the right namespaces
- scope = mkscope :parser => parser
-
- parser.metaclass.send(:attr_accessor, :last)
-
- methods = [:find_hostclass, :find_definition]
- methods.each do |m|
- parser.meta_def(m) do |namespace, name|
- @checked ||= []
- @checked << [namespace, name]
-
- # Only return a value on the last call.
- if @last == namespace
- ret = @checked.dup
- @checked.clear
- return ret
- else
- return nil
- end
- end
- end
-
- test = proc do |should|
- parser.last = scope.namespaces[-1]
- methods.each do |method|
- result = scope.send(method, "testing")
- assert_equal(should, result,
- "did not get correct value from %s with namespaces %s" %
- [method, scope.namespaces.inspect])
- end
- end
-
- # Start with the empty namespace
- assert_nothing_raised { test.call([["", "testing"]]) }
-
- # Now add a namespace
- scope.add_namespace("a")
- assert_nothing_raised { test.call([["a", "testing"]]) }
-
- # And another
- scope.add_namespace("b")
- assert_nothing_raised { test.call([["a", "testing"], ["b", "testing"]]) }
- end
-
# #629 - undef should be "" or :undef
def test_lookupvar_with_undef
scope = mkscope
scope.setvar("testing", :undef)
assert_equal(:undef, scope.lookupvar("testing", false),
"undef was not returned as :undef when not string")
assert_equal("", scope.lookupvar("testing", true),
"undef was not returned as '' when string")
end
end
diff --git a/test/lib/puppettest/parsertesting.rb b/test/lib/puppettest/parsertesting.rb
index dee38eb3a..44c78f2c3 100644
--- a/test/lib/puppettest/parsertesting.rb
+++ b/test/lib/puppettest/parsertesting.rb
@@ -1,411 +1,404 @@
require 'puppettest'
require 'puppet/rails'
module PuppetTest::ParserTesting
include PuppetTest
AST = Puppet::Parser::AST
Compiler = Puppet::Parser::Compiler
# A fake class that we can use for testing evaluation.
class FakeAST
attr_writer :evaluate
def evaluated?
defined? @evaluated and @evaluated
end
def evaluate(*args)
@evaluated = true
return @evaluate
end
def initialize(val = nil)
if val
@evaluate = val
end
end
def reset
@evaluated = nil
end
def safeevaluate(*args)
evaluate()
end
def evaluate_match(othervalue, scope, options={})
value = evaluate()
othervalue == value
end
end
def astarray(*args)
AST::ASTArray.new(
:children => args
)
end
def mkcompiler(parser = nil)
- parser ||= mkparser
node = mknode
- return Compiler.new(node, parser)
+ return Compiler.new(node)
end
def mknode(name = nil)
require 'puppet/node'
name ||= "nodename"
Puppet::Network::Handler.handler(:node)
Puppet::Node.new(name)
end
- def mkinterp
- Puppet::Parser::Interpreter.new
- end
-
- def mkparser(args = {})
- Puppet::Parser::Parser.new(args)
+ def mkparser
+ Puppet::Node::Environment.clear
+ Puppet::Parser::Parser.new(Puppet::Node::Environment.new)
end
def mkscope(hash = {})
hash[:parser] ||= mkparser
compiler ||= mkcompiler(hash[:parser])
compiler.topscope.source = (hash[:parser].find_hostclass("", "") || hash[:parser].newclass(""))
unless compiler.topscope.source
raise "Could not find source for scope"
end
# Make the 'main' stuff
compiler.send(:evaluate_main)
compiler.topscope
end
def classobj(name, hash = {})
hash[:file] ||= __FILE__
hash[:line] ||= __LINE__
hash[:type] ||= name
AST::HostClass.new(hash)
end
def tagobj(*names)
args = {}
newnames = names.collect do |name|
if name.is_a? AST
name
else
nameobj(name)
end
end
args[:type] = astarray(*newnames)
assert_nothing_raised("Could not create tag %s" % names.inspect) {
return AST::Tag.new(args)
}
end
def resourcedef(type, title, params)
unless title.is_a?(AST)
title = stringobj(title)
end
assert_nothing_raised("Could not create %s %s" % [type, title]) {
return AST::Resource.new(
:file => __FILE__,
:line => __LINE__,
:title => title,
:type => type,
:params => resourceinst(params)
)
}
end
def virt_resourcedef(*args)
res = resourcedef(*args)
res.virtual = true
res
end
def resourceoverride(type, title, params)
assert_nothing_raised("Could not create %s %s" % [type, name]) {
return AST::ResourceOverride.new(
:file => __FILE__,
:line => __LINE__,
:object => resourceref(type, title),
:type => type,
:params => resourceinst(params)
)
}
end
def resourceref(type, title)
assert_nothing_raised("Could not create %s %s" % [type, title]) {
return AST::ResourceReference.new(
:file => __FILE__,
:line => __LINE__,
:type => type,
:title => stringobj(title)
)
}
end
def fileobj(path, hash = {"owner" => "root"})
assert_nothing_raised("Could not create file %s" % path) {
return resourcedef("file", path, hash)
}
end
def nameobj(name)
assert_nothing_raised("Could not create name %s" % name) {
return AST::Name.new(
:file => tempfile(),
:line => rand(100),
:value => name
)
}
end
def typeobj(name)
assert_nothing_raised("Could not create type %s" % name) {
return AST::Type.new(
:file => tempfile(),
:line => rand(100),
:value => name
)
}
end
def nodedef(name)
assert_nothing_raised("Could not create node %s" % name) {
return AST::NodeDef.new(
:file => tempfile(),
:line => rand(100),
:names => nameobj(name),
:code => AST::ASTArray.new(
:children => [
varobj("%svar" % name, "%svalue" % name),
fileobj("/%s" % name)
]
)
)
}
end
def resourceinst(hash)
assert_nothing_raised("Could not create resource instance") {
params = hash.collect { |param, value|
resourceparam(param, value)
}
return AST::ResourceInstance.new(
:file => tempfile(),
:line => rand(100),
:children => params
)
}
end
def resourceparam(param, value)
# Allow them to pass non-strings in
if value.is_a?(String)
value = stringobj(value)
end
assert_nothing_raised("Could not create param %s" % param) {
return AST::ResourceParam.new(
:file => tempfile(),
:line => rand(100),
:param => param,
:value => value
)
}
end
def stringobj(value)
AST::String.new(
:file => tempfile(),
:line => rand(100),
:value => value
)
end
def varobj(name, value)
unless value.is_a? AST
value = stringobj(value)
end
assert_nothing_raised("Could not create %s code" % name) {
return AST::VarDef.new(
:file => tempfile(),
:line => rand(100),
:name => nameobj(name),
:value => value
)
}
end
def varref(name)
assert_nothing_raised("Could not create %s variable" % name) {
return AST::Variable.new(
:file => __FILE__,
:line => __LINE__,
:value => name
)
}
end
def argobj(name, value)
assert_nothing_raised("Could not create %s compargument" % name) {
return AST::CompArgument.new(
:children => [nameobj(name), stringobj(value)]
)
}
end
def defaultobj(type, params)
pary = []
params.each { |p,v|
pary << AST::ResourceParam.new(
:file => __FILE__,
:line => __LINE__,
:param => p,
:value => stringobj(v)
)
}
past = AST::ASTArray.new(
:file => __FILE__,
:line => __LINE__,
:children => pary
)
assert_nothing_raised("Could not create defaults for %s" % type) {
return AST::ResourceDefaults.new(
:file => __FILE__,
:line => __LINE__,
:type => type,
:params => past
)
}
end
def taggedobj(name, ftype = :statement)
functionobj("tagged", name, ftype)
end
def functionobj(function, name, ftype = :statement)
func = nil
assert_nothing_raised do
func = Puppet::Parser::AST::Function.new(
:name => function,
:ftype => ftype,
:arguments => AST::ASTArray.new(
:children => [nameobj(name)]
)
)
end
return func
end
# This assumes no nodes
def assert_creates(manifest, *files)
interp = nil
oldmanifest = Puppet[:manifest]
Puppet[:manifest] = manifest
- assert_nothing_raised {
- interp = Puppet::Parser::Interpreter.new
- }
trans = nil
assert_nothing_raised {
- trans = interp.compile(mknode)
+ trans = Puppet::Parser::Compiler.new(mknode).compile
}
config = nil
assert_nothing_raised {
config = trans.extract.to_catalog
}
config.apply
files.each do |file|
assert(FileTest.exists?(file), "Did not create %s" % file)
end
ensure
Puppet[:manifest] = oldmanifest
end
def mk_transobject(file = "/etc/passwd")
obj = nil
assert_nothing_raised {
obj = Puppet::TransObject.new("file", file)
obj["owner"] = "root"
obj["mode"] = "644"
}
return obj
end
def mk_transbucket(*resources)
bucket = nil
assert_nothing_raised {
bucket = Puppet::TransBucket.new
bucket.name = "yayname"
bucket.type = "yaytype"
}
resources.each { |o| bucket << o }
return bucket
end
# Make a tree of resources, yielding if desired
def mk_transtree(depth = 4, width = 2)
top = nil
assert_nothing_raised {
top = Puppet::TransBucket.new
top.name = "top"
top.type = "bucket"
}
bucket = top
file = tempfile()
depth.times do |i|
resources = []
width.times do |j|
path = tempfile + i.to_s
obj = Puppet::TransObject.new("file", path)
obj["owner"] = "root"
obj["mode"] = "644"
# Yield, if they want
if block_given?
yield(obj, i, j)
end
resources << obj
end
newbucket = mk_transbucket(*resources)
bucket.push newbucket
bucket = newbucket
end
return top
end
# Take a list of AST resources, evaluate them, and return the results
def assert_evaluate(children)
top = nil
assert_nothing_raised("Could not create top object") {
top = AST::ASTArray.new(
:children => children
)
}
trans = nil
scope = nil
assert_nothing_raised {
scope = Puppet::Parser::Scope.new()
trans = scope.evaluate(:ast => top)
}
return trans
end
end
diff --git a/test/lib/puppettest/resourcetesting.rb b/test/lib/puppettest/resourcetesting.rb
index d4469a203..95fe5bcb7 100644
--- a/test/lib/puppettest/resourcetesting.rb
+++ b/test/lib/puppettest/resourcetesting.rb
@@ -1,45 +1,48 @@
module PuppetTest::ResourceTesting
Parser = Puppet::Parser
AST = Puppet::Parser::AST
def mkevaltest(parser = nil)
parser ||= mkparser
@parser.newdefine("evaltest",
:arguments => [%w{one}, ["two", stringobj("755")]],
:code => resourcedef("file", "/tmp",
"owner" => varref("one"), "mode" => varref("two"))
)
end
def mkresource(args = {})
args[:source] ||= "source"
args[:scope] ||= mkscope
- {:type => "resource", :title => "testing",
- :source => "source", :scope => "scope"}.each do |param, value|
- args[param] ||= value
+ type = args[:type] || "resource"
+ title = args[:title] || "testing"
+ args.delete(:type)
+ args.delete(:title)
+ {: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
- Parser::Resource.new(args)
+ Parser::Resource.new(type, title, args)
end
def param(name, value, source)
Parser::Resource::Param.new(:name => name, :value => value, :source => source)
end
def paramify(source, hash)
hash.collect do |name, value|
Parser::Resource::Param.new(
:name => name, :value => value, :source => source
)
end
end
end
diff --git a/test/other/relationships.rb b/test/other/relationships.rb
index b15ff5062..e9d3a9376 100755
--- a/test/other/relationships.rb
+++ b/test/other/relationships.rb
@@ -1,90 +1,90 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../lib/puppettest'
require 'puppet'
require 'puppettest'
class TestRelationships < Test::Unit::TestCase
include PuppetTest
def setup
super
Puppet::Type.type(:exec)
end
def newfile
assert_nothing_raised() {
return Puppet::Type.type(:file).new(
:path => tempfile,
:check => [:mode, :owner, :group]
)
}
end
def check_relationship(sources, targets, out, refresher)
if out
deps = sources.builddepends
sources = [sources]
else
deps = targets.builddepends
targets = [targets]
end
assert_instance_of(Array, deps)
assert(! deps.empty?, "Did not receive any relationships")
deps.each do |edge|
assert_instance_of(Puppet::Relationship, edge)
end
sources.each do |source|
targets.each do |target|
edge = deps.find { |e| e.source == source and e.target == target }
assert(edge, "Could not find edge for %s => %s" %
[source.ref, target.ref])
if refresher
assert_equal(:ALL_EVENTS, edge.event)
assert_equal(:refresh, edge.callback)
else
assert_nil(edge.event)
assert_nil(edge.callback, "Got a callback with no events")
end
end
end
end
def test_autorequire
# We know that execs autorequire their cwd, so we'll use that
path = tempfile()
file = Puppet::Type.type(:file).new(:title => "myfile", :path => path,
:ensure => :directory)
exec = Puppet::Type.newexec(:title => "myexec", :cwd => path,
:command => "/bin/echo")
catalog = mk_catalog(file, exec)
reqs = nil
assert_nothing_raised do
reqs = exec.autorequire
end
assert_instance_of(Puppet::Relationship, reqs[0], "Did not return a relationship edge")
assert_equal(file, reqs[0].source, "Did not set the autorequire source correctly")
assert_equal(exec, reqs[0].target, "Did not set the autorequire target correctly")
# Now make sure that these relationships are added to the
# relationship graph
catalog.apply do |trans|
assert(catalog.relationship_graph.edge?(file, exec), "autorequire edge was not created")
end
end
# Testing #411. It was a problem with builddepends.
def test_missing_deps
- file = Puppet::Type.type(:file).new :path => tempfile, :require => Puppet::Resource::Reference.new("file", "/no/such/file")
+ file = Puppet::Type.type(:file).new :path => tempfile, :require => Puppet::Resource.new("file", "/no/such/file")
assert_raise(Puppet::Error) do
file.builddepends
end
end
end
diff --git a/test/other/transactions.rb b/test/other/transactions.rb
index 94deeab26..6f4f5d913 100755
--- a/test/other/transactions.rb
+++ b/test/other/transactions.rb
@@ -1,745 +1,745 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../lib/puppettest'
require 'mocha'
require 'puppet'
require 'puppettest'
require 'puppettest/support/resources'
require 'puppettest/support/utils'
class TestTransactions < Test::Unit::TestCase
include PuppetTest::FileTesting
include PuppetTest::Support::Resources
include PuppetTest::Support::Utils
class Fakeprop true)
def finish
$finished << self.name
end
end
if block
type.class_eval(&block)
end
cleanup do
Puppet::Type.rmtype(:generator)
end
return type
end
# Create a new type that generates instances with shorter names.
def mkreducer(&block)
type = mkgenerator() do
def eval_generate
ret = []
if title.length > 1
ret << self.class.new(:title => title[0..-2])
else
return nil
end
ret
end
end
if block
type.class_eval(&block)
end
return type
end
def test_prefetch
# Create a type just for testing prefetch
name = :prefetchtesting
$prefetched = false
type = Puppet::Type.newtype(name) do
newparam(:name) {}
end
cleanup do
Puppet::Type.rmtype(name)
end
# Now create a provider
type.provide(:prefetch) do
def self.prefetch(resources)
$prefetched = resources
end
end
# Now create an instance
inst = type.new :name => "yay"
# Create a transaction
trans = Puppet::Transaction.new(mk_catalog(inst))
# Make sure prefetch works
assert_nothing_raised do
trans.prefetch
end
assert_equal({inst.title => inst}, $prefetched, "type prefetch was not called")
# Now make sure it gets called from within evaluate()
$prefetched = false
assert_nothing_raised do
trans.evaluate
end
assert_equal({inst.title => inst}, $prefetched, "evaluate did not call prefetch")
end
def test_refreshes_generate_events
path = tempfile()
firstpath = tempfile()
secondpath = tempfile()
file = Puppet::Type.type(:file).new(:title => "file", :path => path, :content => "yayness")
first = Puppet::Type.type(:exec).new(:title => "first",
:command => "/bin/echo first > #{firstpath}",
- :subscribe => Puppet::Resource::Reference.new(:file, path),
+ :subscribe => Puppet::Resource.new(:file, path),
:refreshonly => true
)
second = Puppet::Type.type(:exec).new(:title => "second",
:command => "/bin/echo second > #{secondpath}",
- :subscribe => Puppet::Resource::Reference.new(:exec, "first"),
+ :subscribe => Puppet::Resource.new(:exec, "first"),
:refreshonly => true
)
assert_apply(file, first, second)
assert(FileTest.exists?(secondpath), "Refresh did not generate an event")
end
unless %x{groups}.chomp.split(/ /).length > 1
$stderr.puts "You must be a member of more than one group to test transactions"
else
def ingroup(gid)
require 'etc'
begin
group = Etc.getgrgid(gid)
rescue => detail
puts "Could not retrieve info for group %s: %s" % [gid, detail]
return nil
end
return @groups.include?(group.name)
end
def setup
super
@groups = %x{groups}.chomp.split(/ /)
unless @groups.length > 1
p @groups
raise "You must be a member of more than one group to test this"
end
end
def newfile(hash = {})
tmpfile = tempfile()
File.open(tmpfile, "w") { |f| f.puts rand(100) }
# XXX now, because os x apparently somehow allows me to make a file
# owned by a group i'm not a member of, i have to verify that
# the file i just created is owned by one of my groups
# grrr
unless ingroup(File.stat(tmpfile).gid)
Puppet.info "Somehow created file in non-member group %s; fixing" %
File.stat(tmpfile).gid
require 'etc'
firstgr = @groups[0]
unless firstgr.is_a?(Integer)
str = Etc.getgrnam(firstgr)
firstgr = str.gid
end
File.chown(nil, firstgr, tmpfile)
end
hash[:name] = tmpfile
assert_nothing_raised() {
return Puppet::Type.type(:file).new(hash)
}
end
def newexec(file)
assert_nothing_raised() {
return Puppet::Type.type(:exec).new(
:name => "touch %s" % file,
:path => "/bin:/usr/bin:/sbin:/usr/sbin",
:returns => 0
)
}
end
# test that services are correctly restarted and that work is done
# in the right order
def test_refreshing
transaction = nil
file = newfile()
execfile = File.join(tmpdir(), "exectestingness")
exec = newexec(execfile)
properties = {}
check = [:group,:mode]
file[:check] = check
file[:group] = @groups[0]
config = mk_catalog(file)
config.apply
@@tmpfiles << execfile
# 'subscribe' expects an array of arrays
- exec[:subscribe] = Puppet::Resource::Reference.new(file.class.name,file.name)
+ exec[:subscribe] = Puppet::Resource.new(file.class.name,file.name)
exec[:refreshonly] = true
assert_nothing_raised() {
file.retrieve
exec.retrieve
}
check.each { |property|
properties[property] = file.value(property)
}
assert_nothing_raised() {
file[:mode] = "755"
}
# Make a new catalog so the resource relationships get
# set up.
config = mk_catalog(file, exec)
trans = assert_events([:mode_changed, :restarted], config)
assert(FileTest.exists?(execfile), "Execfile does not exist")
File.unlink(execfile)
assert_nothing_raised() {
file[:group] = @groups[1]
}
trans = assert_events([:group_changed, :restarted], config)
assert(FileTest.exists?(execfile), "Execfile does not exist")
end
# Verify that one component requiring another causes the contained
# resources in the requiring component to get refreshed.
def test_refresh_across_two_components
transaction = nil
file = newfile()
execfile = File.join(tmpdir(), "exectestingness2")
@@tmpfiles << execfile
exec = newexec(execfile)
properties = {}
check = [:group,:mode]
file[:check] = check
file[:group] = @groups[0]
assert_apply(file)
config = Puppet::Resource::Catalog.new
fcomp = Puppet::Type.type(:component).new(:name => "file")
config.add_resource fcomp
config.add_resource file
config.add_edge(fcomp, file)
ecomp = Puppet::Type.type(:component).new(:name => "exec")
config.add_resource ecomp
config.add_resource exec
config.add_edge(ecomp, exec)
# 'subscribe' expects an array of arrays
#component[:require] = [[file.class.name,file.name]]
ecomp[:subscribe] = fcomp.ref
exec[:refreshonly] = true
trans = assert_events([], config)
assert_nothing_raised() {
file[:group] = @groups[1]
file[:mode] = "755"
}
trans = assert_events([:group_changed, :mode_changed, :restarted], config)
end
# Make sure that multiple subscriptions get triggered.
def test_multisubs
path = tempfile()
file1 = tempfile()
file2 = tempfile()
file = Puppet::Type.type(:file).new(
:path => path,
:ensure => "file"
)
exec1 = Puppet::Type.type(:exec).new(
:path => ENV["PATH"],
:command => "touch %s" % file1,
:refreshonly => true,
- :subscribe => Puppet::Resource::Reference.new(:file, path)
+ :subscribe => Puppet::Resource.new(:file, path)
)
exec2 = Puppet::Type.type(:exec).new(
:path => ENV["PATH"],
:command => "touch %s" % file2,
:refreshonly => true,
- :subscribe => Puppet::Resource::Reference.new(:file, path)
+ :subscribe => Puppet::Resource.new(:file, path)
)
assert_apply(file, exec1, exec2)
assert(FileTest.exists?(file1), "File 1 did not get created")
assert(FileTest.exists?(file2), "File 2 did not get created")
end
# Make sure that a failed trigger doesn't result in other events not
# getting triggered.
def test_failedrefreshes
path = tempfile()
newfile = tempfile()
file = Puppet::Type.type(:file).new(
:path => path,
:ensure => "file"
)
exec1 = Puppet::Type.type(:exec).new(
:path => ENV["PATH"],
:command => "touch /this/cannot/possibly/exist",
:logoutput => true,
:refreshonly => true,
:subscribe => file,
:title => "one"
)
exec2 = Puppet::Type.type(:exec).new(
:path => ENV["PATH"],
:command => "touch %s" % newfile,
:logoutput => true,
:refreshonly => true,
:subscribe => [file, exec1],
:title => "two"
)
assert_apply(file, exec1, exec2)
assert(FileTest.exists?(newfile), "Refresh file did not get created")
end
# Make sure that unscheduled and untagged objects still respond to events
def test_unscheduled_and_untagged_response
Puppet::Type.type(:schedule).mkdefaultschedules
Puppet[:ignoreschedules] = false
file = Puppet::Type.type(:file).new(
:name => tempfile(),
:ensure => "file",
:backup => false
)
fname = tempfile()
exec = Puppet::Type.type(:exec).new(
:name => "touch %s" % fname,
:path => "/usr/bin:/bin",
:schedule => "monthly",
- :subscribe => Puppet::Resource::Reference.new("file", file.name)
+ :subscribe => Puppet::Resource.new("file", file.name)
)
config = mk_catalog(file, exec)
# Run it once
assert_apply(config)
assert(FileTest.exists?(fname), "File did not get created")
assert(!exec.scheduled?, "Exec is somehow scheduled")
# Now remove it, so it can get created again
File.unlink(fname)
file[:content] = "some content"
assert_events([:content_changed, :restarted], config)
assert(FileTest.exists?(fname), "File did not get recreated")
# Now remove it, so it can get created again
File.unlink(fname)
# And tag our exec
exec.tag("testrun")
# And our file, so it runs
file.tag("norun")
Puppet[:tags] = "norun"
file[:content] = "totally different content"
assert(! file.insync?(file.retrieve), "Uh, file is in sync?")
assert_events([:content_changed, :restarted], config)
assert(FileTest.exists?(fname), "File did not get recreated")
end
def test_failed_reqs_mean_no_run
exec = Puppet::Type.type(:exec).new(
:command => "/bin/mkdir /this/path/cannot/possibly/exit",
:title => "mkdir"
)
file1 = Puppet::Type.type(:file).new(
:title => "file1",
:path => tempfile(),
:require => exec,
:ensure => :file
)
file2 = Puppet::Type.type(:file).new(
:title => "file2",
:path => tempfile(),
:require => file1,
:ensure => :file
)
config = mk_catalog(exec, file1, file2)
assert_apply(config)
assert(! FileTest.exists?(file1[:path]),
"File got created even tho its dependency failed")
assert(! FileTest.exists?(file2[:path]),
"File got created even tho its deep dependency failed")
end
end
# We need to generate resources before we prefetch them, else generated
# resources that require prefetching don't work.
def test_generate_before_prefetch
config = mk_catalog()
trans = Puppet::Transaction.new(config)
generate = nil
prefetch = nil
trans.expects(:generate).with { |*args| generate = Time.now; true }
trans.expects(:prefetch).with { |*args| ! generate.nil? }
trans.prepare
return
resource = Puppet::Type.type(:file).new :ensure => :present, :path => tempfile()
other_resource = mock 'generated'
def resource.generate
[other_resource]
end
config = mk_catalog(yay, rah)
trans = Puppet::Transaction.new(config)
assert_nothing_raised do
trans.generate
end
%w{ya ra y r}.each do |name|
assert(trans.catalog.vertex?(Puppet::Type.type(:generator)[name]),
"Generated %s was not a vertex" % name)
assert($finished.include?(name), "%s was not finished" % name)
end
# Now make sure that cleanup gets rid of those generated types.
assert_nothing_raised do
trans.cleanup
end
end
def test_ignore_tags?
config = Puppet::Resource::Catalog.new
config.host_config = true
transaction = Puppet::Transaction.new(config)
assert(! transaction.ignore_tags?, "Ignoring tags when applying a host catalog")
config.host_config = false
transaction = Puppet::Transaction.new(config)
assert(transaction.ignore_tags?, "Not ignoring tags when applying a non-host catalog")
end
def test_missing_tags?
resource = Puppet::Type.type(:notify).new :title => "foo"
resource.stubs(:tagged?).returns true
config = Puppet::Resource::Catalog.new
# Mark it as a host config so we don't care which test is first
config.host_config = true
transaction = Puppet::Transaction.new(config)
assert(! transaction.missing_tags?(resource), "Considered a resource to be missing tags when none are set")
# host catalogs pay attention to tags, no one else does.
Puppet[:tags] = "three,four"
config.host_config = false
transaction = Puppet::Transaction.new(config)
assert(! transaction.missing_tags?(resource), "Considered a resource to be missing tags when not running a host catalog")
#
config.host_config = true
transaction = Puppet::Transaction.new(config)
assert(! transaction.missing_tags?(resource), "Considered a resource to be missing tags when running a host catalog and all tags are present")
transaction = Puppet::Transaction.new(config)
resource.stubs :tagged? => false
assert(transaction.missing_tags?(resource), "Considered a resource not to be missing tags when running a host catalog and tags are missing")
end
# Make sure changes in contained files still generate callback events.
def test_generated_callbacks
dir = tempfile()
maker = tempfile()
Dir.mkdir(dir)
file = File.join(dir, "file")
File.open(file, "w") { |f| f.puts "" }
File.chmod(0644, file)
File.chmod(0755, dir) # So only the child file causes a change
dirobj = Puppet::Type.type(:file).new :mode => "755", :recurse => true, :path => dir
exec = Puppet::Type.type(:exec).new :title => "make",
:command => "touch #{maker}", :path => ENV['PATH'], :refreshonly => true,
:subscribe => dirobj
assert_apply(dirobj, exec)
assert(FileTest.exists?(maker), "Did not make callback file")
end
# Testing #401 -- transactions are calling refresh() on classes that don't support it.
def test_callback_availability
$called = []
klass = Puppet::Type.newtype(:norefresh) do
newparam(:name, :namevar => true) {}
def method_missing(method, *args)
$called << method
end
end
cleanup do
$called = nil
Puppet::Type.rmtype(:norefresh)
end
file = Puppet::Type.type(:file).new :path => tempfile(), :content => "yay"
one = klass.new :name => "one", :subscribe => file
assert_apply(file, one)
assert(! $called.include?(:refresh), "Called refresh when it wasn't set as a method")
end
# Testing #437 - cyclic graphs should throw failures.
def test_fail_on_cycle
one = Puppet::Type.type(:exec).new(:name => "/bin/echo one")
two = Puppet::Type.type(:exec).new(:name => "/bin/echo two")
one[:require] = two
two[:require] = one
config = mk_catalog(one, two)
trans = Puppet::Transaction.new(config)
assert_raise(Puppet::Error) do
trans.prepare
end
end
def test_errors_during_generation
type = Puppet::Type.newtype(:failer) do
newparam(:name) {}
def eval_generate
raise ArgumentError, "Invalid value"
end
def generate
raise ArgumentError, "Invalid value"
end
end
cleanup { Puppet::Type.rmtype(:failer) }
obj = type.new(:name => "testing")
assert_apply(obj)
end
def test_self_refresh_causes_triggering
type = Puppet::Type.newtype(:refresher, :self_refresh => true) do
attr_accessor :refreshed, :testing
newparam(:name) {}
newproperty(:testing) do
def retrieve
:eh
end
def sync
# noop
:ran_testing
end
end
def refresh
@refreshed = true
end
end
cleanup { Puppet::Type.rmtype(:refresher)}
obj = type.new(:name => "yay", :testing => "cool")
assert(! obj.insync?(obj.retrieve), "fake object is already in sync")
# Now make sure it gets refreshed when the change happens
assert_apply(obj)
assert(obj.refreshed, "object was not refreshed during transaction")
end
# Testing #433
def test_explicit_dependencies_beat_automatic
# Create a couple of different resource sets that have automatic relationships and make sure the manual relationships win
rels = {}
# First users and groups
group = Puppet::Type.type(:group).new(:name => nonrootgroup.name, :ensure => :present)
user = Puppet::Type.type(:user).new(:name => nonrootuser.name, :ensure => :present, :gid => group.title)
# Now add the explicit relationship
group[:require] = user
rels[group] = user
# Now files
d = tempfile()
f = File.join(d, "file")
file = Puppet::Type.type(:file).new(:path => f, :content => "yay")
dir = Puppet::Type.type(:file).new(:path => d, :ensure => :directory, :require => file)
rels[dir] = file
rels.each do |after, before|
config = mk_catalog(before, after)
trans = Puppet::Transaction.new(config)
str = "from %s to %s" % [before, after]
assert_nothing_raised("Failed to create graph %s" % str) do
trans.prepare
end
graph = trans.relationship_graph
assert(graph.edge?(before, after), "did not create manual relationship %s" % str)
assert(! graph.edge?(after, before), "created automatic relationship %s" % str)
end
end
# #542 - make sure resources in noop mode still notify their resources,
# so that users know if a service will get restarted.
def test_noop_with_notify
path = tempfile
epath = tempfile
spath = tempfile
file = Puppet::Type.type(:file).new(:path => path, :ensure => :file,
:title => "file")
exec = Puppet::Type.type(:exec).new(:command => "touch %s" % epath,
:path => ENV["PATH"], :subscribe => file, :refreshonly => true,
:title => 'exec1')
exec2 = Puppet::Type.type(:exec).new(:command => "touch %s" % spath,
:path => ENV["PATH"], :subscribe => exec, :refreshonly => true,
:title => 'exec2')
Puppet[:noop] = true
assert(file.noop, "file not in noop")
assert(exec.noop, "exec not in noop")
@logs.clear
assert_apply(file, exec, exec2)
assert(! FileTest.exists?(path), "Created file in noop")
assert(! FileTest.exists?(epath), "Executed exec in noop")
assert(! FileTest.exists?(spath), "Executed second exec in noop")
assert(@logs.detect { |l|
l.message =~ /should be/ and l.source == file.property(:ensure).path},
"did not log file change")
assert(@logs.detect { |l|
l.message =~ /Would have/ and l.source == exec.path },
"did not log first exec trigger")
assert(@logs.detect { |l|
l.message =~ /Would have/ and l.source == exec2.path },
"did not log second exec trigger")
end
def test_only_stop_purging_with_relations
files = []
paths = []
3.times do |i|
path = tempfile
paths << path
file = Puppet::Type.type(:file).new(:path => path, :ensure => :absent,
:backup => false, :title => "file%s" % i)
File.open(path, "w") { |f| f.puts "" }
files << file
end
files[0][:ensure] = :file
files[0][:require] = files[1..2]
# Mark the second as purging
files[1].purging
assert_apply(*files)
assert(FileTest.exists?(paths[1]), "Deleted required purging file")
assert(! FileTest.exists?(paths[2]), "Did not delete non-purged file")
end
def test_flush
$state = :absent
$flushed = 0
type = Puppet::Type.newtype(:flushtest) do
newparam(:name)
newproperty(:ensure) do
newvalues :absent, :present, :other
def retrieve
$state
end
def set(value)
$state = value
:thing_changed
end
end
def flush
$flushed += 1
end
end
cleanup { Puppet::Type.rmtype(:flushtest) }
obj = type.new(:name => "test", :ensure => :present)
# first make sure it runs through and flushes
assert_apply(obj)
assert_equal(:present, $state, "Object did not make a change")
assert_equal(1, $flushed, "object was not flushed")
# Now run a noop and make sure we don't flush
obj[:ensure] = "other"
obj[:noop] = true
assert_apply(obj)
assert_equal(:present, $state, "Object made a change in noop")
assert_equal(1, $flushed, "object was flushed in noop")
end
end
diff --git a/test/ral/type/exec.rb b/test/ral/type/exec.rb
index c533aff1c..27a3de4d1 100755
--- a/test/ral/type/exec.rb
+++ b/test/ral/type/exec.rb
@@ -1,753 +1,753 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../lib/puppettest'
require 'puppettest'
class TestExec < Test::Unit::TestCase
include PuppetTest
def test_numvsstring
[0, "0"].each { |val|
command = nil
output = nil
assert_nothing_raised {
command = Puppet::Type.type(:exec).new(
:command => "/bin/echo",
:returns => val
)
}
assert_events([:executed_command], command)
}
end
def test_path_or_qualified
command = nil
output = nil
assert_raise(Puppet::Error) {
command = Puppet::Type.type(:exec).new(
:command => "echo"
)
}
assert_nothing_raised {
command = Puppet::Type.type(:exec).new(
:command => "echo",
:path => "/usr/bin:/bin:/usr/sbin:/sbin"
)
}
assert_nothing_raised {
command = Puppet::Type.type(:exec).new(
:command => "/bin/echo"
)
}
assert_nothing_raised {
command = Puppet::Type.type(:exec).new(
:command => "/bin/echo",
:path => "/usr/bin:/bin:/usr/sbin:/sbin"
)
}
end
def test_nonzero_returns
assert_nothing_raised {
command = Puppet::Type.type(:exec).new(
:command => "mkdir /this/directory/does/not/exist",
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:returns => 1
)
}
assert_nothing_raised {
command = Puppet::Type.type(:exec).new(
:command => "touch /etc",
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:returns => 1
)
}
assert_nothing_raised {
command = Puppet::Type.type(:exec).new(
:command => "thiscommanddoesnotexist",
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:returns => 127
)
}
end
def test_cwdsettings
command = nil
dir = "/tmp"
wd = Dir.chdir(dir) {
Dir.getwd
}
assert_nothing_raised {
command = Puppet::Type.type(:exec).new(
:command => "pwd",
:cwd => dir,
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:returns => 0
)
}
assert_events([:executed_command], command)
assert_equal(wd,command.output.chomp)
end
def test_refreshonly_functional
file = nil
cmd = nil
tmpfile = tempfile()
@@tmpfiles.push tmpfile
trans = nil
file = Puppet::Type.type(:file).new(
:path => tmpfile,
:content => "yay"
)
# Get the file in sync
assert_apply(file)
# Now make an exec
maker = tempfile()
assert_nothing_raised {
cmd = Puppet::Type.type(:exec).new(
:command => "touch %s" % maker,
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:subscribe => file,
:refreshonly => true
)
}
assert(cmd, "did not make exec")
assert_nothing_raised do
assert(! cmd.check, "Check passed when refreshonly is set")
end
assert_events([], file, cmd)
assert(! FileTest.exists?(maker), "made file without refreshing")
# Now change our content, so we throw a refresh
file[:content] = "yayness"
assert_events([:content_changed, :restarted], file, cmd)
assert(FileTest.exists?(maker), "file was not made in refresh")
end
def test_refreshonly
cmd = true
assert_nothing_raised {
cmd = Puppet::Type.type(:exec).new(
:command => "pwd",
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:refreshonly => true
)
}
# Checks should always fail when refreshonly is enabled
assert(!cmd.check, "Check passed with refreshonly true")
# Now make sure it passes if we pass in "true"
assert(cmd.check(true), "Check failed with refreshonly true while refreshing")
# Now set it to false
cmd[:refreshonly] = false
assert(cmd.check, "Check failed with refreshonly false")
end
def test_creates
file = tempfile()
exec = nil
assert(! FileTest.exists?(file), "File already exists")
assert_nothing_raised {
exec = Puppet::Type.type(:exec).new(
:command => "touch %s" % file,
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:creates => file
)
}
comp = mk_catalog("createstest", exec)
assert_events([:executed_command], comp, "creates")
assert_events([], comp, "creates")
end
# Verify that we can download the file that we're going to execute.
def test_retrievethenmkexe
exe = tempfile()
oexe = tempfile()
sh = %x{which sh}
File.open(exe, "w") { |f| f.puts "#!#{sh}\necho yup" }
file = Puppet::Type.type(:file).new(
:path => oexe,
:source => exe,
:mode => 0755
)
exec = Puppet::Type.type(:exec).new(
:command => oexe,
- :require => Puppet::Resource::Reference.new(:file, oexe)
+ :require => Puppet::Resource.new(:file, oexe)
)
comp = mk_catalog("Testing", file, exec)
assert_events([:file_created, :executed_command], comp)
end
# Verify that we auto-require any managed scripts.
def test_autorequire_files
exe = tempfile()
oexe = tempfile()
sh = %x{which sh}
File.open(exe, "w") { |f| f.puts "#!#{sh}\necho yup" }
file = Puppet::Type.type(:file).new(
:path => oexe,
:source => exe,
:mode => 755
)
basedir = File.dirname(oexe)
baseobj = Puppet::Type.type(:file).new(
:path => basedir,
:source => exe,
:mode => 755
)
ofile = Puppet::Type.type(:file).new(
:path => exe,
:mode => 755
)
exec = Puppet::Type.type(:exec).new(
:command => oexe,
:path => ENV["PATH"],
:cwd => basedir
)
cat = Puppet::Type.type(:exec).new(
:command => "cat %s %s" % [exe, oexe],
:path => ENV["PATH"]
)
catalog = mk_catalog(file, baseobj, ofile, exec, cat)
rels = nil
assert_nothing_raised do
rels = exec.autorequire
end
# Verify we get the script itself
assert(rels.detect { |r| r.source == file }, "Exec did not autorequire its command")
# Verify we catch the cwd
assert(rels.detect { |r| r.source == baseobj }, "Exec did not autorequire its cwd")
# Verify we don't require ourselves
assert(! rels.detect { |r| r.source == ofile }, "Exec incorrectly required mentioned file")
# We not longer autorequire inline files
assert_nothing_raised do
rels = cat.autorequire
end
assert(! rels.detect { |r| r.source == ofile }, "Exec required second inline file")
assert(! rels.detect { |r| r.source == file }, "Exec required inline file")
end
def test_ifonly
afile = tempfile()
bfile = tempfile()
exec = nil
assert_nothing_raised {
exec = Puppet::Type.type(:exec).new(
:command => "touch %s" % bfile,
:onlyif => "test -f %s" % afile,
:path => ENV['PATH']
)
}
assert_events([], exec)
system("touch %s" % afile)
assert_events([:executed_command], exec)
assert_events([:executed_command], exec)
system("rm %s" % afile)
assert_events([], exec)
end
def test_unless
afile = tempfile()
bfile = tempfile()
exec = nil
assert_nothing_raised {
exec = Puppet::Type.type(:exec).new(
:command => "touch %s" % bfile,
:unless => "test -f %s" % afile,
:path => ENV['PATH']
)
}
comp = mk_catalog(exec)
assert_events([:executed_command], comp)
assert_events([:executed_command], comp)
system("touch %s" % afile)
assert_events([], comp)
assert_events([], comp)
system("rm %s" % afile)
assert_events([:executed_command], comp)
assert_events([:executed_command], comp)
end
if Puppet::Util::SUIDManager.uid == 0
# Verify that we can execute commands as a special user
def mknverify(file, user, group = nil, id = true)
File.umask(0022)
args = {
:command => "touch %s" % file,
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
}
if user
#Puppet.warning "Using user %s" % user.name
if id
# convert to a string, because that's what the object expects
args[:user] = user.uid.to_s
else
args[:user] = user.name
end
end
if group
#Puppet.warning "Using group %s" % group.name
if id
args[:group] = group.gid.to_s
else
args[:group] = group.name
end
end
exec = nil
assert_nothing_raised {
exec = Puppet::Type.type(:exec).new(args)
}
comp = mk_catalog("usertest", exec)
assert_events([:executed_command], comp, "usertest")
assert(FileTest.exists?(file), "File does not exist")
if user
assert_equal(user.uid, File.stat(file).uid, "File UIDs do not match")
end
# We can't actually test group ownership, unfortunately, because
# behaviour changes wildlly based on platform.
Puppet::Type.allclear
end
def test_userngroup
file = tempfile()
[
[nonrootuser()], # just user, by name
[nonrootuser(), nil, true], # user, by uid
[nil, nonrootgroup()], # just group
[nil, nonrootgroup(), true], # just group, by id
[nonrootuser(), nonrootgroup()], # user and group, by name
[nonrootuser(), nonrootgroup(), true], # user and group, by id
].each { |ary|
mknverify(file, *ary) {
}
}
end
end
def test_logoutput
exec = nil
assert_nothing_raised {
exec = Puppet::Type.type(:exec).new(
:title => "logoutputesting",
:path => "/usr/bin:/bin",
:command => "echo logoutput is false",
:logoutput => false
)
}
assert_apply(exec)
assert_nothing_raised {
exec[:command] = "echo logoutput is true"
exec[:logoutput] = true
}
assert_apply(exec)
assert_nothing_raised {
exec[:command] = "echo logoutput is on_failure"
exec[:logoutput] = "on_failure"
}
assert_apply(exec)
end
def test_execthenfile
exec = nil
file = nil
basedir = tempfile()
path = File.join(basedir, "subfile")
assert_nothing_raised {
exec = Puppet::Type.type(:exec).new(
:title => "mkdir",
:path => "/usr/bin:/bin",
:creates => basedir,
:command => "mkdir %s; touch %s" % [basedir, path]
)
}
assert_nothing_raised {
file = Puppet::Type.type(:file).new(
:path => basedir,
:recurse => true,
:mode => "755",
- :require => Puppet::Resource::Reference.new("exec", "mkdir")
+ :require => Puppet::Resource.new("exec", "mkdir")
)
}
comp = mk_catalog(file, exec)
comp.finalize
assert_events([:executed_command, :mode_changed], comp)
assert(FileTest.exists?(path), "Exec ran first")
assert(File.stat(path).mode & 007777 == 0755)
end
# Make sure all checks need to be fully qualified.
def test_falsevals
exec = nil
assert_nothing_raised do
exec = Puppet::Type.type(:exec).new(
:command => "/bin/touch yayness"
)
end
Puppet::Type.type(:exec).checks.each do |check|
klass = Puppet::Type.type(:exec).paramclass(check)
next if klass.value_collection.values.include? :false
assert_raise(Puppet::Error, "Check '%s' did not fail on false" % check) do
exec[check] = false
end
end
end
def test_createcwdandexe
exec1 = exec2 = nil
dir = tempfile()
file = tempfile()
assert_nothing_raised {
exec1 = Puppet::Type.type(:exec).new(
:title => "one",
:path => ENV["PATH"],
:command => "mkdir #{dir}"
)
}
assert_nothing_raised("Could not create exec w/out existing cwd") {
exec2 = Puppet::Type.type(:exec).new(
:title => "two",
:path => ENV["PATH"],
:command => "touch #{file}",
:cwd => dir
)
}
# Throw a check in there with our cwd and make sure it works
assert_nothing_raised("Could not check with a missing cwd") do
exec2[:unless] = "test -f /this/file/does/not/exist"
exec2.retrieve
end
assert_raise(Puppet::Error) do
exec2.property(:returns).sync
end
assert_nothing_raised do
exec2[:require] = exec1
end
assert_apply(exec1, exec2)
assert(FileTest.exists?(file))
end
def test_checkarrays
exec = nil
file = tempfile()
test = "test -f #{file}"
assert_nothing_raised {
exec = Puppet::Type.type(:exec).new(
:path => ENV["PATH"],
:command => "touch #{file}"
)
}
assert_nothing_raised {
exec[:unless] = test
}
assert_nothing_raised {
assert(exec.check, "Check did not pass")
}
assert_nothing_raised {
exec[:unless] = [test, test]
}
assert_nothing_raised {
exec.finish
}
assert_nothing_raised {
assert(exec.check, "Check did not pass")
}
assert_apply(exec)
assert_nothing_raised {
assert(! exec.check, "Check passed")
}
end
def test_missing_checks_cause_failures
# Solaris's sh exits with 1 here instead of 127
return if Facter.value(:operatingsystem) == "Solaris"
exec = Puppet::Type.type(:exec).new(
:command => "echo true",
:path => ENV["PATH"],
:onlyif => "/bin/nosuchthingexists"
)
assert_raise(ArgumentError, "Missing command did not raise error") {
exec.run("/bin/nosuchthingexists")
}
end
def test_envparam
exec = Puppet::Type.newexec(
:command => "echo $envtest",
:path => ENV["PATH"],
:env => "envtest=yayness"
)
assert(exec, "Could not make exec")
output = status = nil
assert_nothing_raised {
output, status = exec.run("echo $envtest")
}
assert_equal("yayness\n", output)
# Now check whether we can do multiline settings
assert_nothing_raised do
exec[:env] = "envtest=a list of things
and stuff"
end
output = status = nil
assert_nothing_raised {
output, status = exec.run('echo "$envtest"')
}
assert_equal("a list of things\nand stuff\n", output)
# Now test arrays
assert_nothing_raised do
exec[:env] = ["funtest=A", "yaytest=B"]
end
output = status = nil
assert_nothing_raised {
output, status = exec.run('echo "$funtest" "$yaytest"')
}
assert_equal("A B\n", output)
end
def test_environmentparam
exec = Puppet::Type.newexec(
:command => "echo $environmenttest",
:path => ENV["PATH"],
:environment => "environmenttest=yayness"
)
assert(exec, "Could not make exec")
output = status = nil
assert_nothing_raised {
output, status = exec.run("echo $environmenttest")
}
assert_equal("yayness\n", output)
# Now check whether we can do multiline settings
assert_nothing_raised do
exec[:environment] = "environmenttest=a list of things
and stuff"
end
output = status = nil
assert_nothing_raised {
output, status = exec.run('echo "$environmenttest"')
}
assert_equal("a list of things\nand stuff\n", output)
# Now test arrays
assert_nothing_raised do
exec[:environment] = ["funtest=A", "yaytest=B"]
end
output = status = nil
assert_nothing_raised {
output, status = exec.run('echo "$funtest" "$yaytest"')
}
assert_equal("A B\n", output)
end
def test_timeout
exec = Puppet::Type.type(:exec).new(:command => "sleep 1", :path => ENV["PATH"], :timeout => "0.2")
time = Time.now
assert_raise(Timeout::Error) {
exec.run("sleep 1")
}
Puppet.info "%s seconds, vs a timeout of %s" % [Time.now.to_f - time.to_f, exec[:timeout]]
assert_apply(exec)
end
# Testing #470
def test_run_as_created_user
exec = nil
if Process.uid == 0
user = "nosuchuser"
assert_nothing_raised("Could not create exec with non-existent user") do
exec = Puppet::Type.type(:exec).new(
:command => "/bin/echo yay",
:user => user
)
end
end
# Now try the group
group = "nosuchgroup"
assert_nothing_raised("Could not create exec with non-existent user") do
exec = Puppet::Type.type(:exec).new(
:command => "/bin/echo yay",
:group => group
)
end
end
# make sure paths work both as arrays and strings
def test_paths_as_arrays
path = %w{/usr/bin /usr/sbin /sbin}
exec = nil
assert_nothing_raised("Could not use an array for the path") do
exec = Puppet::Type.type(:exec).new(:command => "echo yay",
:path => path)
end
assert_equal(path, exec[:path], "array-based path did not match")
assert_nothing_raised("Could not use a string for the path") do
exec = Puppet::Type.type(:exec).new(:command => "echo yay",
:path => path.join(":"))
end
assert_equal(path, exec[:path], "string-based path did not match")
assert_nothing_raised("Could not use a colon-separated strings in an array for the path") do
exec = Puppet::Type.type(:exec).new(:command => "echo yay",
:path => ["/usr/bin", "/usr/sbin:/sbin"])
end
assert_equal(path, exec[:path], "colon-separated array path did not match")
end
def test_checks_apply_to_refresh
file = tempfile()
maker = tempfile()
exec = Puppet::Type.type(:exec).new(
:title => "maker",
:command => "touch #{maker}",
:path => ENV["PATH"]
)
# Make sure it runs normally
assert_apply(exec)
assert(FileTest.exists?(maker), "exec did not run")
File.unlink(maker)
# Now make sure it refreshes
assert_nothing_raised("Failed to refresh exec") do
exec.refresh
end
assert(FileTest.exists?(maker), "exec did not run refresh")
File.unlink(maker)
# Now add the checks
exec[:creates] = file
# Make sure it runs when the file doesn't exist
assert_nothing_raised("Failed to refresh exec") do
exec.refresh
end
assert(FileTest.exists?(maker), "exec did not refresh when checks passed")
File.unlink(maker)
# Now create the file and make sure it doesn't refresh
File.open(file, "w") { |f| f.puts "" }
assert_nothing_raised("Failed to refresh exec") do
exec.refresh
end
assert(! FileTest.exists?(maker), "exec refreshed with failing checks")
end
def test_explicit_refresh
refresher = tempfile()
maker = tempfile()
exec = Puppet::Type.type(:exec).new(
:title => "maker",
:command => "touch #{maker}",
:path => ENV["PATH"]
)
# Call refresh normally
assert_nothing_raised do
exec.refresh
end
# Make sure it created the normal file
assert(FileTest.exists?(maker), "normal refresh did not work")
File.unlink(maker)
# Now reset refresh, and make sure it wins
assert_nothing_raised("Could not set refresh parameter") do
exec[:refresh] = "touch #{refresher}"
end
assert_nothing_raised do
exec.refresh
end
# Make sure it created the normal file
assert(FileTest.exists?(refresher), "refresh param was ignored")
assert(! FileTest.exists?(maker), "refresh param also ran command")
end
if Puppet.features.root?
def test_autorequire_user
user = Puppet::Type.type(:user).new(:name => "yay")
exec = Puppet::Type.type(:exec).new(:command => "/bin/echo fun", :user => "yay")
rels = nil
assert_nothing_raised("Could not evaluate autorequire") do
rels = exec.autorequire
end
assert(rels.find { |r| r.source == user and r.target == exec }, "Exec did not autorequire user")
end
end
end