diff options
Diffstat (limited to 'lib/puppet')
-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 |
8 files changed, 259 insertions, 212 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 |