Page MenuHomePhorge

No OneTemporary

diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/compiler.rb
index fdabd05c9..6e8e3d26b 100644
--- a/lib/puppet/parser/compiler.rb
+++ b/lib/puppet/parser/compiler.rb
@@ -1,489 +1,489 @@
# 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}"
ensure
# We get these from the environment and only cache them in a thread
# variable for the duration of the compilation.
Thread.current[:known_resource_types] = nil
Thread.current[:env_module_directories] = nil
end
attr_reader :node, :facts, :collections, :catalog, :node_scope, :resources, :relationships
# Add a collection to the global list.
def add_collection(coll)
@collections << coll
end
def add_relationship(dep)
@relationships << dep
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)
# Add our container edge. If we're a class, then we get treated specially - we can
# control the stage that the class is applied in. Otherwise, we just
# get added to our parent container.
return if resource.type.to_s.downcase == "stage"
if resource.type.to_s.downcase != "class"
raise ArgumentError, "Only classes can set 'stage'; normal resources like #{resource} cannot change run stage" if resource[:stage]
return @catalog.add_edge(scope.resource, resource)
end
unless stage = @catalog.resource(:stage, resource[:stage] || (scope && scope.resource && scope.resource[:stage]) || :main)
raise ArgumentError, "Could not find stage #{resource[:stage] || :main} specified by #{resource}"
end
resource[:stage] ||= stage.title unless stage.title == :main
@catalog.add_edge(stage, resource)
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
@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
create_settings_scope
evaluate_main
evaluate_ast_node
evaluate_node_classes
evaluate_generators
finish
fail_on_unevaluated
@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)
@environment = (node.environment and node.environment != "") ? node.environment : nil
end
Puppet::Node::Environment.current = @environment
@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)
raise Puppet::DevError, "No source for scope passed to evaluate_classes" unless scope.source
found = []
param_classes = nil
# if we are a param class, save the classes hash
# and transform classes to be the keys
if classes.class == Hash
param_classes = classes
classes = classes.keys
end
classes.each do |name|
# If we can find the class, then make a resource that will evaluate it.
if klass = scope.find_hostclass(name)
if param_classes
resource = klass.ensure_in_catalog(scope, param_classes[name] || {})
else
found << name and next if scope.class_scope(klass)
resource = klass.ensure_in_catalog(scope)
end
# 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 #{name} for #{node.name}"
+ Puppet.warning "Could not find class #{name} for #{node.name}"
@catalog.tag(name)
end
end
found
end
def evaluate_relationships
@relationships.each { |rel| rel.evaluate(catalog) }
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 #{param}"
end
end
initvars
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 '#{node.names.join(", ")}'"
end
# Create a resource to model this node, and then add it to the list
# of resources.
resource = astnode.ensure_in_catalog(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
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
!unevaluated_resources.each { |resource| resource.evaluate }.empty?
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, ""))
@topscope.source = @main
@main_resource = Puppet::Parser::Resource.new("class", :main, :scope => @topscope, :source => @main)
@topscope.resource = @main_resource
add_resource(@topscope, @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
raise Puppet::ParseError, "Failed to realize virtual resources #{remaining.join(', ')}" unless remaining.empty?
end
# Make sure all of our resources and such have done any last work
# necessary.
def finish
evaluate_relationships
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
add_resource_metaparams
end
def add_resource_metaparams
unless main = catalog.resource(:class, :main)
raise "Couldn't find main"
end
names = []
Puppet::Type.eachmetaparam do |name|
next if Puppet::Parser::Resource.relationship_parameter?(name)
names << name
end
data = {}
catalog.walk(main, :out) do |source, target|
if source_data = data[source] || metaparams_as_data(source, names)
# only store anything in the data hash if we've actually got
# data
data[source] ||= source_data
source_data.each do |param, value|
target[param] = value if target[param].nil?
end
data[target] = source_data.merge(metaparams_as_data(target, names))
end
target.tag(*(source.tags))
end
end
def metaparams_as_data(resource, params)
data = nil
params.each do |param|
unless resource[param].nil?
# Because we could be creating a hash for every resource,
# and we actually probably don't often have any data here at all,
# we're optimizing a bit by only creating a hash if there's
# any data to put in it.
data ||= {}
data[param] = resource[param]
end
end
data
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 = []
# The list of relationships to evaluate.
@relationships = []
# For maintaining the relationship between scopes and their resources.
@catalog = Puppet::Resource::Catalog.new(@node.name)
@catalog.version = known_resource_types.version
# Create our initial scope and a resource that will evaluate main.
@topscope = Puppet::Parser::Scope.new(:compiler => self)
@main_stage_resource = Puppet::Parser::Resource.new("stage", :main, :scope => @topscope)
@catalog.add_resource(@main_stage_resource)
# local resource array to maintain resource ordering
@resources = []
# Make sure any external node classes are in our class list
if @node.classes.class == Hash
@catalog.add_class(*@node.classes.keys)
else
@catalog.add_class(*@node.classes)
end
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
def create_settings_scope
unless settings_type = environment.known_resource_types.hostclass("settings")
settings_type = Puppet::Resource::Type.new :hostclass, "settings"
environment.known_resource_types.add(settings_type)
end
settings_resource = Puppet::Parser::Resource.new("class", "settings", :scope => @topscope)
settings_type.evaluate_code(settings_resource)
@catalog.add_resource(settings_resource)
scope = @topscope.class_scope(settings_type)
Puppet.settings.each do |name, setting|
next if name.to_s == "name"
scope.setvar name.to_s, environment[name]
end
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
# The order of these is significant for speed due to short-circuting
resources.reject { |resource| resource.evaluated? or resource.virtual? or resource.builtin_type? }
end
end
diff --git a/spec/unit/parser/compiler_spec.rb b/spec/unit/parser/compiler_spec.rb
index 687f2ecb9..261cfdec1 100755
--- a/spec/unit/parser/compiler_spec.rb
+++ b/spec/unit/parser/compiler_spec.rb
@@ -1,850 +1,849 @@
#!/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 [](attr)
return nil if attr == :stage
:main
end
def ref
"#{type.to_s.capitalize}[#{title}]"
end
def evaluated?
@evaluated
end
def builtin_type?
@builtin
end
def virtual?
@virtual
end
def evaluate
end
end
describe Puppet::Parser::Compiler do
def resource(type, title)
Puppet::Parser::Resource.new(type, title, :scope => @scope)
end
before :each do
@node = Puppet::Node.new "testnode"
@known_resource_types = Puppet::Resource::TypeCollection.new "development"
@compiler = Puppet::Parser::Compiler.new(@node)
@scope = Puppet::Parser::Scope.new(:compiler => @compiler, :source => stub('source'))
@scope_resource = Puppet::Parser::Resource.new(:file, "/my/file", :scope => @scope)
@scope.resource = @scope_resource
@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 =~ ['foo', 'bar']
end
it "should transform node class hashes into a class list" do
node = Puppet::Node.new("mynode")
node.classes = {'foo'=>{'one'=>'1'}, 'bar'=>{'two'=>'2'}}
compiler = Puppet::Parser::Compiler.new(node)
compiler.classlist.should =~ ['foo', 'bar']
end
it "should add a 'main' stage to the catalog" do
@compiler.catalog.resource(:stage, :main).should be_instance_of(Puppet::Parser::Resource)
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, :evaluate_relationships]
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 any parameterized classes named in the node" do
classes = {'foo'=>{'1'=>'one'}, 'bar'=>{'2'=>'two'}}
@node.stubs(:classes).returns(classes)
@compiler.expects(:evaluate_classes).with(classes, @compiler.topscope)
@compiler.compile
end
it "should evaluate the main class if it exists" do
compile_stub(:evaluate_main)
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)
@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)
end
it "should add an edge between the main stage and main class" do
@compiler.compile
(stage = @compiler.catalog.resource(:stage, "main")).should be_instance_of(Puppet::Parser::Resource)
(klass = @compiler.catalog.resource(:class, "")).should be_instance_of(Puppet::Parser::Resource)
@compiler.catalog.edge?(stage, klass).should be_true
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 = resource(:file, "testing")
@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 = resource(:file, "testing")
resource.stubs(:evaluated?).returns true
@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
describe "when finishing" do
before do
@compiler.send(:evaluate_main)
@catalog = @compiler.catalog
end
def add_resource(name, parent = nil)
resource = Puppet::Parser::Resource.new "file", name, :scope => @scope
@compiler.add_resource(@scope, resource)
@catalog.add_edge(parent, resource) if parent
resource
end
it "should call finish() on all resources" do
# Add a resource that does respond to :finish
resource = Puppet::Parser::Resource.new "file", "finish", :scope => @scope
resource.expects(:finish)
@compiler.add_resource(@scope, resource)
# And one that does not
dnf_resource = stub_everything "dnf", :ref => "File[dnf]", :type => "file"
@compiler.add_resource(@scope, dnf_resource)
@compiler.send(:finish)
end
it "should call finish() in add_resource order" do
resources = sequence('resources')
resource1 = add_resource("finish1")
resource1.expects(:finish).in_sequence(resources)
resource2 = add_resource("finish2")
resource2.expects(:finish).in_sequence(resources)
@compiler.send(:finish)
end
it "should add each container's metaparams to its contained resources" do
main = @catalog.resource(:class, :main)
main[:noop] = true
resource1 = add_resource("meh", main)
@compiler.send(:finish)
resource1[:noop].should be_true
end
it "should add metaparams recursively" do
main = @catalog.resource(:class, :main)
main[:noop] = true
resource1 = add_resource("meh", main)
resource2 = add_resource("foo", resource1)
@compiler.send(:finish)
resource2[:noop].should be_true
end
it "should prefer metaparams from immediate parents" do
main = @catalog.resource(:class, :main)
main[:noop] = true
resource1 = add_resource("meh", main)
resource2 = add_resource("foo", resource1)
resource1[:noop] = false
@compiler.send(:finish)
resource2[:noop].should be_false
end
it "should merge tags downward" do
main = @catalog.resource(:class, :main)
main.tag("one")
resource1 = add_resource("meh", main)
resource1.tag "two"
resource2 = add_resource("foo", resource1)
@compiler.send(:finish)
resource2.tags.should be_include("one")
resource2.tags.should be_include("two")
end
it "should work if only middle resources have metaparams set" do
main = @catalog.resource(:class, :main)
resource1 = add_resource("meh", main)
resource1[:noop] = true
resource2 = add_resource("foo", resource1)
@compiler.send(:finish)
resource2[:noop].should be_true
end
end
it "should return added resources in add order" do
resource1 = resource(:file, "yay")
@compiler.add_resource(@scope, resource1)
resource2 = resource(:file, "youpi")
@compiler.add_resource(@scope, resource2)
@compiler.resources.should == [resource1, resource2]
end
it "should add resources that do not conflict with existing resources" do
resource = resource(: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
path = Puppet.features.posix? ? "/foo" : "C:/foo"
file1 = Puppet::Type.type(:file).new :path => path
file2 = Puppet::Type.type(:file).new :path => path
@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 = resource(:file, "yay")
@compiler.add_resource(@scope, resource)
@compiler.catalog.should be_edge(@scope.resource, resource)
end
it "should add an edge to any specified stage for class resources" do
other_stage = resource(:stage, "other")
@compiler.add_resource(@scope, other_stage)
resource = resource(:class, "foo")
resource[:stage] = 'other'
@compiler.add_resource(@scope, resource)
@compiler.catalog.edge?(other_stage, resource).should be_true
end
it "should fail if a non-class resource attempts to set a stage" do
other_stage = resource(:stage, "other")
@compiler.add_resource(@scope, other_stage)
resource = resource(:file, "foo")
resource[:stage] = 'other'
lambda { @compiler.add_resource(@scope, resource) }.should raise_error(ArgumentError)
end
it "should fail if an unknown stage is specified" do
resource = resource(:class, "foo")
resource[:stage] = 'other'
lambda { @compiler.add_resource(@scope, resource) }.should raise_error(ArgumentError)
end
it "should add edges from the class resources to the parent's stage if no stage is specified" do
main = @compiler.catalog.resource(:stage, :main)
foo_stage = resource(:stage, :foo_stage)
@compiler.add_resource(@scope, foo_stage)
resource = resource(:class, "foo")
@scope.stubs(:resource).returns(:stage => :foo_stage)
@compiler.add_resource(@scope, resource)
@compiler.catalog.should be_edge(foo_stage, resource)
end
it "should add edges from top-level class resources to the main stage if no stage is specified" do
main = @compiler.catalog.resource(:stage, :main)
resource = resource(:class, "foo")
@compiler.add_resource(@scope, resource)
@compiler.catalog.should be_edge(main, resource)
end
it "should not add non-class resources that don't specify a stage to the 'main' stage" do
main = @compiler.catalog.resource(:stage, :main)
resource = resource(:file, "foo")
@compiler.add_resource(@scope, resource)
@compiler.catalog.should_not be_edge(main, resource)
end
it "should not add any parent-edges to stages" do
stage = resource(:stage, "other")
@compiler.add_resource(@scope, stage)
@scope.resource = resource(:class, "foo")
@compiler.catalog.edge?(@scope.resource, stage).should be_false
end
it "should not attempt to add stages to other stages" do
other_stage = resource(:stage, "other")
second_stage = resource(:stage, "second")
@compiler.add_resource(@scope, other_stage)
@compiler.add_resource(@scope, second_stage)
second_stage[:stage] = "other"
@compiler.catalog.edge?(other_stage, second_stage).should be_false
end
it "should have a method for looking up resources" do
resource = resource(:yay, "foo")
@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 = resource(:yay, "foo")
@compiler.add_resource(@scope, resource)
@compiler.findresource("Yay", "foo").should equal(resource)
end
it "should not evaluate virtual defined resources" do
resource = resource(:file, "testing")
resource.virtual = true
@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, 'Failed to realize virtual resources something'
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, 'Failed to realize virtual resources one, two'
end
end
describe "when evaluating relationships" do
it "should evaluate each relationship with its catalog" do
dep = stub 'dep'
dep.expects(:evaluate).with(@compiler.catalog)
@compiler.add_relationship dep
@compiler.evaluate_relationships
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
- # I wish it would fail
it "should log when it can't find class" do
klasses = {'foo'=>nil}
@node.classes = klasses
@compiler.topscope.stubs(:find_hostclass).with('foo').returns(nil)
- Puppet.expects(:info).with('Could not find class foo for testnode')
+ Puppet.expects(:warning).with('Could not find class foo for testnode')
@compiler.compile
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(:ensure_in_catalog).with(@scope)
@scope.stubs(:class_scope).with(@class)
@compiler.evaluate_classes(%w{myclass}, @scope)
end
it "should ensure each node class hash is in catalog and have appropriate parameters" do
klasses = {'foo'=>{'1'=>'one'}, 'bar::foo'=>{'2'=>'two'}, 'bar'=>{'1'=> [1,2,3], '2'=>{'foo'=>'bar'}}}
@node.classes = klasses
ast_obj = Puppet::Parser::AST::String.new(:value => 'foo')
klasses.each do |name, params|
klass = Puppet::Resource::Type.new(:hostclass, name, :arguments => {'1' => ast_obj, '2' => ast_obj})
@compiler.topscope.known_resource_types.add klass
end
catalog = @compiler.compile
catalog.classes.should =~ ['foo', 'bar::foo', 'settings', 'bar']
r1 = catalog.resources.detect {|r| r.title == 'Foo' }
r1.to_hash.should == {:'1' => 'one', :'2' => 'foo'}
r1.tags. should =~ ['class', 'foo']
r2 = catalog.resources.detect {|r| r.title == 'Bar::Foo' }
r2.to_hash.should == {:'1' => 'foo', :'2' => 'two'}
r2.tags.should =~ ['bar::foo', 'class', 'bar', 'foo']
r2 = catalog.resources.detect {|r| r.title == 'Bar' }
r2.to_hash.should == {:'1' => [1,2,3], :'2' => {'foo'=>'bar'}}
r2.tags.should =~ ['class', 'bar']
end
it "should ensure each node class is in catalog and has appropriate tags" do
klasses = ['bar::foo']
@node.classes = klasses
ast_obj = Puppet::Parser::AST::String.new(:value => 'foo')
klasses.each do |name|
klass = Puppet::Resource::Type.new(:hostclass, name, :arguments => {'1' => ast_obj, '2' => ast_obj})
@compiler.topscope.known_resource_types.add klass
end
catalog = @compiler.compile
r2 = catalog.resources.detect {|r| r.title == 'Bar::Foo' }
r2.tags.should =~ ['bar::foo', 'class', 'bar', 'foo']
end
it "should fail if required parameters are missing" do
klass = {'foo'=>{'1'=>'one'}}
@node.classes = klass
klass = Puppet::Resource::Type.new(:hostclass, 'foo', :arguments => {'1' => nil, '2' => nil})
@compiler.topscope.known_resource_types.add klass
lambda { @compiler.compile }.should raise_error Puppet::ParseError, "Must pass 2 to Class[Foo]"
end
it "should fail if invalid parameters are passed" do
klass = {'foo'=>{'3'=>'one'}}
@node.classes = klass
klass = Puppet::Resource::Type.new(:hostclass, 'foo', :arguments => {'1' => nil, '2' => nil})
@compiler.topscope.known_resource_types.add klass
lambda { @compiler.compile }.should raise_error Puppet::ParseError, "Invalid parameter 3"
end
it "should ensure class is in catalog without params" do
@node.classes = klasses = {'foo'=>nil}
foo = Puppet::Resource::Type.new(:hostclass, 'foo')
@compiler.topscope.known_resource_types.add foo
catalog = @compiler.compile
catalog.classes.should include 'foo'
end
it "should not evaluate the resources created for found classes unless asked" do
@compiler.catalog.stubs(:tag)
@resource.expects(:evaluate).never
@class.expects(:ensure_in_catalog).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(:ensure_in_catalog).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 :ensure_in_catalog
@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(:ensure_in_catalog).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(:ensure_in_catalog).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(:ensure_in_catalog).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", :ensure_in_catalog => 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.stubs :create_settings_scope
@compiler.compile
@compiler.topscope.should equal(scope)
end
end
describe "when managing resource overrides" do
before do
@override = stub 'override', :ref => "File[/foo]", :type => "my"
@resource = resource(:file, "/foo")
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, 'Could not find resource(s) File[/foo] for overriding'
end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 1, 9:22 AM (1 d, 14 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
10075779
Default Alt Text
(44 KB)

Event Timeline