(Changes to parser.rb have been removed from this email.)

This is just a first, simplest version, but you can
now specify relationships directly in the language:

  File[/foo] -> Service[bar]

Specifies a normal dependency while:

  File[/foo] => Service[bar]

Specifies a subscription.

Next in the plan is to support complete resources or collections
on either side and to make the statements extendable:

  File[/foo] -> Package[baz] -> Service[bar]

Signed-off-by: Luke Kanies <[email protected]>
---
 lib/puppet/parser/ast.rb            |    1 +
 lib/puppet/parser/ast/dependency.rb |   31 +
 lib/puppet/parser/compiler.rb       |   15 +-
 lib/puppet/parser/dependency.rb     |   24 +
 lib/puppet/parser/grammar.ra        |    9 +-
 lib/puppet/parser/lexer.rb          |    4 +
 lib/puppet/parser/parser.rb         | 2276 ++++++++++++++++++-----------------
 spec/unit/parser/ast/dependency.rb  |   64 +
 spec/unit/parser/compiler.rb        |   11 +-
 spec/unit/parser/dependency.rb      |   60 +
 10 files changed, 1381 insertions(+), 1114 deletions(-)
 create mode 100644 lib/puppet/parser/ast/dependency.rb
 create mode 100644 lib/puppet/parser/dependency.rb
 create mode 100644 spec/unit/parser/ast/dependency.rb
 create mode 100644 spec/unit/parser/dependency.rb

diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb
index 6af7745..41bd721 100644
--- a/lib/puppet/parser/ast.rb
+++ b/lib/puppet/parser/ast.rb
@@ -116,3 +116,4 @@ require 'puppet/parser/ast/resourceparam'
 require 'puppet/parser/ast/selector'
 require 'puppet/parser/ast/tag'
 require 'puppet/parser/ast/vardef'
+require 'puppet/parser/ast/dependency'
diff --git a/lib/puppet/parser/ast/dependency.rb 
b/lib/puppet/parser/ast/dependency.rb
new file mode 100644
index 0000000..5678ffa
--- /dev/null
+++ b/lib/puppet/parser/ast/dependency.rb
@@ -0,0 +1,31 @@
+require 'puppet/parser/ast'
+require 'puppet/parser/ast/branch'
+require 'puppet/parser/dependency'
+
+class Puppet::Parser::AST::Dependency < Puppet::Parser::AST::Branch
+    attr_accessor :source, :target, :type
+
+    # Evaluate our object, but just return a simple array of the type
+    # and name.
+    def evaluate(scope)
+        source = self.source.safeevaluate(scope)
+        target = self.target.safeevaluate(scope)
+        scope.compiler.add_dependency(Puppet::Parser::Dependency.new(source, 
target, type))
+    end
+
+    def initialize(left, right, type, args = {})
+        super(args)
+        if type.include?(">")
+            @source = left
+            @target = right
+        else
+            @source = right
+            @target = left
+        end
+        if type.include?("-")
+            @type = :dependency
+        else
+            @type = :subscription
+        end
+    end
+end
diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/compiler.rb
index 8e84f5a..8208f9c 100644
--- a/lib/puppet/parser/compiler.rb
+++ b/lib/puppet/parser/compiler.rb
@@ -21,13 +21,17 @@ class Puppet::Parser::Compiler
         raise Puppet::Error, "#{detail} on node #{node.name}"
     end
 
-    attr_reader :node, :facts, :collections, :catalog, :node_scope, :resources
+    attr_reader :node, :facts, :collections, :catalog, :node_scope, 
:resources, :dependencies
 
     # Add a collection to the global list.
     def add_collection(coll)
         @collections << coll
     end
 
+    def add_dependency(dep)
+        @dependencies << dep
+    end
+
     # Store a resource override.
     def add_override(override)
         # If possible, merge the override in immediately.
@@ -144,6 +148,10 @@ class Puppet::Parser::Compiler
         found
     end
 
+    def evaluate_dependencies
+        @dependencies.each { |dep| dep.evaluate(catalog) }
+    end
+
     # Return a resource by either its ref or its type and title.
     def findresource(*args)
         @catalog.resource(*args)
@@ -337,6 +345,8 @@ class Puppet::Parser::Compiler
     # Make sure all of our resources and such have done any last work
     # necessary.
     def finish
+        evaluate_dependencies()
+
         resources.each do |resource|
             # Add in any resource overrides.
             if overrides = resource_overrides(resource)
@@ -374,6 +384,9 @@ class Puppet::Parser::Compiler
         # but they each refer back to the scope that created them.
         @collections = []
 
+        # The list of dependencies to evaluate.
+        @dependencies = []
+
         # For maintaining the relationship between scopes and their resources.
         @catalog = Puppet::Resource::Catalog.new(@node.name)
         @catalog.version = known_resource_types.version
diff --git a/lib/puppet/parser/dependency.rb b/lib/puppet/parser/dependency.rb
new file mode 100644
index 0000000..b085e6f
--- /dev/null
+++ b/lib/puppet/parser/dependency.rb
@@ -0,0 +1,24 @@
+class Puppet::Parser::Dependency
+    attr_accessor :source, :target, :type
+
+    PARAM_MAP = {:dependency => :require, :subscription => :subscribe}
+
+    def evaluate(catalog)
+        unless source_resource = catalog.resource(source)
+            raise ArgumentError, "Could not find resource #{source} for 
dependency on #{target}"
+        end
+        unless target_resource = catalog.resource(target)
+            raise ArgumentError, "Could not find resource #{target} for 
dependency from #{source}"
+        end
+        source_resource[param_name] ||= []
+        source_resource[param_name] << target
+    end
+
+    def initialize(source, target, type)
+        @source, @target, @type = source.to_s, target.to_s, type
+    end
+
+    def param_name
+        PARAM_MAP[type] || raise(ArgumentError, "Invalid dependency type 
#{type}")
+    end
+end
diff --git a/lib/puppet/parser/grammar.ra b/lib/puppet/parser/grammar.ra
index 5b9a609..6d19033 100644
--- a/lib/puppet/parser/grammar.ra
+++ b/lib/puppet/parser/grammar.ra
@@ -11,7 +11,7 @@ token QMARK LPAREN RPAREN ISEQUAL GREATEREQUAL GREATERTHAN 
LESSTHAN
 token IF ELSE IMPORT DEFINE ELSIF VARIABLE CLASS INHERITS NODE BOOLEAN
 token NAME SEMIC CASE DEFAULT AT LCOLLECT RCOLLECT CLASSNAME CLASSREF
 token NOT OR AND UNDEF PARROW PLUS MINUS TIMES DIV LSHIFT RSHIFT UMINUS
-token MATCH NOMATCH REGEX
+token MATCH NOMATCH REGEX IN_EDGE OUT_EDGE IN_EDGE_SUB OUT_EDGE_SUB
 
 prechigh
     right NOT
@@ -74,6 +74,13 @@ statement:    resource
             | nodedef
             | resourceoverride
             | append
+            | relationship
+
+relationship: resourceref edge resourceref {
+    result = AST::Dependency.new(val[0], val[2], val[1], ast_context)
+}
+
+edge: IN_EDGE | OUT_EDGE | IN_EDGE_SUB | OUT_EDGE_SUB
 
 fstatement:   NAME LPAREN funcvalues RPAREN {
     args = aryfy(val[2])
diff --git a/lib/puppet/parser/lexer.rb b/lib/puppet/parser/lexer.rb
index 2a1f88e..628a37a 100644
--- a/lib/puppet/parser/lexer.rb
+++ b/lib/puppet/parser/lexer.rb
@@ -129,6 +129,10 @@ class Puppet::Parser::Lexer
         ':' => :COLON,
         '@' => :AT,
         '<<|' => :LLCOLLECT,
+        '->' => :IN_EDGE,
+        '<-' => :OUT_EDGE,
+        '=>' => :IN_EDGE_SUB,
+        '<=' => :OUT_EDGE_SUB,
         '|>>' => :RRCOLLECT,
         '<|' => :LCOLLECT,
         '|>' => :RCOLLECT,
diff --git a/spec/unit/parser/ast/dependency.rb 
b/spec/unit/parser/ast/dependency.rb
new file mode 100644
index 0000000..52283d6
--- /dev/null
+++ b/spec/unit/parser/ast/dependency.rb
@@ -0,0 +1,64 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+describe Puppet::Parser::AST::Dependency do
+    before do
+        @class = Puppet::Parser::AST::Dependency
+    end
+
+    it "should set its type to :dependency if the relationship type is '->'" do
+        @class.new(:left, :right, "->").type.should == :dependency
+    end
+
+    it "should set its type to :dependency if the relationship type is '<-'" do
+        @class.new(:left, :right, "<-").type.should == :dependency
+    end
+
+    it "should set its type to :subscription if the relationship type is '=>'" 
do
+        @class.new(:left, :right, "=>").type.should == :subscription
+    end
+
+    it "should set its type to :subscription if the relationship type is '<='" 
do
+        @class.new(:left, :right, "<=").type.should == :subscription
+    end
+
+    it "should set its source to the first argument and target to the second 
if the arrow points to the right" do
+        dep = @class.new(:left, :right, "->")
+        dep.source.should == :left
+        dep.target.should == :right
+        dep = @class.new(:left, :right, "=>")
+        dep.source.should == :left
+        dep.target.should == :right
+    end
+
+    it "should set its source to the second argument and target to the first 
if the arrow points to the left" do
+        dep = @class.new(:left, :right, "<-")
+        dep.source.should == :right
+        dep.target.should == :left
+        dep = @class.new(:left, :right, "<=")
+        dep.source.should == :right
+        dep.target.should == :left
+    end
+
+    it "should set its line and file if provided" do
+        dep = @class.new(:left, :right, "<=", :line => 50, :file => "/foo")
+        dep.line.should == 50
+        dep.file.should == "/foo"
+    end
+
+    describe "when evaluating" do
+        before do
+            @compiler = Puppet::Parser::Compiler.new(Puppet::Node.new("foo"))
+            @scope = Puppet::Parser::Scope.new(:compiler => @compiler)
+        end
+
+        it "should create a dependency with the evaluated source and target 
and add it to the scope" do
+            source = stub 'source', :safeevaluate => :left
+            target = stub 'target', :safeevaluate => :right
+            @class.new(source, target, "->").evaluate(@scope)
+            @compiler.dependencies[0].source.should == "left"
+            @compiler.dependencies[0].target.should == "right"
+        end
+    end
+end
diff --git a/spec/unit/parser/compiler.rb b/spec/unit/parser/compiler.rb
index 6fd4d1f..35fff54 100755
--- a/spec/unit/parser/compiler.rb
+++ b/spec/unit/parser/compiler.rb
@@ -134,7 +134,7 @@ describe Puppet::Parser::Compiler do
 
         def compile_methods
             [:set_node_parameters, :evaluate_main, :evaluate_ast_node, 
:evaluate_node_classes, :evaluate_generators, :fail_on_unevaluated,
-                :finish, :store, :extract]
+                :finish, :store, :extract, :evaluate_dependencies]
         end
 
         # Stub all of the main compile methods except the ones we're 
specifically interested in.
@@ -394,6 +394,15 @@ describe Puppet::Parser::Compiler do
         end
     end
 
+    describe "when evaluating dependencies" do
+        it "should evaluate each dependency with its catalog" do
+            dep = stub 'dep'
+            dep.expects(:evaluate).with(@compiler.catalog)
+            @compiler.add_dependency dep
+            @compiler.evaluate_dependencies
+        end
+    end
+
     describe "when told to evaluate missing classes" do
 
         it "should fail if there's no source listed for the scope" do
diff --git a/spec/unit/parser/dependency.rb b/spec/unit/parser/dependency.rb
new file mode 100644
index 0000000..d8206c1
--- /dev/null
+++ b/spec/unit/parser/dependency.rb
@@ -0,0 +1,60 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../spec_helper'
+
+require 'puppet/parser/dependency'
+
+describe Puppet::Parser::Dependency do
+    before do
+        @source = Puppet::Resource.new(:mytype, "source")
+        @target = Puppet::Resource.new(:mytype, "target")
+        @dep = Puppet::Parser::Dependency.new(@source, @target, :dependency)
+    end
+
+    it "should set the source to the stringified version" do
+        @dep.source.should == "Mytype[source]"
+    end
+
+    it "should set the target to the stringified version" do
+        @dep.target.should == "Mytype[target]"
+    end
+
+    describe "when evaluating" do
+        before do
+            @catalog = Puppet::Resource::Catalog.new
+            @catalog.add_resource(@source)
+            @catalog.add_resource(@target)
+        end
+
+        it "should fail if the source resource cannot be found" do
+            @catalog = Puppet::Resource::Catalog.new
+            @catalog.add_resource @target
+            lambda { @dep.evaluate(@catalog) }.should 
raise_error(ArgumentError)
+        end
+
+        it "should fail if the target resource cannot be found" do
+            @catalog = Puppet::Resource::Catalog.new
+            @catalog.add_resource @source
+            lambda { @dep.evaluate(@catalog) }.should 
raise_error(ArgumentError)
+        end
+
+        it "should add the target as a 'require' value if the type is 
'dependency'" do
+            @dep.type = :dependency
+            @dep.evaluate(@catalog)
+            @source[:require].should be_include("Mytype[target]")
+        end
+
+        it "should add the target as a 'subscribe' value if the type is 
'subscription'" do
+            @dep.type = :subscription
+            @dep.evaluate(@catalog)
+            @source[:subscribe].should be_include("Mytype[target]")
+        end
+
+        it "should supplement rather than clobber existing relationship 
values" do
+            @source[:require] = "File[/bar]"
+            @dep.evaluate(@catalog)
+            @source[:require].should be_include("Mytype[target]")
+            @source[:require].should be_include("File[/bar]")
+        end
+    end
+end
-- 
1.6.1

-- 
You received this message because you are subscribed to the Google Groups 
"Puppet Developers" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/puppet-dev?hl=en.

Reply via email to