diff options
-rw-r--r-- | lib/puppet/parser/ast/node.rb | 4 | ||||
-rw-r--r-- | lib/puppet/parser/interpreter.rb | 95 | ||||
-rw-r--r-- | lib/puppet/parser/scope.rb | 1736 |
3 files changed, 880 insertions, 955 deletions
diff --git a/lib/puppet/parser/ast/node.rb b/lib/puppet/parser/ast/node.rb index 32bed7a2b..2e68cbdb3 100644 --- a/lib/puppet/parser/ast/node.rb +++ b/lib/puppet/parser/ast/node.rb @@ -19,10 +19,6 @@ class Puppet::Parser::AST ) scope.context = self.object_id - # Mark this scope as a nodescope, so that classes will be - # singletons within it - scope.isnodescope - # Now set all of the facts inside this scope facts.each { |var, value| scope.setvar(var, value) diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index 730b59e15..35c122c43 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -85,6 +85,7 @@ module Puppet require 'ldap' rescue LoadError @ldap = nil + return end begin @ldap = LDAP::Conn.new(Puppet[:ldapserver], Puppet[:ldapport]) @@ -95,6 +96,25 @@ module Puppet end end + # Search for our node in the various locations. + def nodesearch(node) + # At this point, stop at the first source that defines + # the node + @nodesources.each do |source| + method = "nodesearch_%s" % source + if self.respond_to? method + parent, nodeclasses = self.send(method, node) + end + + if nodeclasses + Puppet.info "Found %s in %s" % [client, source] + return parent, nodeclasses + end + end + + return nil, nil + end + # Find the ldap node and extra the info, returning just # the critical data. def nodesearch_ldap(node) @@ -171,63 +191,42 @@ module Puppet names << "#{client}.#{facts['domain']}" end - begin - if @usenodes - unless client - raise Puppet::Error, - "Cannot evaluate nodes with a nil client" - end + scope = Puppet::Parser::Scope.new() # no parent scope + scope.name = "top" + scope.type = "puppet" + scope.interp = self - classes = nil - parent = nil - # At this point, stop at the first source that defines - # the node - @nodesources.each do |source| - method = "nodesearch_%s" % source - if self.respond_to? method - parent, classes = self.send(method, client) - end + classes = @classes.dup - if classes - Puppet.info "Found %s in %s" % [client, source] - break - end - end + args = {:ast => @ast, :facts => facts, :classes => classes} - # We've already evaluated the AST, in this case - #return @scope.evalnode(names, facts, classes, parent) - return @scope.evalnode( - :name => names, - :facts => facts, - :classes => classes, - :parent => parent - ) - else - # We've already evaluated the AST, in this case - @scope = Puppet::Parser::Scope.new() # no parent scope - @scope.interp = self - #return @scope.evaluate(@ast, facts, @classes) - return @scope.evaluate( - :ast => @ast, - :facts => facts, - :classes => @classes - ) + if @usenodes + unless client + raise Puppet::Error, + "Cannot evaluate nodes with a nil client" end - #@ast.evaluate(@scope) + + Puppet.debug "Nodes defined" + args[:names] = names + + parent, nodeclasses = nodesearch(client) + + classes += nodeclasses if nodeclasses + + args[:parentnode] = parent if parent + end + + begin + return scope.evaluate(args) rescue Puppet::DevError, Puppet::Error, Puppet::ParseError => except - #Puppet.err "File %s, line %s: %s" % - # [except.file, except.line, except.message] - if Puppet[:debug] - puts except.backtrace - end - #exit(1) raise rescue => except error = Puppet::DevError.new("%s: %s" % [except.class, except.message]) - if Puppet[:debug] - puts except.backtrace - end + error.backtrace = except.backtrace + #if Puppet[:debug] + # puts except.backtrace + #end raise error end end diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index 6e945adb4..35137fda7 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -1,1061 +1,991 @@ # The scope class, which handles storing and retrieving variables and types and # such. +require 'puppet/parser/parser' require 'puppet/transportable' -module Puppet - module Parser - class Scope - class ScopeObj < Hash - attr_accessor :file, :line, :type, :name - end +module Puppet::Parser + class Scope + class ScopeObj < Hash + attr_accessor :file, :line, :type, :name + end - Puppet::Util.logmethods(self) + Puppet::Util.logmethods(self) - include Enumerable - attr_accessor :parent, :level, :interp - attr_accessor :name, :type, :topscope, :base, :keyword + include Enumerable + attr_accessor :parent, :level, :interp + attr_accessor :name, :type, :topscope, :base, :keyword - attr_accessor :top, :context + attr_accessor :top, :context - # This is probably not all that good of an idea, but... - # This way a parent can share its tables with all of its children. - attr_writer :nodetable, :classtable, :definedtable + # This is probably not all that good of an idea, but... + # This way a parent can share its tables with all of its children. + attr_writer :nodetable, :classtable, :definedtable - # Whether we behave declaratively. Note that it's a class variable, - # so all scopes behave the same. - @@declarative = true + # Whether we behave declaratively. Note that it's a class variable, + # so all scopes behave the same. + @@declarative = true - # Retrieve and set the declarative setting. - def Scope.declarative - return @@declarative - end - - def Scope.declarative=(val) - @@declarative = val - end + # Retrieve and set the declarative setting. + def self.declarative + return @@declarative + end - # Add all of the defaults for a given object to that object. - def adddefaults(obj) - defaults = self.lookupdefaults(obj.type) + def self.declarative=(val) + @@declarative = val + end - defaults.each do |var, value| - unless obj[var] - self.debug "Adding default %s for %s" % - [var, obj.type] + # Add all of the defaults for a given object to that object. + def adddefaults(obj) + defaults = lookupdefaults(obj.type) - obj[var] = value - end - end - end + defaults.each do |var, value| + unless obj[var] + self.debug "Adding default %s for %s" % + [var, obj.type] - # Add a single object's tags to the global list of tags for - # that object. - def addtags(obj) - unless defined? @tagtable - raise Puppet::DevError, "Told to add tags, but no tag table" + obj[var] = value end - list = @tagtable[obj.type][obj.name] - - obj.tags.each { |tag| - unless list.include?(tag) - if tag.nil? or tag == "" - Puppet.debug "Got tag %s from %s(%s)" % - [tag.inspect, obj.type, obj.name] - else - list << tag - end - end - } end + end - # Is the type a builtin type? - def builtintype?(type) - if typeklass = Puppet::Type.type(type) - return typeklass - else - return false - end + # Add a single object's tags to the global list of tags for + # that object. + def addtags(obj) + unless defined? @tagtable + raise Puppet::DevError, "Told to add tags, but no tag table" end + list = @tagtable[obj.type][obj.name] - # Verify that the given object isn't defined elsewhere. - def chkobjectclosure(hash) - type = hash[:type] - name = hash[:name] - unless name - return true - end - if @definedtable[type].include?(name) - typeklass = Puppet::Type.type(type) - if typeklass and ! typeklass.isomorphic? - Puppet.info "Allowing duplicate %s" % type + obj.tags.each { |tag| + unless list.include?(tag) + if tag.nil? or tag == "" + Puppet.debug "Got tag %s from %s(%s)" % + [tag.inspect, obj.type, obj.name] else - # Either it's a defined type, which are never - # isomorphic, or it's a non-isomorphic type. - msg = "Duplicate definition: %s[%s] is already defined" % - [type, name] - error = Puppet::ParseError.new(msg) - if hash[:line] - error.line = hash[:line] - end - if hash[:file] - error.file = hash[:file] - end - raise error + list << tag end end + } + end - return true - end - - def declarative=(val) - self.class.declarative = val - end - - def declarative - self.class.declarative + # Is the type a builtin type? + def builtintype?(type) + if typeklass = Puppet::Type.type(type) + return typeklass + else + return false end + end - # Log the existing tags. At some point this should be in a better - # place, but eh. - def logtags - @tagtable.sort { |a, b| - a[0] <=> b[0] - }.each { |type, names| - names.sort { |a, b| - a[0] <=> b[0] - }.each { |name, tags| - Puppet.info "%s(%s): '%s'" % [type, name, tags.join("' '")] - } - } + # Verify that the given object isn't defined elsewhere. + def chkobjectclosure(hash) + type = hash[:type] + name = hash[:name] + unless name + return true end - - # Create a new child scope. - def child=(scope) - @children.push(scope) - - if defined? @nodetable - scope.nodetable = @nodetable + if @definedtable[type].include?(name) + typeklass = Puppet::Type.type(type) + if typeklass and ! typeklass.isomorphic? + Puppet.info "Allowing duplicate %s" % type else - raise Puppet::DevError, "No nodetable has been defined" - end - - if defined? @classtable - scope.classtable = @classtable - else - raise Puppet::DevError, "No classtable has been defined" - end - - if defined? @definedtable - scope.definedtable = @definedtable - else - raise Puppet::DevError, "No definedtable has been defined" - end - end - - # Test whether a given scope is declarative. Even though it's - # a global value, the calling objects don't need to know that. - def declarative? - @@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 + # Either it's a defined type, which are never + # isomorphic, or it's a non-isomorphic type. + msg = "Duplicate definition: %s[%s] is already defined" % + [type, name] + error = Puppet::ParseError.new(msg) + if hash[:line] + error.line = hash[:line] end - }.each { |child| - @children.delete(child) - } - - @children.each { |child| - if child.is_a?(Scope) - child.nodeclean + if hash[:file] + error.file = hash[:file] end - } + raise error + end end - # Is this scope associated with being a node? The answer determines - # whether we store class instances here - def nodescope? - @nodescope - end + return true + end - # Mark that we are a nodescope. - def isnodescope - @nodescope = true + def declarative=(val) + self.class.declarative = val + end - # Also, create the extra tables associated with being a node - # scope. - # The table for storing class singletons. - @classtable = Hash.new(nil) + def declarative + self.class.declarative + end - # Also, create the object checking map - @definedtable = Hash.new { |types, type| - types[type] = {} + # Log the existing tags. At some point this should be in a better + # place, but eh. + def logtags + @tagtable.sort { |a, b| + a[0] <=> b[0] + }.each { |type, names| + names.sort { |a, b| + a[0] <=> b[0] + }.each { |name, tags| + Puppet.info "%s(%s): '%s'" % [type, name, tags.join("' '")] } - end + } + end - # Are we the top scope? - def topscope? - @level == 1 - end + # Create a new child scope. + def child=(scope) + @children.push(scope) - # Return a list of all of the defined classes. - def classlist - unless defined? @classtable - raise Puppet::DevError, "Scope did not receive class table" - end - return @classtable.values + if defined? @nodetable + scope.nodetable = @nodetable + else + raise Puppet::DevError, "No nodetable has been defined" end - # Yield each child scope in turn - def each - @children.reject { |child| - yield child - } + if defined? @classtable + scope.classtable = @classtable + else + raise Puppet::DevError, "No classtable has been defined" 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, classes = nil, parent = nil) - def evalnode(hash) - names = hash[:name] - facts = hash[:facts] - classes = hash[:classes] - parent = hash[:parent] - # First make sure there aren't any other node scopes lying around - self.nodeclean - - # If they've passed classes in, then just generate from there. - if classes - return self.gennode( - :names => names, - :facts => facts, - :classes => classes, - :parent => parent - ) - end - - scope = code = nil - # Find a node that matches one of our names - names.each { |node| - if hash = @nodetable[node] - code = hash[:node] - scope = hash[:scope] - break - end - } - - # And fail if we don't find one. - unless scope and code - raise Puppet::Error, "Could not find configuration for %s" % - names.join(" or ") - end - - # 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. We also retrieve the created - # nodescope so that we can get any classes set within it - nodescope = code.safeevaluate(:scope => scope, :facts => facts) - - # We don't need to worry about removing the Node code because - # it will be removed during translation. - - # convert the whole thing - objects = self.to_trans - - # Add any evaluated classes to our top-level object - unless nodescope.classlist.empty? - objects.classes = nodescope.classlist - end - - if objects.is_a?(Puppet::TransBucket) - objects.top = true - end - # I should do something to add the node as an object with tags - # but that will possibly end up with far too many tags. - #self.logtags - return objects + if defined? @definedtable + scope.definedtable = @definedtable + else + raise Puppet::DevError, "No definedtable has been defined" end + end - # Pull in all of the appropriate classes and evaluate them. It'd - # be nice if this didn't know quite so much about how AST::Node - # operated internally. - #def gennode(names, facts, classes, parent) - def gennode(hash) - names = hash[:names] - facts = hash[:facts] - classes = hash[:classes] - parent = hash[:parent] - name = names.shift - arghash = { - :type => name, - :code => AST::ASTArray.new(:pin => "[]") - } - - if parent - arghash[:parentclass] = parent - end + # Test whether a given scope is declarative. Even though it's + # a global value, the calling objects don't need to know that. + def declarative? + @@declarative + end - # Create the node - node = AST::Node.new(arghash) - node.keyword = "node" - - # Now evaluate it, which evaluates the parent but doesn't really - # do anything else but does return the nodescope - scope = node.safeevaluate(:scope => self) - - # And now evaluate each set klass within the nodescope. - classes.each { |klass| - if code = scope.lookuptype(klass) - #code.safeevaluate(scope, {}, klass, klass) - code.safeevaluate( - :scope => scope, - :facts => {}, - :type => klass - ) - end - } + # Remove a specific child. + def delete(child) + @children.delete(child) + end - return scope.to_trans - end + # Are we the top scope? + def topscope? + @level == 1 + end - # Retrieve a specific node. This is used in ast.rb to find a - # parent node and in findnode to retrieve and evaluate a node. - def node(name) - @nodetable[name] + # Return a list of all of the defined classes. + def classlist + unless defined? @classtable + raise Puppet::DevError, "Scope did not receive class table" end + return @classtable.values + end - # Store a host in the site node table. - def setnode(name,code) - unless defined? @nodetable - raise Puppet::DevError, "No node table defined" - end - if @nodetable.include?(name) - raise Puppet::ParseError, "Host %s is already defined" % name - else - #Puppet.warning "Setting node %s at level %s" % [name, @level] + # Yield each child scope in turn + def each + @children.reject { |child| + yield child + } + end - # We have to store both the scope that's setting the node and - # the node itself, so that the node gets evaluated in the correct - # scope. - @nodetable[name] = { + # Evaluate a list of classes. + def evalclasses(classes) + return unless classes + classes.each do |klass| + if code = lookuptype(klass) + code.safeevaluate( :scope => self, - :node => code - } + :facts => {}, + :type => klass + ) end end + end - # Evaluate normally, with no node definitions. This is a bit of a - # silly method, in that it just calls evaluate on the passed-in - # objects, and then calls to_trans on itself. It just conceals - # a paltry amount of info from whomever's using the scope object. - def evaluate(hash) - objects = hash[:ast] - facts = hash[:facts] || {} - classes = hash[:classes] || [] - facts.each { |var, value| - self.setvar(var, value) - } + # 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, classes = nil, parent = nil) + def evalnode(hash) + objects = hash[:ast] + names = hash[:names] or + raise Puppet::DevError, "Node names must be provided to evalnode" + facts = hash[:facts] + classes = hash[:classes] + parent = hash[:parent] + + scope = code = nil + # Find a node that matches one of our names + names.each { |node| + if nodehash = @nodetable[node] + code = nodehash[:node] + scope = nodehash[:scope] + break + end + } + + # And fail if we don't find one. + unless scope and code + raise Puppet::Error, "Could not find configuration for %s" % + names.join(" or ") + end + + # 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. We also retrieve the created + # scope so that we can get any classes set within it + nodescope = code.safeevaluate(:scope => scope, :facts => facts) + + scope.evalclasses(classes) + end - objects.safeevaluate(:scope => self) + # Evaluate normally, with no node definitions. This is a bit of a + # silly method, in that it just calls evaluate on the passed-in + # objects, and then calls to_trans on itself. It just conceals + # a paltry amount of info from whomever's using the scope object. + def evaluate(hash) + objects = hash[:ast] + facts = hash[:facts] || {} + + unless objects + raise Puppet::DevError, "Evaluation requires an AST tree" + end + # Set all of our facts in the top-level scope. + facts.each { |var, value| + self.setvar(var, value) + } + + # Evaluate all of our configuration. This does not evaluate any + # node definitions. + objects.safeevaluate(:scope => self) + + # If they've provided a name or a parent, we assume they're looking for nodes. + if hash.include? :parentnode + # Specifying a parent node takes precedence, because it is assumed + # that this node was found in a remote repository like ldap. + gennode(hash) + elsif hash.include? :names # else, look for it in the config + evalnode(hash) + else + # Else we're not using nodes at all, so just evaluate any passed-in + # classes. + classes = hash[:classes] || [] + evalclasses(classes) # These classes would be passed in manually, via something like # a cfengine module - classes.each { |klass| - if code = self.lookuptype(klass) - code.safeevaluate( - :scope => self, - :facts => {}, - :type => klass - ) - end - } - - objects = self.to_trans - objects.top = true - - # Add our class list - unless self.classlist.empty? - objects.classes = self.classlist - end - - return objects end - # Take all of our objects and evaluate them. - def finish - self.info "finishing" - @objectlist.each { |object| - if object.is_a? ScopeObj - self.info "finishing %s" % object.name - if obj = finishobject(object) - @children << obj - end - end - } - - @finished = true - - self.info "finished" - end - - # If the object is defined in an upper scope, then add our - # params to that upper scope; else, create a transobject - # or evaluate the definition. - def finishobject(object) - type = object.type - name = object.name - - # It should be a defined type. - definedtype = self.lookuptype(type) - - unless definedtype - error = Puppet::ParseError.new("No such type %s" % type) - error.line = object.line - error.file = object.file - raise error - end - - return definedtype.safeevaluate( - :scope => self, - :arguments => object, - :type => type, - :name => name - ) - end + objects = self.to_trans + objects.top = true - def finished? - @finished + # Add our class list + unless self.classlist.empty? + objects.classes = self.classlist end - # Initialize our new scope. Defaults to having no parent and to - # being declarative. - def initialize(hash = {}) - @parent = nil - @type = nil - @name = nil - @finished = false - hash.each { |name, val| - method = name.to_s + "=" - if self.respond_to? method - self.send(method, val) - else - raise Puppet::DevError, "Invalid scope argument %s" % name - end - } - #@parent = hash[:parent] - @nodescope = false + return objects + end - @tags = [] + # Pull in all of the appropriate classes and evaluate them. It'd + # be nice if this didn't know quite so much about how AST::Node + # operated internally. This is used when a list of classes is passed in, + # instead of a node definition, such as from the cfengine module. + def gennode(hash) + names = hash[:names] or + raise Puppet::DevError, "Node names must be provided to gennode" + facts = hash[:facts] + classes = hash[:classes] + parent = hash[:parent] + name = names.shift + arghash = { + :type => name, + :code => AST::ASTArray.new(:pin => "[]") + } + + if parent + arghash[:parentclass] = parent + end + + # Create the node + node = AST::Node.new(arghash) + node.keyword = "node" + + # Now evaluate it, which evaluates the parent and nothing else + # but does return the nodescope. + scope = node.safeevaluate(:scope => self) + + # Finally evaluate our list of classes in this new scope. + scope.evalclasses(classes) + end - if @parent.nil? - unless hash.include?(:declarative) - hash[:declarative] = true - end - self.istop(hash[:declarative]) + # Take all of our objects and evaluate them. +# def finish +# self.info "finishing" +# @objectlist.each { |object| +# if object.is_a? ScopeObj +# self.info "finishing %s" % object.name +# if obj = finishobject(object) +# @children << obj +# end +# end +# } +# +# @finished = true +# +# self.info "finished" +# end +# +# # If the object is defined in an upper scope, then add our +# # params to that upper scope; else, create a transobject +# # or evaluate the definition. +# def finishobject(object) +# type = object.type +# name = object.name +# +# # It should be a defined type. +# definedtype = lookuptype(type) +# +# unless definedtype +# error = Puppet::ParseError.new("No such type %s" % type) +# error.line = object.line +# error.file = object.file +# raise error +# end +# +# return definedtype.safeevaluate( +# :scope => self, +# :arguments => object, +# :type => type, +# :name => name +# ) +# end +# +# def finished? +# @finished +# end + + # Initialize our new scope. Defaults to having no parent and to + # being declarative. + def initialize(hash = {}) + @parent = nil + @type = nil + @name = nil + @finished = false + hash.each { |name, val| + method = name.to_s + "=" + if self.respond_to? method + self.send(method, val) else - @parent.child = self - @level = @parent.level + 1 - @interp = @parent.interp - @topscope = @parent.topscope - @context = @parent.context - end - - # Our child scopes and objects - @children = [] + raise Puppet::DevError, "Invalid scope argument %s" % name + end + } + + @tags = [] + + if @parent.nil? + unless hash.include?(:declarative) + hash[:declarative] = true + end + self.istop(hash[:declarative]) + else + @parent.child = self + @level = @parent.level + 1 + @interp = @parent.interp + @topscope = @parent.topscope + @context = @parent.context + end + + # Our child scopes and objects + @children = [] + + # The symbol table for this scope + @symtable = Hash.new(nil) + + # The type table for this scope + @typetable = 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. + @defaultstable = Hash.new { |dhash,type| + dhash[type] = Hash.new(nil) + } + + # The object table is similar, but it is actually a hash of hashes + # where the innermost objects are TransObject instances. + @objectable = Hash.new { |typehash,typekey| + # See #newobject for how to create the actual objects + typehash[typekey] = Hash.new(nil) + } + + # The list of simpler hash objects. + @objectlist = [] + + # This is just for collecting statements locally, so we can + # verify that there is no overlap within this specific scope + @localobjectable = Hash.new { |typehash,typekey| + typehash[typekey] = Hash.new(nil) + } + + # Map the names to the tables. + @map = { + "variable" => @symtable, + "type" => @typetable, + "node" => @nodetable, + "object" => @objectable, + "defaults" => @defaultstable + } + end - # The symbol table for this scope - @symtable = Hash.new(nil) + # Mark that we're the top scope, and set some hard-coded info. + def istop(declarative = true) + # the level is mostly used for debugging + @level = 1 - # The type table for this scope - @typetable = Hash.new(nil) + # The table for storing class singletons. This will only actually + # be used by top scopes and node scopes. + @classtable = 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. - @defaultstable = Hash.new { |dhash,type| - dhash[type] = Hash.new(nil) - } + self.class.declarative = declarative - # The object table is similar, but it is actually a hash of hashes - # where the innermost objects are TransObject instances. - @objectable = Hash.new { |typehash,typekey| - # See #newobject for how to create the actual objects - typehash[typekey] = Hash.new(nil) - } + # The table for all defined objects. + @definedtable = Hash.new { |types, type| + types[type] = {} + } - # The list of simpler hash objects. - @objectlist = [] + # A table for storing nodes. + @nodetable = Hash.new(nil) - # This is just for collecting statements locally, so we can - # verify that there is no overlap within this specific scope - @localobjectable = Hash.new { |typehash,typekey| - typehash[typekey] = Hash.new(nil) - } + # Eventually, if we support sites, this will allow definitions + # of nodes with the same name in different sites. For now + # the top-level scope is always the only site scope. + @sitescope = true - # Map the names to the tables. - @map = { - "variable" => @symtable, - "type" => @typetable, - "node" => @nodetable, - "object" => @objectable, - "defaults" => @defaultstable + # And create a tag table, so we can collect all of the tags + # associated with any objects created in this scope tree + @tagtable = Hash.new { |types, type| + types[type] = Hash.new { |names, name| + names[name] = [] } - end + } - # Mark that we're the top scope, and set some hard-coded info. - def istop(declarative = true) - # the level is mostly used for debugging - @level = 1 - - # The table for storing class singletons. This will only actually - # be used by top scopes and node scopes. - @classtable = Hash.new(nil) + @context = nil + @topscope = self + @type = "puppet" + @name = "top" + end - self.class.declarative = declarative + # Look up a given class. This enables us to make sure classes are + # singletons + def lookupclass(klassid) + unless defined? @classtable + raise Puppet::DevError, "Scope did not receive class table" + end + return @classtable[klassid] + end - # The table for all defined objects. This will only be - # used in the top scope if we don't have any nodescopes. - @definedtable = Hash.new { |types, type| - types[type] = {} + # Collect all of the defaults set at any higher scopes. + # This is a different type of lookup because it's additive -- + # it collects all of the defaults, with defaults in closer scopes + # overriding those in later scopes. + def lookupdefaults(type) + values = {} + + # first collect the values from the parents + unless @parent.nil? + @parent.lookupdefaults(type).each { |var,value| + values[var] = value } + end - # A table for storing nodes. - @nodetable = Hash.new(nil) - - # Eventually, if we support sites, this will allow definitions - # of nodes with the same name in different sites. For now - # the top-level scope is always the only site scope. - @sitescope = true - - # And create a tag table, so we can collect all of the tags - # associated with any objects created in this scope tree - @tagtable = Hash.new { |types, type| - types[type] = Hash.new { |names, name| - names[name] = [] - } + # then override them with any current values + # this should probably be done differently + if @defaultstable.include?(type) + @defaultstable[type].each { |var,value| + values[var] = value } + end + #Puppet.debug "Got defaults for %s: %s" % + # [type,values.inspect] + return values + end - @context = nil - @topscope = self - @type = "puppet" - @name = "top" + # Look up a node by name + def lookupnode(name) + #Puppet.debug "Looking up type %s" % name + value = lookup("type",name) + if value == :undefined + return nil + else + #Puppet.debug "Found node %s" % name + return value end + end - # This method abstracts recursive searching. It accepts the type - # of search being done and then either a literal key to search for or - # a Proc instance to do the searching. - def lookup(type,sub, usecontext = false) - table = @map[type] - if table.nil? - error = Puppet::ParseError.new( - "Could not retrieve %s table at level %s" % - [type,self.level] - ) - raise error - end + # Look up a defined type. + def lookuptype(name) + #Puppet.debug "Looking up type %s" % name + value = lookup("type",name) + if value == :undefined + return nil + else + #Puppet.debug "Found type %s" % name + return value + end + end - if sub.is_a?(Proc) and obj = sub.call(table) - return obj - elsif table.include?(sub) - return table[sub] - elsif ! @parent.nil? - #self.notice "Context is %s, parent %s is %s" % - # [self.context, @parent.type, @parent.context] - if usecontext and self.context != @parent.context - return :undefined - else - return @parent.lookup(type,sub, usecontext) + # Look up an object by name and type. This should only look up objects + # within a class structure, not within the entire scope structure. + def lookupobject(hash) + type = hash[:type] + name = hash[:name] + #Puppet.debug "Looking up object %s of type %s in level %s" % + # [name, type, @level] + sub = proc { |table| + if table.include?(type) + if table[type].include?(name) + table[type][name] end else - return :undefined + nil end + } + value = lookup("object",sub, true) + if value == :undefined + return nil + else + return value end + end - # Look up a given class. This enables us to make sure classes are - # singletons - def lookupclass(klassid) - unless defined? @classtable - raise Puppet::DevError, "Scope did not receive class table" - end - return @classtable[klassid] + # Look up a variable. The simplest value search we do. + def lookupvar(name) + #Puppet.debug "Looking up variable %s" % name + value = lookup("variable", name) + if value == :undefined + return "" + #error = Puppet::ParseError.new( + # "Undefined variable '%s'" % name + #) + #raise error + else + return value end + end - # Collect all of the defaults set at any higher scopes. - # This is a different type of lookup because it's additive -- - # it collects all of the defaults, with defaults in closer scopes - # overriding those in later scopes. - def lookupdefaults(type) - values = {} - - # first collect the values from the parents - unless @parent.nil? - @parent.lookupdefaults(type).each { |var,value| - values[var] = value - } - end - - # then override them with any current values - # this should probably be done differently - if @defaultstable.include?(type) - @defaultstable[type].each { |var,value| - values[var] = value - } - end - #Puppet.debug "Got defaults for %s: %s" % - # [type,values.inspect] - return values + # Add a new object to our object table. + def newobject(hash) + if @objectable[hash[:type]].include?(hash[:name]) + raise Puppet::DevError, "Object %s[%s] is already defined" % + [hash[:type], hash[:name]] 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 node %s" % name - return value - end - end + self.chkobjectclosure(hash) - # Look up a defined type. - def lookuptype(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 + obj = nil - # Look up an object by name and type. This should only look up objects - # within a class structure, not within the entire scope structure. - def lookupobject(hash) - type = hash[:type] - name = hash[:name] - #Puppet.debug "Looking up object %s of type %s in level %s" % - # [name, type, @level] - sub = proc { |table| - if table.include?(type) - if table[type].include?(name) - table[type][name] - end - else - nil - end - } - value = self.lookup("object",sub, true) - if value == :undefined - return nil - else - return value - end - end + # If it's a builtin type, then use a transobject, else use + # a ScopeObj, which will get replaced later. + if self.builtintype?(hash[:type]) + obj = Puppet::TransObject.new(hash[:name], hash[:type]) - # Look up a variable. The simplest value search we do. - def lookupvar(name) - #Puppet.debug "Looking up variable %s" % name - value = self.lookup("variable", name) - if value == :undefined - return "" - #error = Puppet::ParseError.new( - # "Undefined variable '%s'" % name - #) - #raise error - else - return value - end + @children << obj + else + obj = ScopeObj.new(nil) + obj.name = hash[:name] + obj.type = hash[:type] end - # Add a new object to our object table. - def newobject(hash) - if @objectable[hash[:type]].include?(hash[:name]) - raise Puppet::DevError, "Object %s[%s] is already defined" % - [hash[:type], hash[:name]] - end - - self.chkobjectclosure(hash) + @objectable[hash[:type]][hash[:name]] = obj - obj = nil + @definedtable[hash[:type]][hash[:name]] = obj - # If it's a builtin type, then use a transobject, else use - # a ScopeObj, which will get replaced later. - if self.builtintype?(hash[:type]) - obj = TransObject.new(hash[:name], hash[:type]) + # Keep them in order, just for kicks + @objectlist << obj - @children << obj - else - obj = ScopeObj.new(nil) - obj.name = hash[:name] - obj.type = hash[:type] - end - - @objectable[hash[:type]][hash[:name]] = obj + return obj + end - @definedtable[hash[:type]][hash[:name]] = obj + # Create a new scope. + def newscope(hash = {}) + hash[:parent] = self + #debug "Creating new scope, level %s" % [self.level + 1] + return Puppet::Parser::Scope.new(hash) + end - # Keep them in order, just for kicks - @objectlist << obj + # Retrieve a specific node. This is used in ast.rb to find a + # parent node and in findnode to retrieve and evaluate a node. + def node(name) + @nodetable[name] + end - return obj + # Store the fact that we've evaluated a given class. We use a hash + # that gets inherited from the top scope down, rather than a global + # hash. We store the object ID, not class name, so that we + # can support multiple unrelated classes with the same name. + def setclass(id, name) + if self.topscope? + @classtable[id] = name + else + @parent.setclass(id, name) end + end - # Create a new scope. - def newscope(hash = {}) - hash[:parent] = self - #Puppet.debug "Creating new scope, level %s" % [self.level + 1] - return Puppet::Parser::Scope.new(hash) + # Set defaults for a type. The typename should already be downcased, + # so that the syntax is isolated. + def setdefaults(type,params) + table = @defaultstable[type] + + # if we got a single param, it'll be in its own array + unless params[0].is_a?(Array) + params = [params] end - # Store the fact that we've evaluated a given class. We use a hash - # that gets inherited from the nodescope down, rather than a global - # hash. We store the object ID, not class name, so that we - # can support multiple unrelated classes with the same name. - def setclass(id, name) - if self.nodescope? or self.topscope? - @classtable[id] = name + params.each { |ary| + #Puppet.debug "Default for %s is %s => %s" % + # [type,ary[0].inspect,ary[1].inspect] + if @@declarative + if table.include?(ary[0]) + error = Puppet::ParseError.new( + "Default already defined for %s { %s }" % + [type,ary[0]] + ) + raise error + end else - @parent.setclass(id, name) + if table.include?(ary[0]) + # we should maybe allow this warning to be turned off... + Puppet.warning "Replacing default for %s { %s }" % + [type,ary[0]] + end end - end - - # Set defaults for a type. The typename should already be downcased, - # so that the syntax is isolated. - def setdefaults(type,params) - table = @defaultstable[type] + table[ary[0]] = ary[1] + } + end - # if we got a single param, it'll be in its own array - unless params[0].is_a?(Array) - params = [params] - end + # Store a host in the site node table. + def setnode(name,code) + unless defined? @nodetable + raise Puppet::DevError, "No node table defined" + end + if @nodetable.include?(name) + raise Puppet::ParseError, "Host %s is already defined" % name + else + #Puppet.warning "Setting node %s at level %s" % [name, @level] - params.each { |ary| - #Puppet.debug "Default for %s is %s => %s" % - # [type,ary[0].inspect,ary[1].inspect] - if @@declarative - if table.include?(ary[0]) - error = Puppet::ParseError.new( - "Default already defined for %s { %s }" % - [type,ary[0]] - ) - raise error - end - else - if table.include?(ary[0]) - # we should maybe allow this warning to be turned off... - Puppet.warning "Replacing default for %s { %s }" % - [type,ary[0]] - end - end - table[ary[0]] = ary[1] + # We have to store both the scope that's setting the node and + # the node itself, so that the node gets evaluated in the correct + # scope. + @nodetable[name] = { + :scope => self, + :node => code } end + end - # Define our type. - def settype(name,ltype) - # Don't let them redefine the class in this scope. - if @typetable.include?(name) - raise Puppet::ParseError, - "%s is already defined" % name - else - @typetable[name] = ltype - end + # Define our type. + def settype(name,ltype) + # Don't let them redefine the class in this scope. + if @typetable.include?(name) + raise Puppet::ParseError, + "%s is already defined" % name + else + @typetable[name] = ltype end + end - # Return an interpolated string. - def strinterp(string) - newstring = string.dup - regex = Regexp.new('\$\{(\w+)\}|\$(\w+)') - #Puppet.debug("interpreting '%s'" % string) - while match = regex.match(newstring) do - if match[1] - newstring.sub!(regex,self.lookupvar(match[1]).to_s) - elsif match[2] - newstring.sub!(regex,self.lookupvar(match[2]).to_s) - else - raise Puppet::DevError, "Could not match variable in %s" % - newstring + # This method will fail if the named object is already defined anywhere + # in the scope tree, which is what provides some minimal closure-like + # behaviour. + def setobject(hash) + # FIXME This objectlookup stuff should be looking up using both + # the name and the namevar. + + # First see if we can look the object up using normal scope + # rules, i.e., one of our parent classes has defined the + # object or something + + name = hash[:name] + type = hash[:type] + params = hash[:arguments] + file = hash[:file] + line = hash[:line] + + # Verify that we're not overriding any already-set parameters. + if localobj = @localobjectable[type][name] + params.each { |var, value| + if localobj.include?(var) + msg = "Cannot reassign attribute %s on %s[%s]" % + [var, type, name] + + error = Puppet::ParseError.new(msg) + error.line = line + error.file = file + raise error end - end - #Puppet.debug("result is '%s'" % newstring) - return newstring.gsub(/\\t/, "\t").gsub(/\\n/, "\n").gsub(/\\s/, "\s") + } end - # This method will fail if the named object is already defined anywhere - # in the scope tree, which is what provides some minimal closure-like - # behaviour. - def setobject(hash) - # FIXME This objectlookup stuff should be looking up using both - # the name and the namevar. - - # First see if we can look the object up using normal scope - # rules, i.e., one of our parent classes has defined the - # object or something - - name = hash[:name] - type = hash[:type] - params = hash[:arguments] - file = hash[:file] - line = hash[:line] - - # Verify that we're not overriding any already-set parameters. - if localobj = @localobjectable[type][name] - params.each { |var, value| - if localobj.include?(var) - msg = "Cannot reassign attribute %s on %s[%s]" % - [var, type, name] - - error = Puppet::ParseError.new(msg) - error.line = line - error.file = file - raise error - end - } - end - - if objecttype = self.lookuptype(type) - # It's a defined type - objecttype.safeevaluate( - :name => name, - :type => type, - :arguments => params, - :scope => self - ) - else - # First look for it in a parent scope - obj = self.lookupobject(:name => name, :type => type) - - unless obj and obj != :undefined - unless obj = @objectable[type][name] - obj = self.newobject( - :type => type, - :name => name, - :line => line, - :file => file - ) - - # only set these if we've created the object, - # which is the most common case - # FIXME we eventually need to store the file - # and line with each param, not the object - # itself. - obj.file = file - obj.line = line - end + if objecttype = lookuptype(type) + # It's a defined type + objecttype.safeevaluate( + :name => name, + :type => type, + :arguments => params, + :scope => self + ) + else + # First look for it in a parent scope + obj = lookupobject(:name => name, :type => type) + + unless obj and obj != :undefined + unless obj = @objectable[type][name] + obj = self.newobject( + :type => type, + :name => name, + :line => line, + :file => file + ) - # Now add our parameters. This has the function of - # overriding existing values, which might have been - # defined in a higher scope. + # only set these if we've created the object, + # which is the most common case + # FIXME we eventually need to store the file + # and line with each param, not the object + # itself. + obj.file = file + obj.line = line end - params.each { |var,value| - # Add it to our found object - obj[var] = value - } end - @localobjectable[type][name] ||= {} - + # Now add our parameters. This has the function of overriding + # existing values, which might have been defined in a higher + # scope. params.each { |var,value| - # And add it to the local table; mmm, hack - @localobjectable[type][name][var] = value + # Add it to our found object + obj[var] = value } + end - return obj + @localobjectable[type][name] ||= {} + + params.each { |var,value| + # And add it to the local table; mmm, hack + @localobjectable[type][name][var] = value + } + + return obj + end + + # Set a variable in the current scope. This will override settings + # in scopes above, but will not allow variables in the current scope + # to be reassigned if we're declarative (which is the default). + def setvar(name,value) + #Puppet.debug "Setting %s to '%s' at level %s" % + # [name.inspect,value,self.level] + if @@declarative and @symtable.include?(name) + raise Puppet::ParseError, "Cannot reassign variable %s" % name + else + if @symtable.include?(name) + Puppet.warning "Reassigning %s to %s" % [name,value] + end + @symtable[name] = value end + end - # Set a variable in the current scope. This will override settings - # in scopes above, but will not allow variables in the current scope - # to be reassigned if we're declarative (which is the default). - def setvar(name,value) - #Puppet.debug "Setting %s to '%s' at level %s" % - # [name.inspect,value,self.level] - if @@declarative and @symtable.include?(name) - raise Puppet::ParseError, "Cannot reassign variable %s" % name + # Return an interpolated string. + def strinterp(string) + newstring = string.dup + regex = Regexp.new('\$\{(\w+)\}|\$(\w+)') + #Puppet.debug("interpreting '%s'" % string) + while match = regex.match(newstring) do + if match[1] + newstring.sub!(regex,lookupvar(match[1]).to_s) + elsif match[2] + newstring.sub!(regex,lookupvar(match[2]).to_s) else - if @symtable.include?(name) - Puppet.warning "Reassigning %s to %s" % [name,value] - end - @symtable[name] = value + raise Puppet::DevError, "Could not match variable in %s" % + newstring end end + #Puppet.debug("result is '%s'" % newstring) + return newstring.gsub(/\\t/, "\t").gsub(/\\n/, "\n").gsub(/\\s/, "\s") + end + + # Add a tag to our current list. These tags will be added to all + # of the objects contained in this scope. + def tag(*ary) + ary.each { |tag| + if tag.nil? or tag == "" + Puppet.debug "got told to tag with %s" % tag.inspect + next + end + unless @tags.include?(tag) + #Puppet.info "Tagging scope %s with %s" % [self.object_id, tag] + @tags << tag.to_s + end + } + end - # Add a tag to our current list. These tags will be added to all - # of the objects contained in this scope. - def tag(*ary) - ary.each { |tag| + # Return the tags associated with this scope. It's basically + # just our parents' tags, plus our type. + def tags + tmp = [] + @tags + unless ! defined? @type or @type.nil? or @type == "" + tmp << @type.to_s + end + if @parent + @parent.tags.each { |tag| if tag.nil? or tag == "" - Puppet.debug "got told to tag with %s" % tag.inspect + Puppet.debug "parent returned tag %s" % tag.inspect next end - unless @tags.include?(tag) - #Puppet.info "Tagging scope %s with %s" % [self.object_id, tag] - @tags << tag.to_s + unless tmp.include?(tag) + tmp << tag end } end + return tmp + end - # Return the tags associated with this scope. It's basically - # just our parents' tags, plus our type. - def tags - tmp = [] + @tags - unless ! defined? @type or @type.nil? or @type == "" - tmp << @type.to_s - end - if @parent - @parent.tags.each { |tag| - if tag.nil? or tag == "" - Puppet.debug "parent returned tag %s" % tag.inspect - next - end - unless tmp.include?(tag) - tmp << tag - end - } - end - return tmp + # Used mainly for logging + def to_s + if @name + return "%s[%s]" % [@type, @name] + else + return @type.to_s end + end - # Used mainly for logging - def to_s - if @name - return "%s[%s]" % [@type, @name] + # Convert our scope to a list of Transportable objects. + def to_trans + + results = [] + + # Iterate across our child scopes and call to_trans on them + @children.each { |child| + if child.is_a?(Scope) + cresult = child.to_trans + + # Scopes normally result in a TransBucket, but they could + # also result in a normal array; if that happens, get rid + # of the array. + unless cresult.is_a?(Puppet::TransBucket) + cresult.each { |result| + results.push(result) + } + else + unless cresult.empty? + # Otherwise, just add it to our list of results. + results.push(cresult) + end + end + elsif child.is_a?(Puppet::TransObject) + if child.empty? + next + end + # Wait until the last minute to set tags, although this + # probably should not matter + child.tags = self.tags + + # Add any defaults. + self.adddefaults(child) + + # Then make sure this child's tags are stored in the + # central table. This should maybe be in the evaluate + # methods, but, eh. + @topscope.addtags(child) + results.push(child) else - return @type.to_s + raise Puppet::DevError, + "Puppet::Parse::Scope cannot handle objects of type %s" % + child.class end - end + } - # Convert our scope to a list of Transportable objects. - def to_trans - #unless self.finished? - # raise Puppet::DevError, "%s not finished" % self.type - # self.err "Not finished" - # self.finish - #end - #Puppet.debug "Translating scope %s at level %s" % - # [self.object_id,self.level] - - results = [] - - # Iterate across our child scopes and call to_trans on them - @children.each { |child| - if child.is_a?(Scope) - cresult = child.to_trans - #Puppet.debug "Got %s from scope %s" % - # [cresult.class,child.object_id] - - # Scopes normally result in a TransBucket, but they could - # also result in a normal array; if that happens, get rid - # of the array. - unless cresult.is_a?(TransBucket) - cresult.each { |result| - results.push(result) - } - else - unless cresult.empty? - # Otherwise, just add it to our list of results. - results.push(cresult) - end - end + # Get rid of any nil objects. + results = results.reject { |child| + child.nil? + } - # 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) - if child.empty? - next - end - # Wait until the last minute to set tags, although this - # probably should not matter - child.tags = self.tags - - # Add any defaults. - self.adddefaults(child) - - # Then make sure this child's tags are stored in the - # central table. This should maybe be in the evaluate - # methods, but, eh. - @topscope.addtags(child) - results.push(child) - else - raise Puppet::DevError, - "Puppet::Parse::Scope cannot handle objects of type %s" % - child.class - end - } + # If we have a name and type, then make a TransBucket, which + # becomes a component. + # Else, just stack all of the objects into the current bucket. + if @type + bucket = Puppet::TransBucket.new - # Get rid of any nil objects. - results = results.reject { |child| - child.nil? - } + if defined? @name and @name + bucket.name = @name + end - # If we have a name and type, then make a TransBucket, which - # becomes a component. - # Else, just stack all of the objects into the current bucket. - if @type - bucket = TransBucket.new + # it'd be nice not to have to do this... + results.each { |result| + #Puppet.warning "Result type is %s" % result.class + bucket.push(result) + } + if defined? @type + bucket.type = @type + else + raise Puppet::ParseError, + "No type for scope %s" % @name + end - if defined? @name and @name - bucket.name = @name - end + if defined? @keyword + bucket.keyword = @keyword + end + #Puppet.debug( + # "TransBucket with name %s and type %s in scope %s" % + # [@name,@type,self.object_id] + #) - # it'd be nice not to have to do this... - results.each { |result| - #Puppet.warning "Result type is %s" % result.class - bucket.push(result) - } - if defined? @type - bucket.type = @type + # now find metaparams + @symtable.each { |var,value| + if Puppet::Type.metaparam?(var.intern) + #Puppet.debug("Adding metaparam %s" % var) + bucket.param(var,value) else - raise Puppet::ParseError, - "No type for scope %s" % @name + #Puppet.debug("%s is not a metaparam" % var) end + } + #Puppet.debug "Returning bucket %s from scope %s" % + # [bucket.name,self.object_id] + return bucket + else + Puppet.debug "nameless scope; just returning a list" + return results + end + end - if defined? @keyword - bucket.keyword = @keyword - end - #Puppet.debug( - # "TransBucket with name %s and type %s in scope %s" % - # [@name,@type,self.object_id] - #) - - # now find metaparams - @symtable.each { |var,value| - if Puppet::Type.metaparam?(var.intern) - #Puppet.debug("Adding metaparam %s" % var) - bucket.param(var,value) - else - #Puppet.debug("%s is not a metaparam" % var) - end - } - #Puppet.debug "Returning bucket %s from scope %s" % - # [bucket.name,self.object_id] - return bucket + protected + + # This method abstracts recursive searching. It accepts the type + # of search being done and then either a literal key to search for or + # a Proc instance to do the searching. + def lookup(type,sub, usecontext = false) + table = @map[type] + if table.nil? + error = Puppet::ParseError.new( + "Could not retrieve %s table at level %s" % + [type,self.level] + ) + raise error + end + + if sub.is_a?(Proc) and obj = sub.call(table) + return obj + elsif table.include?(sub) + return table[sub] + elsif ! @parent.nil? + #self.notice "Context is %s, parent %s is %s" % + # [self.context, @parent.type, @parent.context] + if usecontext and self.context != @parent.context + return :undefined else - #Puppet.debug "nameless scope; just returning a list" - return results + return @parent.lookup(type,sub, usecontext) end + else + return :undefined end end end |