diff --git a/acceptance/pending/ticket_6710_relationship_syntax_should_work_with_title_arrays.rb b/acceptance/tests/ticket_6710_relationship_syntax_should_work_with_title_arrays.rb similarity index 98% rename from acceptance/pending/ticket_6710_relationship_syntax_should_work_with_title_arrays.rb rename to acceptance/tests/ticket_6710_relationship_syntax_should_work_with_title_arrays.rb index 60da19e45..fdf688ea2 100644 --- a/acceptance/pending/ticket_6710_relationship_syntax_should_work_with_title_arrays.rb +++ b/acceptance/tests/ticket_6710_relationship_syntax_should_work_with_title_arrays.rb @@ -1,15 +1,15 @@ -test_name "#6710: Relationship syntax should work with title arraysA" +test_name "#6710: Relationship syntax should work with title arrays" # Jeff McCune # 2011-03-14 # # If bug 6710 is closed, then this manifests should apply cleanly. # There should be a many-to-many relationship established. # apply_manifest_on agents, %q{ notify { [ left_one, left_two ]: } -> notify { [ right_one, right_two ]: } notify { left: } -> notify { right: } notify { left_one_to_many: } -> notify { [ right_one_to_many_1, right_one_to_many_2 ]: } } diff --git a/lib/puppet/parser/ast/relationship.rb b/lib/puppet/parser/ast/relationship.rb index a7134a04f..37ead311f 100644 --- a/lib/puppet/parser/ast/relationship.rb +++ b/lib/puppet/parser/ast/relationship.rb @@ -1,60 +1,47 @@ require 'puppet/parser/ast' require 'puppet/parser/ast/branch' require 'puppet/parser/relationship' class Puppet::Parser::AST::Relationship < Puppet::Parser::AST::Branch RELATIONSHIP_TYPES = %w{-> <- ~> <~} attr_accessor :left, :right, :arrow, :type - def actual_left - chained? ? left.right : left - end - # Evaluate our object, but just return a simple array of the type # and name. def evaluate(scope) - if chained? - real_left = left.safeevaluate(scope) - left_dep = left_dep.shift if left_dep.is_a?(Array) - else - real_left = left.safeevaluate(scope) - end + real_left = left.safeevaluate(scope) real_right = right.safeevaluate(scope) source, target = sides2edge(real_left, real_right) - result = Puppet::Parser::Relationship.new(source, target, type) - scope.compiler.add_relationship(result) + scope.compiler.add_relationship Puppet::Parser::Relationship.new(source, target, type) + real_right end def initialize(left, right, arrow, args = {}) super(args) unless RELATIONSHIP_TYPES.include?(arrow) raise ArgumentError, "Invalid relationship type #{arrow.inspect}; valid types are #{RELATIONSHIP_TYPES.collect { |r| r.to_s }.join(", ")}" end @left, @right, @arrow = left, right, arrow end def type subscription? ? :subscription : :relationship end def sides2edge(left, right) out_edge? ? [left, right] : [right, left] end private - def chained? - left.is_a?(self.class) - end - def out_edge? ["->", "~>"].include?(arrow) end def subscription? ["~>", "<~"].include?(arrow) end end diff --git a/lib/puppet/parser/relationship.rb b/lib/puppet/parser/relationship.rb index 6190df52c..c88a7a2fb 100644 --- a/lib/puppet/parser/relationship.rb +++ b/lib/puppet/parser/relationship.rb @@ -1,43 +1,44 @@ class Puppet::Parser::Relationship attr_accessor :source, :target, :type PARAM_MAP = {:relationship => :before, :subscription => :notify} - def evaluate(catalog) - if source.is_a?(Puppet::Parser::Collector) - sources = source.collected.values - else - sources = [source] - end - if target.is_a?(Puppet::Parser::Collector) - targets = target.collected.values + def arrayify(resources) + case resources + when Puppet::Parser::Collector + resources.collected.values + when Array + resources else - targets = [target] + [resources] end - sources.each do |s| - targets.each do |t| + end + + def evaluate(catalog) + arrayify(source).each do |s| + arrayify(target).each do |t| mk_relationship(s, t, catalog) end end end def initialize(source, target, type) @source, @target, @type = source, target, type end def param_name PARAM_MAP[type] || raise(ArgumentError, "Invalid relationship type #{type}") end def mk_relationship(source, target, catalog) unless source_resource = catalog.resource(source.to_s) raise ArgumentError, "Could not find resource '#{source}' for relationship on '#{target}'" end unless target_resource = catalog.resource(target.to_s) raise ArgumentError, "Could not find resource '#{target}' for relationship from '#{source}'" end Puppet.debug "Adding relationship from #{source.to_s} to #{target.to_s} with '#{param_name}'" source_resource[param_name] ||= [] source_resource[param_name] << target.to_s end end diff --git a/spec/integration/parser/compiler_spec.rb b/spec/integration/parser/compiler_spec.rb index 582882d83..d98a8fc44 100755 --- a/spec/integration/parser/compiler_spec.rb +++ b/spec/integration/parser/compiler_spec.rb @@ -1,134 +1,223 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Parser::Compiler do before :each do @node = Puppet::Node.new "testnode" @scope_resource = stub 'scope_resource', :builtin? => true, :finish => nil, :ref => 'Class[main]' @scope = stub 'scope', :resource => @scope_resource, :source => mock("source") end after do Puppet.settings.clear end it "should be able to determine the configuration version from a local version control repository", :fails_on_windows => true do # This should always work, because we should always be # in the puppet repo when we run this. version = %x{git rev-parse HEAD}.chomp # REMIND: this fails on Windows due to #8410, re-enable the test when it is fixed Puppet.settings[:config_version] = 'git rev-parse HEAD' @parser = Puppet::Parser::Parser.new "development" @compiler = Puppet::Parser::Compiler.new(@node) @compiler.catalog.version.should == version end it "should not create duplicate resources when a class is referenced both directly and indirectly by the node classifier (4792)" do Puppet[:code] = <<-PP class foo { notify { foo_notify: } include bar } class bar { notify { bar_notify: } } PP @node.stubs(:classes).returns(['foo', 'bar']) catalog = Puppet::Parser::Compiler.compile(@node) catalog.resource("Notify[foo_notify]").should_not be_nil catalog.resource("Notify[bar_notify]").should_not be_nil end describe "when resolving class references" do it "should favor local scope, even if there's an included class in topscope" do Puppet[:code] = <<-PP class experiment { class baz { } notify {"x" : require => Class[Baz] } } class baz { } include baz include experiment include experiment::baz PP catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) notify_resource = catalog.resource( "Notify[x]" ) notify_resource[:require].title.should == "Experiment::Baz" end it "should favor local scope, even if there's an unincluded class in topscope" do Puppet[:code] = <<-PP class experiment { class baz { } notify {"x" : require => Class[Baz] } } class baz { } include experiment include experiment::baz PP catalog = Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) notify_resource = catalog.resource( "Notify[x]" ) notify_resource[:require].title.should == "Experiment::Baz" end end it "should recompute the version after input files are re-parsed" do Puppet[:code] = 'class foo { }' Time.stubs(:now).returns(1) node = Puppet::Node.new('mynode') Puppet::Parser::Compiler.compile(node).version.should == 1 Time.stubs(:now).returns(2) Puppet::Parser::Compiler.compile(node).version.should == 1 # no change because files didn't change Puppet::Resource::TypeCollection.any_instance.stubs(:stale?).returns(true).then.returns(false) # pretend change Puppet::Parser::Compiler.compile(node).version.should == 2 end ['class', 'define', 'node'].each do |thing| it "should not allow #{thing} inside evaluated conditional constructs" do Puppet[:code] = <<-PP if true { #{thing} foo { } notify { decoy: } } PP begin Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) raise "compilation should have raised Puppet::Error" rescue Puppet::Error => e e.message.should =~ /at line 2/ end end end it "should not allow classes inside unevaluated conditional constructs" do Puppet[:code] = <<-PP if false { class foo { } } PP lambda { Puppet::Parser::Compiler.compile(Puppet::Node.new("mynode")) }.should raise_error(Puppet::Error) end + + describe "when defining relationships" do + def extract_name(ref) + ref.sub(/File\[(\w+)\]/, '\1') + end + + let(:node) { Puppet::Node.new('mynode') } + let(:code) do + <<-MANIFEST + file { [a,b,c]: + mode => 0644, + } + file { [d,e]: + mode => 0755, + } + MANIFEST + end + let(:expected_relationships) { [] } + let(:expected_subscriptions) { [] } + + before :each do + Puppet[:code] = code + end + + after :each do + catalog = described_class.compile(node) + + resources = catalog.resources.select { |res| res.type == 'File' } + + actual_relationships, actual_subscriptions = [:before, :notify].map do |relation| + resources.map do |res| + dependents = Array(res[relation]) + dependents.map { |ref| [res.title, extract_name(ref)] } + end.inject(&:concat) + end + + actual_relationships.should =~ expected_relationships + actual_subscriptions.should =~ expected_subscriptions + end + + it "should create a relationship" do + code << "File[a] -> File[b]" + + expected_relationships << ['a','b'] + end + + it "should create a subscription" do + code << "File[a] ~> File[b]" + + expected_subscriptions << ['a', 'b'] + end + + it "should create relationships using title arrays" do + code << "File[a,b] -> File[c,d]" + + expected_relationships.concat [ + ['a', 'c'], + ['b', 'c'], + ['a', 'd'], + ['b', 'd'], + ] + end + + it "should create relationships using collection expressions" do + code << "File <| mode == 0644 |> -> File <| mode == 0755 |>" + + expected_relationships.concat [ + ['a', 'd'], + ['b', 'd'], + ['c', 'd'], + ['a', 'e'], + ['b', 'e'], + ['c', 'e'], + ] + end + + it "should create relationships using resource declarations" do + code << "file { l: } -> file { r: }" + + expected_relationships << ['l', 'r'] + end + + it "should chain relationships" do + code << "File[a] -> File[b] ~> File[c] <- File[d] <~ File[e]" + + expected_relationships << ['a', 'b'] << ['d', 'c'] + expected_subscriptions << ['b', 'c'] << ['e', 'd'] + end + end end