(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.
