diff options
| author | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2005-09-21 22:40:31 +0000 |
|---|---|---|
| committer | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2005-09-21 22:40:31 +0000 |
| commit | f7d9b83a83bf43b13846c4b621794257f04832bb (patch) | |
| tree | 465e13c7c8db27a805d04914a2f25da8713f5478 | |
| parent | 03f57330ef06d6afd6b5f011f1764c4420f7d3dd (diff) | |
| download | puppet-f7d9b83a83bf43b13846c4b621794257f04832bb.tar.gz puppet-f7d9b83a83bf43b13846c4b621794257f04832bb.tar.xz puppet-f7d9b83a83bf43b13846c4b621794257f04832bb.zip | |
I am still somewhat in mid-change, but I have made the biggest changes to making nodes work correctly. The core code works, but I still need to fix my various test cases
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@697 980ebf18-57e1-0310-9a29-db15c13687c0
| -rwxr-xr-x | lib/puppet/parsedfile.rb | 2 | ||||
| -rw-r--r-- | lib/puppet/parser/ast.rb | 21 | ||||
| -rw-r--r-- | lib/puppet/parser/grammar.ra | 33 | ||||
| -rw-r--r-- | lib/puppet/parser/interpreter.rb | 131 | ||||
| -rw-r--r-- | lib/puppet/parser/parser.rb | 37 | ||||
| -rw-r--r-- | lib/puppet/parser/scope.rb | 144 | ||||
| -rw-r--r-- | lib/puppet/server/master.rb | 76 | ||||
| -rw-r--r-- | lib/puppet/transportable.rb | 27 | ||||
| -rwxr-xr-x | test/language/tc_ast.rb | 270 | ||||
| -rwxr-xr-x | test/language/tc_interpreter.rb | 28 |
10 files changed, 464 insertions, 305 deletions
diff --git a/lib/puppet/parsedfile.rb b/lib/puppet/parsedfile.rb index 337932309..e32a2daef 100755 --- a/lib/puppet/parsedfile.rb +++ b/lib/puppet/parsedfile.rb @@ -5,6 +5,8 @@ require 'puppet' module Puppet class ParsedFile + attr_reader :file + # Determine whether the file has changed and thus whether it should # be reparsed def changed? diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb index a234bd165..897de71e9 100644 --- a/lib/puppet/parser/ast.rb +++ b/lib/puppet/parser/ast.rb @@ -1167,7 +1167,7 @@ module Puppet names.each { |name| begin - scope.sethost(name, + scope.setnode(name, Node.new( :name => name, :code => @code @@ -1331,13 +1331,24 @@ module Puppet class Node < AST::Component attr_accessor :name, :args, :code, :parentclass - def evaluate(scope,hash,objtype,objname) + def evaluate(scope, facts = {}) scope = scope.newscope - scope.type = objtype - scope.name = objname + scope.type = "node" + scope.name = @name + + # Mark this scope as a nodescope, so that classes will be + # singletons within it scope.nodescope = true - self.code.safeevaluate(scope) + # Now set all of the facts inside this scope + facts.each { |var, value| + scope.setvar(var, value) + } + + # And then evaluate our code. + @code.safeevaluate(scope) + + return scope end end #--------------------------------------------------------------- diff --git a/lib/puppet/parser/grammar.ra b/lib/puppet/parser/grammar.ra index 7b05433ce..95ffa9772 100644 --- a/lib/puppet/parser/grammar.ra +++ b/lib/puppet/parser/grammar.ra @@ -586,6 +586,7 @@ endcomma: # nothing end ---- header ---- require 'puppet' +require 'puppet/parsedfile' require 'puppet/parser/lexer' require 'puppet/parser/ast' #require 'puppet/parser/interpreter' @@ -603,22 +604,26 @@ Puppet[:typecheck] = true Puppet[:paramcheck] = true ---- inner ---- -attr_writer :stack -attr_reader :file +attr_reader :file, :files def file=(file) - if self.stack.include?(file) + unless FileTest.exists?(file) + raise Puppet::Error, "Could not find file %s" % file + end + if @files.detect { |f| f.file == file } raise Puppet::ImportError.new("Import loop detected") else + @files << Puppet::ParsedFile.new(file) @lexer.file = file end end def initialize @lexer = Puppet::Parser::Lexer.new() - if Puppet[:debug] - @yydebut = true - end + @files = [] + #if Puppet[:debug] + # @yydebug = true + #end end def on_error(token,value,stack) @@ -687,20 +692,8 @@ def parse end end -def stack - if defined? @stack and ! @stack.nil? - if @lexer.file - return [@stack,@lexer.file].flatten - else - return @stack - end - else - if @lexer.file - return [@lexer.file] - else - return [] - end - end +def reparse? + return @files.detect { |file| file.changed? } end def string=(string) diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index 508bd17bd..9335d81aa 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -1,11 +1,6 @@ -#!/usr/local/bin/ruby -w - -# $Id$ - -# the interpreter -# -# this builds our virtual pinball machine, into which we'll place our host-specific -# information and out of which we'll receive our host-specific configuration +# The interepreter's job is to convert from a parsed file to the configuration +# for a given client. It really doesn't do any work on its own, it just collects +# and calls out to other objects. require 'puppet' require 'puppet/parser/parser' @@ -14,66 +9,49 @@ require 'puppet/parser/scope' module Puppet module Parser - #--------------------------------------------------------------- class Interpreter attr_accessor :ast, :topscope # just shorten the constant path a bit, using what amounts to an alias AST = Puppet::Parser::AST - #------------------------------------------------------------ - def clear - TransObject.clear - end - #------------------------------------------------------------ - - #------------------------------------------------------------ - #def callfunc(function,*args) - # #Puppet.debug("Calling %s on %s" % [function,@client]) - # @client.callfunc(function,*args) - # #Puppet.debug("Finished %s" % function) - #end - #------------------------------------------------------------ - - #------------------------------------------------------------ # create our interpreter def initialize(hash) - unless hash.include?(:ast) - raise ArgumentError.new("Must pass tree and client to Interpreter") + unless hash.include?(:Manifest) + raise Puppet::DevError, "Interpreter was not passed a file" end - @ast = hash[:ast] - #@client = hash[:client] - @scope = Puppet::Parser::Scope.new() # no parent scope - @topscope = @scope - @scope.interp = self + @file = hash[:Manifest] - if hash.include?(:facts) - facts = hash[:facts] - unless facts.is_a?(Hash) - raise ArgumentError.new("Facts must be a hash") - end - - facts.each { |fact,value| - @scope.setvar(fact,value) - } + if hash.include?(:UseNodes) + @usenodes = hash[:UseNodes] + else + @usenodes = true end + + # Create our parser object + parsefiles + + evaluate end - #------------------------------------------------------------ - #------------------------------------------------------------ # evaluate our whole tree - def run - # evaluate returns a value, but at the top level we only - # care about its side effects - # i think - unless @ast.is_a?(AST) or @ast.is_a?(AST::ASTArray) - Puppet.err "Received top-level non-ast '%s' of type %s" % - [@ast,@ast.class] - raise TypeError.new("Received non-ast '%s' of type %s" % - [@ast,@ast.class]) - end + def run(client, facts) + parsefiles() begin + if @usenodes + unless client + raise Puppet::Error, + "Cannot evaluate no nodes with a nil client" + end + + # We've already evaluated the AST, in this case + @scope.evalnode(client, facts) + else + scope = Puppet::Parser::Scope.new() # no parent scope + scope.interp = self + scope.evaluate(@ast, facts) + end @ast.evaluate(@scope) rescue Puppet::DevError, Puppet::Error, Puppet::ParseError => except #Puppet.err "File %s, line %s: %s" % @@ -125,14 +103,55 @@ module Puppet #TransObject.clear return topbucket end - #------------------------------------------------------------ - #------------------------------------------------------------ def scope return @scope end - #------------------------------------------------------------ + + private + + # Evaluate the configuration. If there aren't any nodes defined, then + # this doesn't actually do anything, because we have to evaluate the + # entire configuration each time we get a connect. + def evaluate + @scope = Puppet::Parser::Scope.new() # no parent scope + @topscope = @scope + @scope.interp = self + + if @usenodes + Puppet.debug "Nodes defined" + @ast.safeevaluate(@scope) + else + Puppet.debug "No nodes defined" + return + end + end + + def parsefiles + if defined? @parser + unless @parser.reparse? + return false + end + end + + unless FileTest.exists?(@file) + if @ast + Puppet.warning "Manifest %s has disappeared" % @file + return + else + raise Puppet::Error, "Manifest %s must exist" % @file + end + end + + # should i be creating a new parser each time...? + @parser = Puppet::Parser::Parser.new() + @parser.file = @file + @ast = @parser.parse + + evaluate + end end - #--------------------------------------------------------------- end end + +# $Id$ diff --git a/lib/puppet/parser/parser.rb b/lib/puppet/parser/parser.rb index b636b7b37..6115dee4b 100644 --- a/lib/puppet/parser/parser.rb +++ b/lib/puppet/parser/parser.rb @@ -8,6 +8,7 @@ require 'racc/parser' require 'puppet' +require 'puppet/parsedfile' require 'puppet/parser/lexer' require 'puppet/parser/ast' #require 'puppet/parser/interpreter' @@ -31,23 +32,27 @@ module Puppet class Parser < Racc::Parser -module_eval <<'..end grammar.ra modeval..idea8fcb8472', 'grammar.ra', 606 -attr_writer :stack -attr_reader :file +module_eval <<'..end grammar.ra modeval..id859845cfb5', 'grammar.ra', 607 +attr_reader :file, :files def file=(file) - if self.stack.include?(file) + unless FileTest.exists?(file) + raise Puppet::Error, "Could not find file %s" % file + end + if @files.detect { |f| f.file == file } raise Puppet::ImportError.new("Import loop detected") else + @files << Puppet::ParsedFile.new(file) @lexer.file = file end end def initialize @lexer = Puppet::Parser::Lexer.new() - if Puppet[:debug] - @yydebut = true - end + @files = [] + #if Puppet[:debug] + # @yydebug = true + #end end def on_error(token,value,stack) @@ -116,26 +121,14 @@ def parse end end -def stack - if defined? @stack and ! @stack.nil? - if @lexer.file - return [@stack,@lexer.file].flatten - else - return @stack - end - else - if @lexer.file - return [@lexer.file] - else - return [] - end - end +def reparse? + return @files.detect { |file| file.changed? } end def string=(string) @lexer.string = string end -..end grammar.ra modeval..idea8fcb8472 +..end grammar.ra modeval..id859845cfb5 ##### racc 1.4.4 generates ### diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index 37e5c39b7..56bf620dd 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -38,6 +38,30 @@ module Puppet @@declarative end + # Remove a specific child. + def delete(child) + @children.delete(child) + end + + # Verify that no nodescopes are hanging around. + def nodeclean + @children.find_all { |child| + if child.is_a?(Scope) + child.nodescope? + else + false + end + }.each { |child| + @children.delete(child) + } + + @children.each { |child| + if child.is_a?(Scope) + child.nodeclean + end + } + end + # Is this scope associated with being a node? The answer determines # whether we store class instances here def nodescope? @@ -60,6 +84,65 @@ module Puppet } end + # Evaluate a specific node's code. This method will normally be called + # on the top-level scope, but it actually evaluates the node at the + # appropriate scope. + def evalnode(names, facts) + scope = code = nil + names.each { |node| + scope, code = self.findnode(node) + if scope and code + break + end + } + + unless scope and code + raise Puppet::Error, "Could not find configuration for %s" % + names.join(" or ") + end + + # First make sure there aren't any other node scopes lying around + self.nodeclean + + # We need to do a little skullduggery here. We want a + # temporary scope, because we don't want this scope to + # show up permanently in the scope tree -- otherwise we could + # not evaluate the node multiple times. We could conceivably + # cache the results, but it's not worth it at this stage. + + # Note that we evaluate the node code with its containing + # scope, not with the top scope. + code.safeevaluate(scope, facts) + + # We don't need to worry about removing the Node code because + # it will be removed during translation. + + # And now return the whole thing + return self.to_trans + end + + # Find a given node's definition; searches downward recursively. + def findnode(node) + if @nodetable.include?(node) + return [self, @nodetable[node]] + else + self.find { |child| + child.findnode(node) + } + end + end + + # Evaluate normally, with no node definitions + def evaluate(objects, facts = {}) + facts.each { |var, value| + self.setvar(var, value) + } + + objects.safeevaluate(self) + + return self.to_trans + end + # Initialize our new scope. Defaults to having no parent and to # being declarative. def initialize(parent = nil, declarative = true) @@ -88,6 +171,9 @@ module Puppet # be used by top scopes and node scopes. @classtable = Hash.new(nil) + # A table for storing nodes. + @nodetable = Hash.new(nil) + # All of the defaults set for types. It's a hash of hashes, # with the first key being the type, then the second key being # the parameter. @@ -115,6 +201,7 @@ module Puppet @map = { "variable" => @symtable, "type" => @typetable, + "node" => @nodetable, "object" => @objectable, "defaults" => @defaultstable } @@ -193,6 +280,18 @@ module Puppet return values end + # Look up a node by name + def lookupnode(name) + Puppet.debug "Looking up type %s" % name + value = self.lookup("type",name) + if value == :undefined + return nil + else + Puppet.debug "Found type %s" % name + return value + end + end + # Look up a defined type. def lookuptype(name) Puppet.debug "Looking up type %s" % name @@ -209,18 +308,16 @@ module Puppet def lookupobject(name,type) Puppet.debug "Looking up object %s of type %s in level %s" % [name, type, @level] - unless defined? @@objectsearch - @@objectsearch = proc { |table| - if table.include?(type) - if table[type].include?(name) - table[type][name] - end - else - nil + sub = proc { |table| + if table.include?(type) + if table[type].include?(name) + table[type][name] end - } - end - value = self.lookup("object",@@objectsearch) + else + nil + end + } + value = self.lookup("object",sub) if value == :undefined return nil else @@ -294,20 +391,14 @@ module Puppet end # Store a host in the global table. - def sethost(name,host) - if @@hosttable.include?(name) - str = "Host %s is already defined" % name - if @@hosttable[name].file - str += " in file %s" % @@hosttable[name].file - end - if @@hosttable[name].line - str += " on line %s" % @@hosttable[name].line - end - raise Puppet::ParseError, - "Host %s is already defined" % name + def setnode(name,code) + if @nodetable.include?(name) + raise Puppet::Error, "Host %s is already defined" % name else - @@hosttable[name] = host + @nodetable[name] = code end + + #self.nodescope = true end # Define our type. @@ -406,6 +497,13 @@ module Puppet # Otherwise, just add it to our list of results. results.push(cresult) end + + # Nodescopes are one-time; once they've been evaluated + # I need to destroy them. Nodeclean makes sure this is + # done correctly, but this should catch most of them. + if child.nodescope? + @children.delete(child) + end elsif child.is_a?(TransObject) results.push(child) else diff --git a/lib/puppet/server/master.rb b/lib/puppet/server/master.rb index 099fe25c8..bf6998bac 100644 --- a/lib/puppet/server/master.rb +++ b/lib/puppet/server/master.rb @@ -22,11 +22,6 @@ class Server @file = hash[:File] || Puppet[:manifest] hash.delete(:File) - @filestamp = nil - @filestatted = nil - @filetimeout = hash[:FileTimeout] || 60 - parsefile - if hash[:Local] @local = hash[:Local] else @@ -38,13 +33,24 @@ class Server else @ca = nil end + + Puppet.debug("Creating interpreter") + + args = {:Manifest => @file} + + if @local + args[:UseNodes] = false + end + + begin + @interpreter = Puppet::Parser::Interpreter.new(args) + rescue => detail + Puppet.err detail + raise + end end def getconfig(facts, client = nil, clientip = nil) - parsefile() - if client - #Puppet.warning request.inspect - end if @local # we don't need to do anything, since we should already # have raw objects @@ -62,20 +68,9 @@ class Server end end - Puppet.debug("Creating interpreter") - - begin - interpreter = Puppet::Parser::Interpreter.new( - :ast => @ast, - :facts => facts - ) - rescue => detail - return detail.to_s - end - Puppet.debug("Running interpreter") begin - retobjects = interpreter.run() + retobjects = @interpreter.run(client, facts) rescue => detail Puppet.err detail.to_s return "" @@ -87,45 +82,6 @@ class Server return CGI.escape(Marshal::dump(retobjects)) end end - - private - - def parsefile - if @filestamp and FileTest.exists?(@file) - if @filetimeout and @filestatted - if Time.now - @filestatted > @filetimeout - tmp = File.stat(@file).ctime - - @filestatted = Time.now - if tmp == @filestamp - return - else - Puppet.notice "Reloading file" - end - else - return - end - else - @filestatted = Time.now - end - end - @filestatted = Time.now - - unless FileTest.exists?(@file) - if @ast - Puppet.warning "Manifest %s has disappeared" % @file - return - else - raise Puppet::Error, "Manifest %s must exist" % @file - end - end - # should i be creating a new parser each time...? - @parser = Puppet::Parser::Parser.new() - @parser.file = @file - @ast = @parser.parse - - @filestamp = File.stat(@file).ctime - end end end end diff --git a/lib/puppet/transportable.rb b/lib/puppet/transportable.rb index 039a8d499..f50060d67 100644 --- a/lib/puppet/transportable.rb +++ b/lib/puppet/transportable.rb @@ -9,42 +9,17 @@ module Puppet class TransObject < Hash attr_accessor :type, :name, :file, :line - @@ohash = {} - @@oarray = [] - - def TransObject.add(object) - @@oarray.push object - - # this is just so we can check, at parse time, whether a required - # object has already been mentioned when it is listed as required - # because we're ordered, as long as an object gets made before its - # dependent objects will get synced later - @@ohash[object.longname] = object - end - - def TransObject.clear - @@oarray.clear - end - - def TransObject.list - return @@oarray - end - def initialize(name,type) self[:name] = name @type = type @name = name - self.class.add(self) + #self.class.add(self) end def longname return [self.type,self[:name]].join('--') end - #def name - # return self[:name] - #end - def to_s return "%s(%s) => %s" % [@type,self[:name],super] end diff --git a/test/language/tc_ast.rb b/test/language/tc_ast.rb index f93feb4f1..3171db280 100755 --- a/test/language/tc_ast.rb +++ b/test/language/tc_ast.rb @@ -22,6 +22,32 @@ class TestAST < TestPuppet ) end + def classobj(name, args = {}) + args[:name] = nameobj(name) + args[:code] = AST::ASTArray.new( + :children => [ + varobj("%svar" % name, "%svalue" % name), + fileobj("/%s" % name) + ] + ) + assert_nothing_raised("Could not create class %s" % name) { + return AST::ClassDef.new(args) + } + end + + def compobj(name, args = {}) + args[:name] = nameobj(name) + args[:code] = AST::ASTArray.new( + :children => [ + varobj("%svar" % name, "%svalue" % name), + fileobj("/%s" % name) + ] + ) + assert_nothing_raised("Could not create compdef %s" % name) { + return AST::CompDef.new(args) + } + end + def fileobj(path, hash = {"owner" => "root"}) assert_nothing_raised("Could not create file %s" % path) { return AST::ObjectDef.new( @@ -40,6 +66,20 @@ class TestAST < TestPuppet } end + def nodeobj(name) + assert_nothing_raised("Could not create node %s" % name) { + return AST::NodeDef.new( + :names => nameobj(name), + :code => AST::ASTArray.new( + :children => [ + varobj("%svar" % name, "%svalue" % name), + fileobj("/%s" % name) + ] + ) + ) + } + end + def objectinst(hash) assert_nothing_raised("Could not create object instance") { params = hash.collect { |param, value| @@ -80,43 +120,17 @@ class TestAST < TestPuppet # create the parent class assert_nothing_raised("Could not create parent object") { - children << AST::ClassDef.new( - :name => nameobj("parent"), - :code => AST::ASTArray.new( - :children => [ - varobj("parentvar", "parentval"), - fileobj("/etc") - ] - ) - ) + children << classobj("parent") } # Create child class one assert_nothing_raised("Could not create child1 object") { - children << AST::ClassDef.new( - :name => nameobj("child1"), - :parentclass => nameobj("parent"), - :code => AST::ASTArray.new( - :children => [ - varobj("child1var", "child1val"), - fileobj("/tmp") - ] - ) - ) + children << classobj("child1", :parentclass => nameobj("parent")) } # Create child class two assert_nothing_raised("Could not create child2 object") { - children << AST::ClassDef.new( - :name => nameobj("child2"), - :parentclass => nameobj("parent"), - :code => AST::ASTArray.new( - :children => [ - varobj("child2var", "child2val"), - fileobj("/var") - ] - ) - ) + children << classobj("child2", :parentclass => nameobj("parent")) } # Now call the two classes @@ -147,32 +161,32 @@ class TestAST < TestPuppet } assert_equal(1, scope.find_all { |child| - child.lookupobject("/etc", "file") - #child.lookupobject("file", "/etc") - }.length, "Found incorrect number of '/etc' objects") + child.lookupobject("/parent", "file") + }.length, "Found incorrect number of '/parent' objects") end # Test that 'setobject' collects all of an object's parameters and stores # them in one TransObject, rather than many. This is probably a bad idea. def test_setobject top = nil + children = [ + fileobj("/etc", "owner" => "root"), + fileobj("/etc", "group" => "root") + ] assert_nothing_raised("Could not create top object") { top = AST::ASTArray.new( - :children => [ - fileobj("/etc", "owner" => "root"), - fileobj("/etc", "group" => "root") - ] + :children => children ) } - scope = nil + + scope = Puppet::Parser::Scope.new() assert_nothing_raised("Could not evaluate") { - scope = Puppet::Parser::Scope.new() - objects = top.evaluate(scope) + top.evaluate(scope) } obj = nil assert_nothing_raised("Could not retrieve file object") { - obj = scope.lookupobject("file", "/etc") + obj = scope.lookupobject("/etc", "file") } assert(obj, "could not retrieve file object") @@ -189,32 +203,10 @@ class TestAST < TestPuppet children = [] # create the parent class - assert_nothing_raised("Could not create parent object") { - children << AST::CompDef.new( - :name => nameobj("parent"), - :args => AST::ASTArray.new(:children => []), - :code => AST::ASTArray.new( - :children => [ - varobj("parentvar", "parentval"), - fileobj("/etc") - ] - ) - ) - } + children << compobj("parent", :args => AST::ASTArray.new(:children => [])) # Create child class one - assert_nothing_raised("Could not create child1 object") { - children << AST::ClassDef.new( - :name => nameobj("child1"), - :parentclass => nameobj("parent"), - :code => AST::ASTArray.new( - :children => [ - varobj("child1var", "child1val"), - fileobj("/tmp") - ] - ) - ) - } + children << classobj("child1", :parentclass => nameobj("parent")) # Now call the two classes assert_nothing_raised("Could not add AST nodes for calling") { @@ -245,31 +237,10 @@ class TestAST < TestPuppet children = [] # create the parent class - assert_nothing_raised("Could not create parent object") { - children << AST::ClassDef.new( - :name => nameobj("parent"), - :code => AST::ASTArray.new( - :children => [ - varobj("parentvar", "parentval"), - fileobj("/etc") - ] - ) - ) - } + children << classobj("parent") # Create child class one - assert_nothing_raised("Could not create child1 object") { - children << AST::ClassDef.new( - :name => nameobj("child1"), - :parentclass => nameobj("parent"), - :code => AST::ASTArray.new( - :children => [ - varobj("child1var", "child1val"), - fileobj("/tmp") - ] - ) - ) - } + children << classobj("child1", :parentclass => nameobj("parent")) # Now call the two classes assert_nothing_raised("Could not add AST nodes for calling") { @@ -281,16 +252,18 @@ class TestAST < TestPuppet } # create the node + nodename = "mynodename" node = nil assert_nothing_raised("Could not create parent object") { node = AST::NodeDef.new( - :names => nameobj("node"), + :names => nameobj(nodename), :code => AST::ASTArray.new( :children => children ) ) } + # Create the wrapper object top = nil assert_nothing_raised("Could not create top object") { top = AST::ASTArray.new( @@ -298,21 +271,132 @@ class TestAST < TestPuppet ) } + # Evaluate the parse tree scope = nil assert_nothing_raised("Could not evaluate node") { scope = Puppet::Parser::Scope.new() - objects = top.evaluate(scope) + top.evaluate(scope) } + # Verify that, well, nothing really happened, and especially verify + # that the top scope is not a node scope assert(scope.topscope?, "Scope is not top scope") assert(! scope.nodescope?, "Scope is mistakenly node scope") assert(! scope.lookupclass("parent"), "Found parent class in top scope") - nodescope = scope.find { |child| child.nodescope? } + # verify we can find our node + assert(scope.findnode(nodename), "Could not find node") + + # And verify that we can evaluate it okay + objects = nil + assert_nothing_raised("Could not retrieve node definition") { + objects = scope.evalnode([nodename], {}) + } + assert(objects, "Could not retrieve node definition") + + # Because node scopes are temporary (i.e., they get destroyed after the node's + # config is returned) we should not be able to find the node scope. + nodescope = nil + assert_nothing_raised { + nodescope = scope.find { |child| + child.nodescope? + } + } + + assert_nil(nodescope, "Found nodescope") + + # And now verify again that the top scope cannot find the node's definition + # of the parent class + assert(! scope.lookupclass("parent"), "Found parent class in top scope") + + # Verify that we can evaluate the node twice + assert_nothing_raised("Could not retrieve node definition") { + scope.evalnode([nodename], {}) + } + end + + # Test that you can look a host up using multiple names, e.g., an FQDN and + # a short name + def test_multiplenodenames + children = [] + + # create a short-name node + shortname = "mynodename" + children << nodeobj(shortname) + + # And a long-name node + longname = "node.domain.com" + children << nodeobj(longname) + + # Create the wrapper object + top = nil + assert_nothing_raised("Could not create top object") { + top = AST::ASTArray.new( + :children => children + ) + } + + # Evaluate the parse tree + scope = nil + assert_nothing_raised("Could not evaluate node") { + scope = Puppet::Parser::Scope.new() + top.evaluate(scope) + } - assert(nodescope, "Could not find nodescope") + # Verify we can find the node via a search list + objects = nil + assert_nothing_raised("Could not retrieve short node definition") { + objects = scope.evalnode(["%s.domain.com" % shortname, shortname], {}) + } + assert(objects, "Could not retrieve short node definition") + + # and then look for the long name + assert_nothing_raised("Could not retrieve long node definition") { + objects = scope.evalnode([longname.sub(/\..+/, ''), longname], {}) + } + assert(objects, "Could not retrieve long node definition") + end - assert(nodescope.lookupclass("parent"), - "Could not find parent class in node scope") + # Test that a node gets the entire configuration except for work meant for + # another node + def test_fullconfigwithnodes + children = [] + + children << fileobj("/testing") + + # create a short-name node + name = "mynodename" + children << nodeobj(name) + + # Create the wrapper object + top = nil + assert_nothing_raised("Could not create top object") { + top = AST::ASTArray.new( + :children => children + ) + } + + # Evaluate the parse tree + scope = nil + assert_nothing_raised("Could not evaluate node") { + scope = Puppet::Parser::Scope.new() + top.evaluate(scope) + } + + # Verify we can find the node via a search list + objects = nil + assert_nothing_raised("Could not retrieve short node definition") { + objects = scope.evalnode([name], {}) + } + assert(objects, "Could not retrieve short node definition") + + # And now verify that we got both the top and node objects + assert_nothing_raised("Could not find top-declared object") { + assert_equal("/testing", objects[0][:name]) + } + + assert_nothing_raised("Could not find node-declared object") { + assert_equal("/%s" % name, objects[1][0][:name]) + } end end diff --git a/test/language/tc_interpreter.rb b/test/language/tc_interpreter.rb new file mode 100755 index 000000000..6135bcb3f --- /dev/null +++ b/test/language/tc_interpreter.rb @@ -0,0 +1,28 @@ +#!/usr/bin/ruby + +if __FILE__ == $0 + $:.unshift '../../lib' + $:.unshift '..' + $puppetbase = "../.." +end + +require 'puppet' +require 'puppet/parser/interpreter' +require 'puppet/parser/parser' +require 'puppet/client' +require 'test/unit' +require 'puppettest' + +class TestInterpreter < TestPuppet + AST = Puppet::Parser::AST + + def test_simple + file = tempfile() + File.open(file, "w") { |f| + f.puts "file { \"/etc\": owner => root }" + } + assert_nothing_raised { + Puppet::Parser::Interpreter.new(:file => file) + } + end +end |
