diff options
-rw-r--r-- | lib/puppet.rb | 1 | ||||
-rw-r--r-- | lib/puppet/element.rb | 5 | ||||
-rwxr-xr-x | lib/puppet/loadedfile.rb | 1 | ||||
-rw-r--r-- | lib/puppet/log.rb | 6 | ||||
-rw-r--r-- | lib/puppet/parser/ast.rb | 266 | ||||
-rw-r--r-- | lib/puppet/parser/ast/astarray.rb | 44 | ||||
-rw-r--r-- | lib/puppet/parser/ast/classdef.rb | 77 | ||||
-rw-r--r-- | lib/puppet/parser/ast/collection.rb | 97 | ||||
-rw-r--r-- | lib/puppet/parser/ast/collexpr.rb | 81 | ||||
-rw-r--r-- | lib/puppet/parser/ast/compdef.rb | 98 | ||||
-rw-r--r-- | lib/puppet/parser/ast/component.rb | 198 | ||||
-rw-r--r-- | lib/puppet/parser/ast/function.rb | 5 | ||||
-rw-r--r-- | lib/puppet/parser/ast/hostclass.rb | 166 | ||||
-rw-r--r-- | lib/puppet/parser/ast/leaf.rb | 23 | ||||
-rw-r--r-- | lib/puppet/parser/ast/node.rb | 113 | ||||
-rw-r--r-- | lib/puppet/parser/ast/nodedef.rb | 73 | ||||
-rw-r--r-- | lib/puppet/parser/ast/objectdef.rb | 353 | ||||
-rw-r--r-- | lib/puppet/parser/ast/objectref.rb | 77 | ||||
-rw-r--r-- | lib/puppet/parser/ast/resourcedef.rb | 215 | ||||
-rw-r--r-- | lib/puppet/parser/ast/resourcedefaults.rb (renamed from lib/puppet/parser/ast/typedefaults.rb) | 24 | ||||
-rw-r--r-- | lib/puppet/parser/ast/resourceoverride.rb | 62 | ||||
-rw-r--r-- | lib/puppet/parser/ast/resourceparam.rb (renamed from lib/puppet/parser/ast/objectparam.rb) | 21 | ||||
-rw-r--r-- | lib/puppet/parser/ast/resourceref.rb | 44 | ||||
-rw-r--r-- | lib/puppet/parser/ast/selector.rb | 6 | ||||
-rw-r--r-- | lib/puppet/parser/ast/vardef.rb | 14 | ||||
-rw-r--r-- | lib/puppet/parser/collector.rb | 97 | ||||
-rw-r--r-- | lib/puppet/parser/functions.rb | 46 | ||||
-rw-r--r-- | lib/puppet/parser/grammar.ra | 485 | ||||
-rw-r--r-- | lib/puppet/parser/interpreter.rb | 1059 | ||||
-rw-r--r-- | lib/puppet/parser/lexer.rb | 96 | ||||
-rw-r--r-- | lib/puppet/parser/parser.rb | 1571 | ||||
-rw-r--r-- | lib/puppet/parser/resource.rb | 324 | ||||
-rw-r--r-- | lib/puppet/parser/resource/param.rb | 44 | ||||
-rw-r--r-- | lib/puppet/parser/resource/reference.rb | 68 | ||||
-rw-r--r-- | lib/puppet/parser/scope.rb | 1544 | ||||
-rw-r--r-- | lib/puppet/parser/templatewrapper.rb | 58 | ||||
-rw-r--r-- | lib/puppet/rails/database.rb | 17 | ||||
-rw-r--r-- | lib/puppet/rails/host.rb | 54 | ||||
-rw-r--r-- | lib/puppet/rails/rails_object.rb | 42 | ||||
-rw-r--r-- | lib/puppet/rails/rails_parameter.rb | 10 | ||||
-rw-r--r-- | lib/puppet/rails/rails_resource.rb | 34 | ||||
-rw-r--r-- | lib/puppet/transportable.rb | 14 | ||||
-rw-r--r-- | lib/puppet/type.rb | 31 | ||||
-rw-r--r-- | lib/puppet/util.rb | 2 | ||||
-rw-r--r-- | lib/puppet/util/errors.rb | 55 | ||||
-rw-r--r-- | lib/puppet/util/logging.rb | 20 | ||||
-rw-r--r-- | lib/puppet/util/methodhelper.rb | 21 | ||||
-rw-r--r-- | test/data/failers/badclassnoparam (renamed from examples/code/failers/badclassnoparam) | 0 | ||||
-rw-r--r-- | test/data/failers/badclassparam (renamed from examples/code/failers/badclassparam) | 0 | ||||
-rw-r--r-- | test/data/failers/badcompnoparam (renamed from examples/code/failers/badcompnoparam) | 0 | ||||
-rw-r--r-- | test/data/failers/badcompparam (renamed from examples/code/failers/badcompparam) | 0 | ||||
-rw-r--r-- | test/data/failers/badtypeparam (renamed from examples/code/failers/badtypeparam) | 0 | ||||
-rw-r--r-- | test/data/failers/noobjectrvalue (renamed from examples/code/failers/noobjectrvalue) | 0 | ||||
-rw-r--r-- | test/data/snippets/aliastest.pp (renamed from examples/code/snippets/aliastest.pp) | 0 | ||||
-rw-r--r-- | test/data/snippets/argumentdefaults (renamed from examples/code/snippets/argumentdefaults) | 0 | ||||
-rw-r--r-- | test/data/snippets/casestatement.pp (renamed from examples/code/snippets/casestatement.pp) | 0 | ||||
-rw-r--r-- | test/data/snippets/classheirarchy.pp (renamed from examples/code/snippets/classheirarchy.pp) | 0 | ||||
-rw-r--r-- | test/data/snippets/classincludes.pp (renamed from examples/code/snippets/classincludes.pp) | 0 | ||||
-rw-r--r-- | test/data/snippets/classpathtest (renamed from examples/code/snippets/classpathtest) | 0 | ||||
-rw-r--r-- | test/data/snippets/componentmetaparams.pp (renamed from examples/code/snippets/componentmetaparams.pp) | 2 | ||||
-rw-r--r-- | test/data/snippets/deepclassheirarchy.pp (renamed from examples/code/snippets/deepclassheirarchy.pp) | 0 | ||||
-rw-r--r-- | test/data/snippets/defineoverrides.pp (renamed from examples/code/snippets/defineoverrides.pp) | 2 | ||||
-rw-r--r-- | test/data/snippets/dirchmod (renamed from examples/code/snippets/dirchmod) | 0 | ||||
-rw-r--r-- | test/data/snippets/emptyclass.pp (renamed from examples/code/snippets/emptyclass.pp) | 0 | ||||
-rw-r--r-- | test/data/snippets/emptyexec.pp (renamed from examples/code/snippets/emptyexec.pp) | 0 | ||||
-rw-r--r-- | test/data/snippets/failmissingexecpath.pp (renamed from examples/code/snippets/failmissingexecpath.pp) | 4 | ||||
-rw-r--r-- | test/data/snippets/falsevalues.pp (renamed from examples/code/snippets/falsevalues.pp) | 0 | ||||
-rw-r--r-- | test/data/snippets/filecreate (renamed from examples/code/snippets/filecreate) | 0 | ||||
-rw-r--r-- | test/data/snippets/implicititeration (renamed from examples/code/snippets/implicititeration) | 0 | ||||
-rw-r--r-- | test/data/snippets/multipleinstances (renamed from examples/code/snippets/multipleinstances) | 0 | ||||
-rw-r--r-- | test/data/snippets/multisubs.pp | 13 | ||||
-rw-r--r-- | test/data/snippets/namevartest (renamed from examples/code/snippets/namevartest) | 0 | ||||
-rw-r--r-- | test/data/snippets/scopetest (renamed from examples/code/snippets/scopetest) | 4 | ||||
-rw-r--r-- | test/data/snippets/selectorvalues.pp (renamed from examples/code/snippets/selectorvalues.pp) | 0 | ||||
-rw-r--r-- | test/data/snippets/simpledefaults (renamed from examples/code/snippets/simpledefaults) | 0 | ||||
-rw-r--r-- | test/data/snippets/simpleselector (renamed from examples/code/snippets/simpleselector) | 0 | ||||
-rw-r--r-- | test/data/snippets/singleary.pp (renamed from examples/code/snippets/singleary.pp) | 4 | ||||
-rw-r--r-- | test/data/snippets/singlequote.pp (renamed from examples/code/snippets/singlequote.pp) | 0 | ||||
-rw-r--r-- | test/data/snippets/singleselector.pp (renamed from examples/code/snippets/singleselector.pp) | 0 | ||||
-rw-r--r-- | test/data/snippets/tag.pp (renamed from examples/code/snippets/tag.pp) | 0 | ||||
-rw-r--r-- | test/data/snippets/tagged.pp (renamed from examples/code/snippets/tagged.pp) | 4 | ||||
-rwxr-xr-x | test/executables/puppetmodule.rb | 7 | ||||
-rwxr-xr-x | test/language/ast.rb | 1135 | ||||
-rwxr-xr-x | test/language/collector.rb | 206 | ||||
-rwxr-xr-x | test/language/functions.rb | 16 | ||||
-rwxr-xr-x | test/language/interpreter.rb | 709 | ||||
-rw-r--r-- | test/language/lexer.rb | 78 | ||||
-rw-r--r-- | test/language/node.rb | 72 | ||||
-rw-r--r-- | test/language/parser.rb | 377 | ||||
-rwxr-xr-x | test/language/rails.rb | 98 | ||||
-rwxr-xr-x | test/language/resource.rb | 391 | ||||
-rwxr-xr-x | test/language/scope.rb | 760 | ||||
-rwxr-xr-x | test/language/snippets.rb | 9 | ||||
-rwxr-xr-x | test/language/transportable.rb | 1 | ||||
-rw-r--r-- | test/lib/puppettest.rb | 31 | ||||
-rw-r--r-- | test/lib/puppettest/parsertesting.rb | 151 | ||||
-rw-r--r-- | test/lib/puppettest/railstesting.rb | 34 | ||||
-rw-r--r-- | test/lib/puppettest/resourcetesting.rb | 64 | ||||
-rw-r--r-- | test/lib/puppettest/support/utils.rb | 4 | ||||
-rwxr-xr-x | test/other/config.rb | 23 | ||||
-rwxr-xr-x | test/rails/rails.rb | 87 | ||||
-rwxr-xr-x | test/rails/railsparameter.rb | 50 | ||||
-rwxr-xr-x | test/rails/railsresource.rb | 60 | ||||
-rw-r--r-- | test/tagging/tagging.rb | 35 |
104 files changed, 6517 insertions, 5676 deletions
diff --git a/lib/puppet.rb b/lib/puppet.rb index a802688e8..b0b3bbea9 100644 --- a/lib/puppet.rb +++ b/lib/puppet.rb @@ -1,5 +1,6 @@ # see the bottom of the file for further inclusions require 'singleton' +require 'facter' require 'puppet/error' require 'puppet/event-loop' require 'puppet/util' diff --git a/lib/puppet/element.rb b/lib/puppet/element.rb index cc682f2e4..934fafeb9 100644 --- a/lib/puppet/element.rb +++ b/lib/puppet/element.rb @@ -7,6 +7,7 @@ require 'puppet' class Puppet::Element include Puppet include Puppet::Util + include Puppet::Util::Errors attr_writer :noop class << self @@ -48,10 +49,10 @@ class Puppet::Element @path = [@parent.path, self.class.name.to_s + "=" + self.name] end else - # The top-level name is always puppet[top], so we don't bother with + # The top-level name is always main[top], so we don't bother with # that. And we don't add the hostname here, it gets added # in the log server thingy. - if self.name == "puppet[top]" + if self.name == "main[top]" @path = ["/"] else if self.is_a?(Puppet.type(:component)) diff --git a/lib/puppet/loadedfile.rb b/lib/puppet/loadedfile.rb index b0e408475..c14bcc195 100755 --- a/lib/puppet/loadedfile.rb +++ b/lib/puppet/loadedfile.rb @@ -44,6 +44,7 @@ module Puppet "Can not use a non-existent file for parsing" end @statted = 0 + @stamp = nil @tstamp = stamp() end diff --git a/lib/puppet/log.rb b/lib/puppet/log.rb index 0659042ce..c8d92e649 100644 --- a/lib/puppet/log.rb +++ b/lib/puppet/log.rb @@ -489,10 +489,8 @@ module Puppet # If they pass a source in to us, we make sure it is a string, and # we retrieve any tags we can. def source=(source) - # We can't store the actual source, we just store the path. This - # is a bit of a stupid hack, specifically testing for elements, but - # eh. - if source.is_a?(Puppet::Element) and source.respond_to?(:path) + # We can't store the actual source, we just store the path. + if source.respond_to?(:path) @objectsource = true @source = source.path else diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb index ccde7928b..6f9c9492c 100644 --- a/lib/puppet/parser/ast.rb +++ b/lib/puppet/parser/ast.rb @@ -3,158 +3,126 @@ require 'puppet' require 'puppet/autoload' -module Puppet - module Parser - # The base class for all of the objects that make up the parse trees. - # Handles things like file name, line #, and also does the initialization - # for all of the parameters of all of the child objects. - class AST - # Do this so I don't have to type the full path in all of the subclasses - AST = Puppet::Parser::AST - - Puppet.setdefaults("ast", - :typecheck => [true, "Whether to validate types during parsing."], - :paramcheck => [true, "Whether to validate parameters during parsing."] - ) - attr_accessor :line, :file, :parent, :scope - - # Just used for 'tree', which is only used in debugging. - @@pink = "[0;31m" - @@green = "[0;32m" - @@yellow = "[0;33m" - @@slate = "[0;34m" - @@reset = "[0m" - - # Just used for 'tree', which is only used in debugging. - @@indent = " " * 4 - @@indline = @@pink + ("-" * 4) + @@reset - @@midline = @@slate + ("-" * 4) + @@reset - - @@settypes = {} - - # Just used for 'tree', which is only used in debugging. - def AST.indention - return @@indent * @@indention - end - - # Just used for 'tree', which is only used in debugging. - def AST.midline - return @@midline - end - - # Evaluate the current object. Basically just iterates across all - # of the contained children and evaluates them in turn, returning a - # list of all of the collected values, rejecting nil values - def evaluate(args) - #Puppet.debug("Evaluating ast %s" % @name) - value = self.collect { |obj| - obj.safeevaluate(args) - }.reject { |obj| - obj.nil? - } - end - - # The version of the evaluate method that should be called, because it - # correctly handles errors. It is critical to use this method because - # it can enable you to catch the error where it happens, rather than - # much higher up the stack. - def safeevaluate(*args) - begin - self.evaluate(*args) - rescue Puppet::DevError => except - except.line ||= @line - except.file ||= @file - raise - rescue Puppet::ParseError => except - except.line ||= @line - except.file ||= @file - raise - rescue => detail - error = Puppet::DevError.new( - "Child of type %s failed with error %s: %s" % - [self.class, detail.class, detail.to_s] - ) - error.line ||= @line - error.file ||= @file - error.set_backtrace detail.backtrace - raise error - end - end - - # Again, just used for printing out the parse tree. - def typewrap(string) - #return self.class.to_s.sub(/.+::/,'') + - #"(" + @@green + string.to_s + @@reset + ")" - return @@green + string.to_s + @@reset + - "(" + self.class.to_s.sub(/.+::/,'') + ")" - end - - # Initialize the object. Requires a hash as the argument, and - # takes each of the parameters of the hash and calls the settor - # method for them. This is probably pretty inefficient and should - # likely be changed at some point. - def initialize(args) - @file = nil - @line = nil - args.each { |param,value| - method = param.to_s + "=" - unless self.respond_to?(method) - error = Puppet::ParseError.new( - "Invalid parameter %s to object class %s" % - [param,self.class.to_s] - ) - error.line = self.line - error.file = self.file - raise error - end - - begin - #Puppet.debug("sending %s to %s" % [method, self.class]) - self.send(method,value) - rescue => detail - error = Puppet::DevError.new( - "Could not set parameter %s on class %s: %s" % - [method,self.class.to_s,detail] - ) - error.line ||= self.line - error.file ||= self.file - raise error - end - } - end - #--------------------------------------------------------------- - # Now autoload everything. - # XXX We can't do this, because it causes multiple loads of some - # things. - #@autoloader = Puppet::Autoload.new(self, - # "puppet/parser/ast" - #) - #@autoloader.loadall +# The base class for all of the objects that make up the parse trees. +# Handles things like file name, line #, and also does the initialization +# for all of the parameters of all of the child objects. +class Puppet::Parser::AST + # Do this so I don't have to type the full path in all of the subclasses + AST = Puppet::Parser::AST + + include Puppet::Util::Errors + include Puppet::Util::MethodHelper + + Puppet.setdefaults("ast", + :typecheck => [true, "Whether to validate types during parsing."], + :paramcheck => [true, "Whether to validate parameters during parsing."] + ) + attr_accessor :line, :file, :parent, :scope + + # Just used for 'tree', which is only used in debugging. + @@pink = "[0;31m" + @@green = "[0;32m" + @@yellow = "[0;33m" + @@slate = "[0;34m" + @@reset = "[0m" + + # Just used for 'tree', which is only used in debugging. + @@indent = " " * 4 + @@indline = @@pink + ("-" * 4) + @@reset + @@midline = @@slate + ("-" * 4) + @@reset + + @@settypes = {} + + # Just used for 'tree', which is only used in debugging. + def AST.indention + return @@indent * @@indention + end + + # Just used for 'tree', which is only used in debugging. + def AST.midline + return @@midline + end + + # Evaluate the current object. Basically just iterates across all + # of the contained children and evaluates them in turn, returning a + # list of all of the collected values, rejecting nil values + def evaluate(args) + #Puppet.debug("Evaluating ast %s" % @name) + value = self.collect { |obj| + obj.safeevaluate(args) + }.reject { |obj| + obj.nil? + } + end + + # Throw a parse error. + def parsefail(message) + self.fail(Puppet::ParseError, message) + end + + # Wrap a statemp in a reusable way so we always throw a parse error. + def parsewrap + exceptwrap :type => Puppet::ParseError do + yield + end + end + + # The version of the evaluate method that should be called, because it + # correctly handles errors. It is critical to use this method because + # it can enable you to catch the error where it happens, rather than + # much higher up the stack. + def safeevaluate(*args) + exceptwrap do + self.evaluate(*args) end end + + # Again, just used for printing out the parse tree. + def typewrap(string) + #return self.class.to_s.sub(/.+::/,'') + + #"(" + @@green + string.to_s + @@reset + ")" + return @@green + string.to_s + @@reset + + "(" + self.class.to_s.sub(/.+::/,'') + ")" + end + + # Initialize the object. Requires a hash as the argument, and + # takes each of the parameters of the hash and calls the settor + # method for them. This is probably pretty inefficient and should + # likely be changed at some point. + def initialize(args) + @file = nil + @line = nil + set_options(args) + end + #--------------------------------------------------------------- + # Now autoload everything. + # XXX We can't do this, because it causes multiple loads of some + # things. + @autoloader = Puppet::Autoload.new(self, + "puppet/parser/ast" + ) + @autoloader.loadall end -require 'puppet/parser/ast/astarray' -require 'puppet/parser/ast/branch' -require 'puppet/parser/ast/collection' -require 'puppet/parser/ast/caseopt' -require 'puppet/parser/ast/casestatement' -require 'puppet/parser/ast/classdef' -require 'puppet/parser/ast/compdef' -require 'puppet/parser/ast/component' -require 'puppet/parser/ast/else' -require 'puppet/parser/ast/hostclass' -require 'puppet/parser/ast/ifstatement' -require 'puppet/parser/ast/leaf' -require 'puppet/parser/ast/node' -require 'puppet/parser/ast/nodedef' -require 'puppet/parser/ast/objectdef' -require 'puppet/parser/ast/objectparam' -require 'puppet/parser/ast/objectref' -require 'puppet/parser/ast/selector' -require 'puppet/parser/ast/typedefaults' -require 'puppet/parser/ast/vardef' -require 'puppet/parser/ast/tag' -require 'puppet/parser/ast/function' +#require 'puppet/parser/ast/astarray' +#require 'puppet/parser/ast/branch' +#require 'puppet/parser/ast/collection' +#require 'puppet/parser/ast/caseopt' +#require 'puppet/parser/ast/casestatement' +#require 'puppet/parser/ast/component' +#require 'puppet/parser/ast/else' +#require 'puppet/parser/ast/hostclass' +#require 'puppet/parser/ast/ifstatement' +#require 'puppet/parser/ast/leaf' +#require 'puppet/parser/ast/node' +#require 'puppet/parser/ast/resourcedef' +#require 'puppet/parser/ast/resourceparam' +#require 'puppet/parser/ast/resourceref' +#require 'puppet/parser/ast/resourceoverride' +#require 'puppet/parser/ast/selector' +#require 'puppet/parser/ast/resourcedefaults' +#require 'puppet/parser/ast/vardef' +#require 'puppet/parser/ast/tag' +#require 'puppet/parser/ast/function' # $Id$ diff --git a/lib/puppet/parser/ast/astarray.rb b/lib/puppet/parser/ast/astarray.rb index c42f658fd..fb0a3f671 100644 --- a/lib/puppet/parser/ast/astarray.rb +++ b/lib/puppet/parser/ast/astarray.rb @@ -25,14 +25,10 @@ class Puppet::Parser::AST # This is such a stupid hack. I've no real idea how to make a # "real" declarative language, so I hack it so it looks like # one, yay. - definelist = [ - AST::CompDef, AST::NodeDef, AST::ClassDef - ] setlist = [ - AST::VarDef, AST::TypeDefaults, AST::Function + AST::VarDef, AST::ResourceDefaults, AST::Function ] - definers = [] settors = [] others = [] @@ -53,15 +49,13 @@ class Puppet::Parser::AST # Now sort them all according to the type of action items.each { |child| - if definelist.include?(child.class) - definers << child - elsif setlist.include?(child.class) + if setlist.include?(child.class) settors << child else others << child end } - rets = [definers, settors, others].flatten.collect { |child| + rets = [settors, others].flatten.collect { |child| child.safeevaluate(:scope => scope) } else @@ -107,37 +101,7 @@ class Puppet::Parser::AST # Used for abstracting the grammar declarations. Basically unnecessary # except that I kept finding bugs because I had too many arrays that # meant completely different things. - class ObjectInst < ASTArray; end - - # Another simple container class to make sure we can correctly arrayfy - # things. - class CompArgument < ASTArray - @@warnings = {} - def initialize(hash) - super - name = @children[0].value - - # If it's not a metaparamer, we're fine. - return unless Puppet::Type.metaparamclass(name) - - if @children[1] - if @children[1].value == false - raise Puppet::ParseError, - "%s is a metaparameter; please choose another name" % - name - else - unless @@warnings[name] - @@warnings[name] = true - Puppet.warning "%s is a metaparam; this value will inherit to all contained elements" % name - end - end - else - raise Puppet::ParseError, - "%s is a metaparameter; please choose another name" % - name - end - end - end + class ResourceInst < ASTArray; end end # $Id$ diff --git a/lib/puppet/parser/ast/classdef.rb b/lib/puppet/parser/ast/classdef.rb deleted file mode 100644 index e09db985f..000000000 --- a/lib/puppet/parser/ast/classdef.rb +++ /dev/null @@ -1,77 +0,0 @@ -require 'puppet/parser/ast/compdef' - -class Puppet::Parser::AST - # Define a new class. Syntactically similar to component definitions, - # but classes are always singletons -- only one can exist on a given - # host. - class ClassDef < AST::CompDef - @keyword = "class" - - def self.genclass - AST::HostClass - end - - def each - if @parentclass - #[@type,@args,@parentclass,@code].each { |child| yield child } - [@type,@parentclass,@code].each { |child| yield child } - else - #[@type,@args,@code].each { |child| yield child } - [@type,@code].each { |child| yield child } - end - end - - # Store our parse tree according to type. - def disabled_evaluate(hash) - scope = hash[:scope] - type = @type.safeevaluate(:scope => scope) - #args = @args.safeevaluate(:scope => scope) - - #:args => args, - arghash = { - :type => type, - :code => @code - } - - if @parentclass - arghash[:parentclass] = @parentclass.safeevaluate(:scope => scope) - end - - #Puppet.debug("defining hostclass '%s' with arguments [%s]" % - # [type,args]) - - begin - hclass = HostClass.new(arghash) - hclass.keyword = self.keyword - scope.settype(type, hclass) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::ParseError.new(detail) - error.line = self.line - error.file = self.file - error.set_backtrace detail.backtrace - raise error - end - end - - def tree(indent = 0) - #@args.tree(indent + 1), - return [ - @type.tree(indent + 1), - ((@@indline * 4 * indent) + self.typewrap("class")), - @parentclass ? @parentclass.tree(indent + 1) : "", - @code.tree(indent + 1), - ].join("\n") - end - - def to_s - return "class %s(%s) inherits %s {\n%s }" % - [@type, @parentclass, @code] - #[@type, @args, @parentclass, @code] - end - end - -end diff --git a/lib/puppet/parser/ast/collection.rb b/lib/puppet/parser/ast/collection.rb index 8e9acce57..fa480b179 100644 --- a/lib/puppet/parser/ast/collection.rb +++ b/lib/puppet/parser/ast/collection.rb @@ -1,91 +1,30 @@ -require 'puppet/rails' +require 'puppet' +require 'puppet/parser/ast/branch' +require 'puppet/parser/collector' +# An object that collects stored objects from the central cache and returns +# them to the current host, yo. class Puppet::Parser::AST - # An object that collects stored objects from the central cache and returns - # them to the current host, yo. - class Collection < AST::Branch - attr_accessor :type +class Collection < AST::Branch + attr_accessor :type, :query, :form - # We cannot evaluate directly here; instead we need to store a - # CollectType object, which will do the collection. This is - # the only way to find certain exported types in the current - # configuration. - def evaluate(hash) - scope = hash[:scope] + # We return an object that does a late-binding evaluation. + def evaluate(hash) + scope = hash[:scope] - @convertedtype = @type.safeevaluate(:scope => scope) - - scope.newcollection(self) + if self.query + q = self.query.safeevaluate :scope => scope + else + q = nil end - # Now perform the actual collection, yo. - def perform(scope) - # First get everything from the export table. - - # FIXME This will only find objects that are before us in the tree, - # which is a problem. - objects = scope.exported(@convertedtype) - - # We want to return all of these objects, and then whichever objects - # we find in the db. - array = objects.values - - # Mark all of these objects as collected, so that they also get - # returned to the client. We don't store them in our scope - # or anything, which is a little odd, but eh. - array.each do |obj| - obj.collected = true - end - - count = array.length - - # Now we also have to see if there are any exported objects - # in our own scope. - scope.lookupexported(@convertedtype).each do |obj| - objects[obj.name] = obj - obj.collected = true - end - - bucket = Puppet::TransBucket.new - - Puppet::Rails::RailsObject.find_all_by_collectable(true).each do |obj| - # FIXME This should check that the source of the object is the - # host we're running on, else it's a bad conflict. - if objects.include?(obj.name) - scope.debug("%s[%s] is already exported" % [@convertedtype, obj.name]) - next - end - count += 1 - trans = obj.to_trans - bucket.push(trans) + newcoll = Puppet::Parser::Collector.new(scope, @type, q, self.form) - args = { - :name => trans.name, - :type => trans.type, - } + scope.newcollection(newcoll) - [:file, :line].each do |param| - if val = trans.send(param) - args[param] = val - end - end - - args[:arguments] = {} - trans.each do |p,v| args[:arguments][p] = v end - - - # XXX Because the scopes don't expect objects to return values, - # we have to manually add our objects to the scope. This is - # uber-lame. - scope.setobject(args) - end - - scope.debug("Collected %s objects of type %s" % - [count, @convertedtype]) - - return bucket - end + newcoll end end +end # $Id$ diff --git a/lib/puppet/parser/ast/collexpr.rb b/lib/puppet/parser/ast/collexpr.rb new file mode 100644 index 000000000..f69b2e92e --- /dev/null +++ b/lib/puppet/parser/ast/collexpr.rb @@ -0,0 +1,81 @@ +require 'puppet' +require 'puppet/parser/ast/branch' +require 'puppet/parser/collector' + +# An object that collects stored objects from the central cache and returns +# them to the current host, yo. +class Puppet::Parser::AST +class CollExpr < AST::Branch + attr_accessor :test1, :test2, :oper, :form, :type, :parens + + # We return an object that does a late-binding evaluation. + def evaluate(hash) + scope = hash[:scope] + + # Make sure our contained expressions have all the info they need. + [@test1, @test2].each do |t| + if t.is_a?(self.class) + t.form ||= self.form + t.type ||= self.type + end + end + + # The code is only used for virtual lookups + str1, code1 = @test1.safeevaluate :scope => scope + str2, code2 = @test2.safeevaluate :scope => scope + + # First build up the virtual code. + # If we're a conjunction operator, then we're calling code. I did + # some speed comparisons, and it's at least twice as fast doing these + # case statements as doing an eval here. + code = proc do |resource| + case @oper + when "and": code1.call(resource) and code2.call(resource) + when "or": code1.call(resource) or code2.call(resource) + when "==": resource[str1] == str2 + when "!=": resource[str1] != str2 + end + end + + # Now build up the rails conditions code + if self.parens and self.form == :exported + Puppet.warning "Parentheses are ignored in Rails searches" + end + + case @oper + when "and", "or": oper = @oper.upcase + when "==": oper = "=" + else + oper = @oper + end + + if oper == "=" or oper == "!=" + # Add the rails association info where necessary + if str1 == "title" + str = "title #{oper} '#{str2}'" + else + unless self.form == :virtual or str1 == "title" + parsefail "Collection from the database only supports " + + "title matching currently" + end + str = "rails_parameters.name = '#{str1}' and " + + "rails_parameters.value #{oper} '#{str2}'" + end + else + str = [str1, oper, str2].join(" ") + end + + return str, code + end + + def initialize(hash = {}) + super + + unless %w{== != and or}.include?(@oper) + raise ArgumentError, "Invalid operator %s" % @oper + end + end +end +end + +# $Id$ diff --git a/lib/puppet/parser/ast/compdef.rb b/lib/puppet/parser/ast/compdef.rb deleted file mode 100644 index 17ee60181..000000000 --- a/lib/puppet/parser/ast/compdef.rb +++ /dev/null @@ -1,98 +0,0 @@ -class Puppet::Parser::AST - # Define a new component. This basically just stores the - # associated parse tree by name in our current scope. Note that - # there is currently a mismatch in how we look up components -- it - # usually uses scopes, but sometimes uses '@@settypes'. - # FIXME This class should verify that each of its direct children - # has an abstractable name -- i.e., if a file does not include a - # variable in its name, then the user is essentially guaranteed to - # encounter an error if the component is instantiated more than - # once. - class CompDef < AST::Branch - attr_accessor :type, :args, :code, :scope, :parentclass - attr_writer :keyword - - @keyword = "define" - - class << self - attr_reader :keyword - end - - - def self.genclass - AST::Component - end - - def each - [@type,@args,@code].each { |child| yield child } - end - - # Store the parse tree. - def evaluate(hash) - scope = hash[:scope] - arghash = {:code => @code} - arghash[:type] = @type.safeevaluate(:scope => scope) - - if @args - arghash[:args] = @args.safeevaluate(:scope => scope) - end - - if @parentclass - arghash[:parentclass] = @parentclass.safeevaluate(:scope => scope) - end - - - begin - comp = self.class.genclass.new(arghash) - comp.keyword = self.keyword - scope.settype(arghash[:type], comp) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::ParseError.new(detail) - error.line = self.line - error.file = self.file - error.set_backtrace detail.backtrace - raise error - end - end - - def initialize(hash) - @parentclass = nil - @args = nil - super - - #if @parentclass - # Puppet.notice "Parent class of %s is %s" % - # [@type.value, @parentclass.value] - #end - - #Puppet.debug "Defining type %s" % @type.value - end - - def keyword - if defined? @keyword - @keyword - else - self.class.keyword - end - end - - def tree(indent = 0) - return [ - @type.tree(indent + 1), - ((@@indline * 4 * indent) + self.typewrap("define")), - @args.tree(indent + 1), - @code.tree(indent + 1), - ].join("\n") - end - - def to_s - return "define %s(%s) {\n%s }" % [@type, @args, @code] - end - end -end - -# $Id$ diff --git a/lib/puppet/parser/ast/component.rb b/lib/puppet/parser/ast/component.rb index ebbf21959..312b11d99 100644 --- a/lib/puppet/parser/ast/component.rb +++ b/lib/puppet/parser/ast/component.rb @@ -1,80 +1,68 @@ +require 'puppet/parser/ast/branch' + class Puppet::Parser::AST # Evaluate the stored parse tree for a given component. This will # receive the arguments passed to the component and also the type and # name of the component. class Component < AST::Branch + include Puppet::Util + include Puppet::Util::Warnings + include Puppet::Util::MethodHelper class << self attr_accessor :name end # The class name - @name = :component + @name = :definition - attr_accessor :type, :args, :code, :scope, :keyword - attr_accessor :collectable, :parentclass + attr_accessor :type, :arguments, :code, :scope, :keyword + attr_accessor :exported, :namespace, :fqname, :interp # These are retrieved when looking up the superclass - attr_accessor :name, :arguments + attr_accessor :name def evaluate(hash) origscope = hash[:scope] objtype = hash[:type] - @name = hash[:name] - @arguments = hash[:arguments] || {} + name = hash[:name] + args = symbolize_options(hash[:arguments] || {}) - @collectable = hash[:collectable] + exported = hash[:exported] pscope = origscope - #pscope = if ! Puppet[:lexical] or hash[:asparent] == false - # origscope - #else - # @scope - #end - scope = pscope.newscope( - :type => @type, - :name => @name, - :keyword => self.keyword - ) - newcontext = hash[:newcontext] + scope = subscope(pscope, name) - if @collectable or origscope.collectable - scope.collectable = true + if exported or origscope.exported? + scope.exported = true end - - unless self.is_a? AST::HostClass and ! newcontext - #scope.warning "Setting context to %s" % self.object_id - scope.context = self.object_id - end - @scope = scope - + scope = scope # Additionally, add a tag for whatever kind of class # we are - scope.tag(@type) + if @type != "" + scope.tag(@type) + end - unless @name.nil? - scope.tag(@name) + unless name.nil? or name =~ /[^\w]/ + scope.tag(name) end # define all of the arguments in our local scope - if self.args + if self.arguments # Verify that all required arguments are either present or # have been provided with defaults. # FIXME This should probably also require each parent # class's arguments... - self.args.each { |arg, default| - unless @arguments.include?(arg) + self.arguments.each { |arg, default| + arg = symbolize(arg) + unless args.include?(arg) if defined? default and ! default.nil? - @arguments[arg] = default + default = default.safeevaluate :scope => scope + args[arg] = default #Puppet.debug "Got default %s for %s in %s" % # [default.inspect, arg.inspect, @name.inspect] else - error = Puppet::ParseError.new( - "Must pass %s to %s of type %s" % - [arg.inspect,@name,@type] - ) - error.line = self.line - error.file = self.file - raise error + parsefail "Must pass %s to %s of type %s" % + [arg,name,@type] end end } @@ -82,54 +70,116 @@ class Puppet::Parser::AST # Set each of the provided arguments as variables in the # component's scope. - @arguments.each { |arg,value| - begin - scope.setvar(arg,@arguments[arg]) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => except - error = Puppet::ParseError.new(except.message) - error.line = self.line - error.file = self.file - error.set_backtrace except.backtrace - raise error + args.each { |arg,value| + unless validattr?(arg) + parsefail "%s does not accept attribute %s" % [@type, arg] + end + + exceptwrap do + scope.setvar(arg.to_s,args[arg]) end } - unless @arguments.include? "name" - scope.setvar("name",@name) + unless args.include? "name" + scope.setvar("name",name) end - # Now just evaluate the code with our new bindings. - #scope.inside(self) do # part of definition inheritance - self.code.safeevaluate(:scope => scope) - #end + if self.code + return self.code.safeevaluate(:scope => scope) + else + return nil + end + end + + def initialize(hash = {}) + @arguments = nil + @parentclass = nil + super + + # Deal with metaparams in the argument list. + if @arguments + @arguments.each do |arg, defvalue| + next unless Puppet::Type.metaparamclass(arg) + if defvalue + warnonce "%s is a metaparam; this value will inherit to all contained elements" % arg + else + raise Puppet::ParseError, + "%s is a metaparameter; please choose another name" % + name + end + end + end + end + + def parentclass + parentobj do |name| + @interp.findclass(namespace, name) + end + end - # If we're being evaluated as a parent class, we want to return the - # scope, so it can be overridden and such, but if not, we want to - # return a TransBucket of our objects. - if hash.include?(:asparent) - return scope + # Set our parent class, with a little check to avoid some potential + # weirdness. + def parentclass=(name) + if name == @type + parsefail "Parent classes must have dissimilar names" + end + + @parentclass = name + end + + # Hunt down our class object. + def parentobj + if @parentclass + # Cache our result, since it should never change. + unless @parentclass.is_a?(AST::HostClass) + unless tmp = yield(@parentclass) + parsefail "Could not find %s %s" % [self.class.name, @parentclass] + end + + if tmp == self + parsefail "Parent classes must have dissimilar names" + end + + @parentclass = tmp + end + @parentclass else - return scope.to_trans + nil end end + # Create a new subscope in which to evaluate our code. + def subscope(scope, name = nil) + args = { + :type => @type, + :keyword => self.keyword, + :namespace => self.namespace + } + + args[:name] = name if name + args[:type] = self.type if self.type + scope = scope.newscope(args) + scope.source = self + + return scope + end + + def to_s + fqname + end + # Check whether a given argument is valid. Searches up through # any parent classes that might exist. - def validarg?(param) + def validattr?(param) + return true if Puppet::Type.metaparam?(param) + + param = param.to_s found = false - unless @args.is_a? Array - @args = [@args] + unless @arguments.is_a? Array + @arguments = [@arguments] end - found = @args.detect { |arg| + found = @arguments.detect { |arg| if arg.is_a? Array arg[0] == param else diff --git a/lib/puppet/parser/ast/function.rb b/lib/puppet/parser/ast/function.rb index 2c63c8b76..f67531ae3 100644 --- a/lib/puppet/parser/ast/function.rb +++ b/lib/puppet/parser/ast/function.rb @@ -11,7 +11,10 @@ class Puppet::Parser::AST args = @arguments.safeevaluate(:scope => scope) - return scope.send("function_" + @name, args) + #exceptwrap :message => "Failed to execute %s" % @name, + # :type => Puppet::ParseError do + return scope.send("function_" + @name, args) + #end end def initialize(hash) diff --git a/lib/puppet/parser/ast/hostclass.rb b/lib/puppet/parser/ast/hostclass.rb index 44077983d..4a4664f0f 100644 --- a/lib/puppet/parser/ast/hostclass.rb +++ b/lib/puppet/parser/ast/hostclass.rb @@ -1,3 +1,5 @@ +require 'puppet/parser/ast/component' + class Puppet::Parser::AST # The code associated with a class. This is different from components # in that each class is a singleton -- only one will exist for a given @@ -5,152 +7,50 @@ class Puppet::Parser::AST class HostClass < AST::Component @name = :class + # Are we a child of the passed class? Do a recursive search up our + # parentage tree to figure it out. + def child_of?(klass) + return false unless self.parentclass + + if klass == self.parentclass + return true + else + return self.parentclass.child_of?(klass) + end + end + + # Evaluate the code associated with this class. def evaluate(hash) scope = hash[:scope] - objname = hash[:name] args = hash[:arguments] + # Verify that we haven't already been evaluated - # FIXME The second subclass won't evaluate the parent class - # code at all, and any overrides will throw an error. - if myscope = scope.lookupclass(self.object_id) + if scope.setclass?(self) Puppet.debug "%s class already evaluated" % @type - - # Not used, but will eventually be used to fix #140. - if myscope.is_a? Puppet::Parser::Scope - unless scope.object_id == myscope.object_id - #scope.parent = myscope - end - end return nil end - # Set the class before we do anything else, so that it's set - # during the evaluation and can be inspected. - scope.setclass(self.object_id, @type) - - origscope = scope - - # Default to creating a new context - newcontext = true - - # If we've got a parent, then we pass it the original scope we - # received. It will get passed all the way up to the top class, - # which will create a subscope and pass that subscope to its - # subclass. - if @parentscope = self.evalparent( - :scope => scope, :arguments => args, :name => objname - ) - if @parentscope.is_a? Puppet::TransBucket - raise Puppet::DevError, "Got a bucket instead of a scope" - end - - # Override our scope binding with the parent scope - # binding. - scope = @parentscope - - # But don't create a new context if our parent created one - newcontext = false - end - - # Just use the Component evaluate method, but change the type - # to our own type. - result = super( - :scope => scope, - :arguments => args, - :type => @type, - :name => objname, # might be nil - :newcontext => newcontext, - :asparent => hash[:asparent] || false # might be nil - ) - - # Now set the class again, this time using the scope. This way - # we can look up the parent scope of this class later, so we - # can hook the children together. - scope.setscope(self.object_id, result) - - # This is important but painfully difficult. If we're the top-level - # class, that is, we have no parent classes, then the transscope - # is our own scope, but if there are parent classes, then the topmost - # parent's scope is the transscope, since it contains its code and - # all of the subclass's code. - transscope ||= result - - if hash[:asparent] - # If we're a parent class, then return the scope object itself. - return result - else - transscope = nil - if @parentscope - transscope = @parentscope - until transscope.parent.object_id == origscope.object_id - transscope = transscope.parent - end + if @parentclass + if pklass = self.parentclass + pklass.safeevaluate :scope => scope else - transscope = result + parsefail "Could not find class %s" % @parentclass end - - # But if we're the final subclass, translate the whole scope tree - # into TransObjects and TransBuckets. - return transscope.to_trans end - end - # Evaluate our parent class. Parent classes are evaluated in the - # exact same scope as the children. This is maybe not a good idea - # but, eh. - #def evalparent(scope, args, name) - def evalparent(hash) - scope = hash[:scope] - args = hash[:arguments] - name = hash[:name] - if @parentclass - #scope.warning "parent class of %s is %s" % - # [@type, @parentclass.inspect] - parentobj = nil + # Set the class before we do anything else, so that it's set + # during the evaluation and can be inspected. + scope.setclass(self) - begin - parentobj = scope.lookuptype(@parentclass) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::ParseError.new(detail) - error.line = self.line - error.file = self.file - raise error - end - unless parentobj - error = Puppet::ParseError.new( - "Could not find parent '%s' of '%s'" % - [@parentclass,@name]) - error.line = self.line - error.file = self.file - raise error - end + unless hash[:nosubscope] + scope = subscope(scope) + end - # Verify that the parent and child are of the same type - unless parentobj.class == self.class - error = Puppet::ParseError.new( - "Class %s has incompatible parent type, %s vs %s" % - [@type, parentobj.class, self.class] - ) - error.file = self.file - error.line = self.line - raise error - end - # We don't need to pass the type, because the parent will just - # use its own type. Specify that it's being evaluated as a parent, - # so that it returns the scope, not a transbucket. - return parentobj.safeevaluate( - :scope => scope, - :arguments => args, - :name => name, - :asparent => true, - :collectable => self.collectable - ) + # Now evaluate our code, yo. + if self.code + return self.code.evaluate(:scope => scope) else - return false + return nil end end @@ -158,7 +58,7 @@ class Puppet::Parser::AST @parentclass = nil super end - end - end + +# $Id$ diff --git a/lib/puppet/parser/ast/leaf.rb b/lib/puppet/parser/ast/leaf.rb index 7b9a283d7..298c0168f 100644 --- a/lib/puppet/parser/ast/leaf.rb +++ b/lib/puppet/parser/ast/leaf.rb @@ -28,15 +28,11 @@ class Puppet::Parser::AST def initialize(hash) super - unless @value == 'true' or @value == 'false' + unless @value == true or @value == false raise Puppet::DevError, "'%s' is not a boolean" % @value end - if @value == 'true' - @value = true - else - @value = false - end + @value end end @@ -69,6 +65,9 @@ class Puppet::Parser::AST # Lower-case words. class Name < AST::Leaf; end + # double-colon separated class names + class ClassName < AST::Leaf; end + # Host names, either fully qualified or just the short name class HostName < AST::Leaf def initialize(hash) @@ -87,18 +86,8 @@ class Puppet::Parser::AST # Looks up the value of the object in the scope tree (does # not include syntactical constructs, like '$' and '{}'). def evaluate(hash) - begin + parsewrap do return hash[:scope].lookupvar(@value) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::DevError.new(detail) - error.line = self.line - error.file = self.file - error.set_backtrace detail.backtrace - raise error end end end diff --git a/lib/puppet/parser/ast/node.rb b/lib/puppet/parser/ast/node.rb index e4e69bed9..3f508b2bf 100644 --- a/lib/puppet/parser/ast/node.rb +++ b/lib/puppet/parser/ast/node.rb @@ -1,113 +1,70 @@ +require 'puppet/parser/ast/hostclass' + class Puppet::Parser::AST # The specific code associated with a host. Nodes are annoyingly unlike # other objects. That's just the way it is, at least for now. class Node < AST::HostClass @name = :node - attr_accessor :type, :args, :code, :parentclass + attr_accessor :name #def evaluate(scope, facts = {}) def evaluate(hash) - origscope = hash[:scope] - facts = hash[:facts] || {} - - # nodes are never instantiated like a normal object, - # but we need the type to be the name users would use for - # instantiation, otherwise tags don't work out + scope = hash[:scope] - pscope = origscope #pscope = if ! Puppet[:lexical] or hash[:asparent] # @scope #else # origscope #end - scope = pscope.newscope( - :type => self.type, - :keyword => @keyword - ) - scope.context = self.object_id + Puppet.warning "%s => %s" % [scope.type, self.name] - # Now set all of the facts inside this scope - facts.each { |var, value| - scope.setvar(var, value) - } - - if tmp = self.evalparent(scope) - # Again, override our scope with the parent scope, if - # there is one. - scope = tmp + # We don't have to worry about the declarativeness of node parentage, + # because the entry point is always a single node definition. + if parent = self.parentclass + scope = parent.safeevaluate :scope => scope end + Puppet.notice "%s => %s" % [scope.type, self.name] - #scope.tag(@name) - - # We never pass the facts to the parent class, because they've - # already been defined at this top-level scope. - #super(scope, facts, @name, @name) + scope = scope.newscope( + :type => self.name, + :keyword => @keyword, + :source => self, + :namespace => "" # nodes are always in "" + ) + Puppet.info "%s => %s" % [scope.type, self.name] # Mark our node name as a class, too, but strip it of the domain # name. Make the mark before we evaluate the code, so that it is # marked within the code itself. - scope.setclass(self.object_id, @type.sub(/\..+/, '')) + scope.setclass(self) - # And then evaluate our code. - @code.safeevaluate(:scope => scope) + # And then evaluate our code if we have any + if self.code + @code.safeevaluate(:scope => scope) + end return scope end - # Evaluate our parent class. - def evalparent(scope) - if @parentclass - # This is pretty messed up. I don't know if this will - # work in the long term, but we need to evaluate the node - # in our own scope, even though our parent node has - # a scope associated with it, because otherwise we 1) won't - # get our facts defined, and 2) we won't actually get the - # objects returned, based on how nodes work. - - # We also can't just evaluate the node itself, because - # it would create a node scope within this scope, - # and that would cause mass havoc. - node = nil - - # The 'node' method just returns a hash of the node - # code and name. It's used here, and in 'evalnode'. - unless hash = scope.node(@parentclass) - raise Puppet::ParseError, - "Could not find parent node %s" % - @parentclass - end - - node = hash[:node] - type = nil - if type = node.type - scope.tag(node.type) - end - - begin - code = node.code - code.safeevaluate(:scope => scope, :asparent => true) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::ParseError.new(detail) - error.line = self.line - error.file = self.file - raise error - end + def initialize(hash) + @parentclass = nil + super - if node.parentclass - node.evalparent(scope) - end + # Do some validation on the node name + if @name =~ /[^-\w.]/ + raise Puppet::ParseError, "Invalid node name %s" % @name end end - def initialize(hash) - @parentclass = nil - super + def parentclass + parentobj do |name| + @interp.nodesearch(name) + end + @parentclass end end end + +# $Id$ diff --git a/lib/puppet/parser/ast/nodedef.rb b/lib/puppet/parser/ast/nodedef.rb deleted file mode 100644 index 06b104828..000000000 --- a/lib/puppet/parser/ast/nodedef.rb +++ /dev/null @@ -1,73 +0,0 @@ -class Puppet::Parser::AST - # Define a node. The node definition stores a parse tree for each - # specified node, and this parse tree is only ever looked up when - # a client connects. - class NodeDef < AST::Branch - attr_accessor :names, :code, :parentclass, :keyword, :scope - - def each - [@names,@code].each { |child| yield child } - end - - # Do implicit iteration over each of the names passed. - def evaluate(hash) - scope = hash[:scope] - names = @names.safeevaluate(:scope => scope) - - unless names.is_a?(Array) - names = [names] - end - - names.each { |name| - #Puppet.debug("defining host '%s' in scope %s" % - # [name, scope.object_id]) - # We use 'type' here instead of name, because every component - # type supports both 'type' and 'name', and 'type' is a more - # appropriate description of the syntactic role that this term - # plays. - arghash = { - :type => name, - :code => @code - } - - if @parentclass - arghash[:parentclass] = @parentclass.safeevaluate(:scope => scope) - end - - begin - node = Node.new(arghash) - node.keyword = true - scope.setnode(name, node) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::ParseError.new(detail) - error.line = self.line - error.file = self.file - raise error - end - } - end - - def initialize(hash) - @parentclass = nil - @keyword = "node" - super - end - - def tree(indent = 0) - return [ - @names.tree(indent + 1), - ((@@indline * 4 * indent) + self.typewrap("node")), - @code.tree(indent + 1), - ].join("\n") - end - - def to_s - return "node %s {\n%s }" % [@name, @code] - end - end - -end diff --git a/lib/puppet/parser/ast/objectdef.rb b/lib/puppet/parser/ast/objectdef.rb deleted file mode 100644 index ac5fe3070..000000000 --- a/lib/puppet/parser/ast/objectdef.rb +++ /dev/null @@ -1,353 +0,0 @@ -class Puppet::Parser::AST - # Any normal puppet object declaration. Can result in a class or a - # component, in addition to builtin types. - class ObjectDef < AST::Branch - attr_accessor :name, :type, :collectable - attr_reader :params - - # probably not used at all - def []=(index,obj) - @params[index] = obj - end - - # probably not used at all - def [](index) - return @params[index] - end - - # Iterate across all of our children. - def each - [@type,@name,@params].flatten.each { |param| - #Puppet.debug("yielding param %s" % param) - yield param - } - end - - # Does not actually return an object; instead sets an object - # in the current scope. - def evaluate(hash) - scope = hash[:scope] - @scope = scope - hash = {} - - # Get our type and name. - objtype = @type.safeevaluate(:scope => scope) - - # Disable definition inheritance, for now. 8/27/06, luke - #if objtype == "super" - # objtype = supertype() - # @subtype = true - #else - @subtype = false - #end - - # If the type was a variable, we wouldn't have typechecked yet. - # Do it now, if so. - unless @checked - self.typecheck(objtype) - end - - # See if our object type was defined. If not, we know it's - # builtin because we already typechecked. - begin - object = scope.lookuptype(objtype) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::ParseError.new(detail) - error.line = self.line - error.file = self.file - error.set_backtrace detail.backtrace - raise error - end - - hash = {} - # Evaluate all of the specified params. - @params.each { |param| - ary = param.safeevaluate(:scope => scope) - hash[ary[0]] = ary[1] - } - - # Now collect info from our parent. - parentname = nil - if @subtype - parentname = supersetup(hash) - end - - objnames = [nil] - # Determine our name if we have one. - if self.name - objnames = @name.safeevaluate(:scope => scope) - # it's easier to always use an array, even for only one name - unless objnames.is_a?(Array) - objnames = [objnames] - end - else - if parentname - objnames = [parentname] - else - # See if they specified the name as a parameter instead of - # as a normal name (i.e., before the colon). - unless object # we're a builtin - if objclass = Puppet::Type.type(objtype) - namevar = objclass.namevar - - tmp = hash["name"] || hash[namevar.to_s] - - if tmp - objnames = [tmp] - end - else - # this should never happen, because we've already - # typechecked, but it's no real problem if it does - # happen. We just end up with an object with no - # name. - end - end - end - end - - # this is where our implicit iteration takes place; - # if someone passed an array as the name, then we act - # just like the called us many times - objnames.collect { |objname| - # If the object is a class, that means it's a builtin type, so - # we just store it in the scope - begin - #Puppet.debug( - # ("Setting object '%s' " + - # "in scope %s " + - # "with arguments %s") % - # [objname, scope.object_id, hash.inspect] - #) - obj = scope.setobject( - :type => objtype, - :name => objname, - :arguments => hash, - :file => @file, - :line => @line, - :collectable => self.collectable - ) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::ParseError.new(detail) - error.line = self.line - error.file = self.file - error.set_backtrace detail.backtrace - raise error - end - }.reject { |obj| obj.nil? } - end - - # Create our ObjectDef. Handles type checking for us. - def initialize(hash) - @checked = false - super - - #self.typecheck(@type.value) - end - - # Verify that all passed parameters are valid - def paramcheck(builtin, objtype) - # This defaults to true - unless Puppet[:paramcheck] - return - end - - @params.each { |param| - if builtin - self.parambuiltincheck(builtin, param) - else - self.paramdefinedcheck(objtype, param) - end - } - - # Mark that we've made it all the way through. - @checked = true - end - - def parambuiltincheck(type, param) - unless param.is_a?(AST::ObjectParam) - raise Puppet::DevError, - "Got something other than param" - end - begin - pname = param.param.value - rescue => detail - raise Puppet::DevError, detail.to_s - end - - return if pname == "name" # always allow these - unless type.validattr?(pname) - error = Puppet::ParseError.new( - "Invalid parameter '%s' for type '%s'" % - [pname, type.name] - ) - error.line = self.line - error.file = self.file - raise error - end - end - - def paramdefinedcheck(objtype, param) - # FIXME We might need to do more here eventually. Metaparams - # behave strangely on containers. - if Puppet::Type.metaparam?(param.param.value.intern) - return - end - - begin - pname = param.param.value - rescue => detail - raise Puppet::DevError, detail.to_s - end - - unless objtype.validarg?(pname) - error = Puppet::ParseError.new( - "Invalid parameter '%s' for type '%s'" % - [pname,objtype.type] - ) - error.line = self.line - error.file = self.file - raise error - end - end - - # Set the parameters for our object. - def params=(params) - if params.is_a?(AST::ASTArray) - @params = params - else - @params = AST::ASTArray.new( - :line => params.line, - :file => params.file, - :children => [params] - ) - end - end - - def supercomp - unless defined? @supercomp - if @scope and comp = @scope.inside - @supercomp = comp - else - error = Puppet::ParseError.new( - "'super' is only valid within definitions" - ) - error.line = self.line - error.file = self.file - raise error - end - end - @supercomp - end - - # Take all of the arguments of our parent and add them into our own, - # without overriding anything. - def supersetup(hash) - comp = supercomp() - - # Now check each of the arguments from the parent. - comp.arguments.each do |name, value| - unless hash.has_key? name - hash[name] = value - end - end - - # Return the parent name, so it can be used if appropriate. - return comp.name - end - - # Retrieve our supertype. - def supertype - unless defined? @supertype - if parent = supercomp.parentclass - @supertype = parent - else - error = Puppet::ParseError.new( - "%s does not have a parent class" % comp.type - ) - error.line = self.line - error.file = self.file - raise error - end - end - @supertype - end - - # Print this object out. - def tree(indent = 0) - return [ - @type.tree(indent + 1), - @name.tree(indent + 1), - ((@@indline * indent) + self.typewrap(self.pin)), - @params.collect { |param| - begin - param.tree(indent + 1) - rescue NoMethodError => detail - Puppet.err @params.inspect - error = Puppet::DevError.new( - "failed to tree a %s" % self.class - ) - error.set_backtrace detail.backtrace - raise error - end - }.join("\n") - ].join("\n") - end - - # Verify that the type is valid. This throws an error if there's - # a problem, so the return value doesn't matter - def typecheck(objtype) - # This will basically always be on, but I wanted to make it at - # least simple to turn off if it came to that - unless Puppet[:typecheck] - return - end - - builtin = false - begin - builtin = Puppet::Type.type(objtype) - rescue TypeError - # nothing; we've already set builtin to false - end - - typeobj = nil - if builtin - self.paramcheck(builtin, objtype) - else - # If there's no set scope, then we're in initialize, not - # evaluate, so we can't test defined types. - return true unless defined? @scope and @scope - - # Unless we can look up the type, throw an error - unless typeobj = @scope.lookuptype(objtype) - error = Puppet::ParseError.new( - "Unknown type '%s'" % objtype - ) - error.line = self.line - error.file = self.file - raise error - end - - # Now that we have the type, verify all of the parameters. - # Note that we're now passing an AST Class object or whatever - # as the type, not a simple string. - self.paramcheck(builtin, typeobj) - end - end - - def to_s - return "%s => { %s }" % [@name, - @params.collect { |param| - param.to_s - }.join("\n") - ] - end - end -end diff --git a/lib/puppet/parser/ast/objectref.rb b/lib/puppet/parser/ast/objectref.rb deleted file mode 100644 index f9a63e222..000000000 --- a/lib/puppet/parser/ast/objectref.rb +++ /dev/null @@ -1,77 +0,0 @@ -class Puppet::Parser::AST - # A reference to an object. Only valid as an rvalue. - class ObjectRef < AST::Branch - attr_accessor :name, :type - - def each - [@type,@name].flatten.each { |param| - #Puppet.debug("yielding param %s" % param) - yield param - } - end - - # Evaluate our object, but just return a simple array of the type - # and name. - def evaluate(hash) - scope = hash[:scope] - objtype = @type.safeevaluate(:scope => scope) - objnames = @name.safeevaluate(:scope => scope) - - # it's easier to always use an array, even for only one name - unless objnames.is_a?(Array) - objnames = [objnames] - end - - # See if we can look the object up in our scope tree. - begin - object = scope.lookuptype(objtype) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::ParseError.new(detail) - error.line = self.line - error.file = self.file - error.set_backtrace detail.backtrace - raise error - end - - # If the type isn't defined, verify that it's builtin - unless object or Puppet::Type.type(objtype) - error = Puppet::ParseError.new("Could not find type %s" % - objtype.inspect) - error.line = self.line - error.file = self.file - raise error - end - - # should we implicitly iterate here? - # yes, i believe that we essentially have to... - ret = objnames.collect { |objname| - if object.is_a?(AST::Component) - objname = "%s[%s]" % [objtype,objname] - objtype = "component" - end - [objtype,objname] - }.reject { |obj| obj.nil? } - - # Return a flattened array, since we know that we've created an - # array - return *ret - end - - def tree(indent = 0) - return [ - @type.tree(indent + 1), - @name.tree(indent + 1), - ((@@indline * indent) + self.typewrap(self.pin)) - ].join("\n") - end - - def to_s - return "%s[%s]" % [@name,@type] - end - end - -end diff --git a/lib/puppet/parser/ast/resourcedef.rb b/lib/puppet/parser/ast/resourcedef.rb new file mode 100644 index 000000000..1c9333030 --- /dev/null +++ b/lib/puppet/parser/ast/resourcedef.rb @@ -0,0 +1,215 @@ +require 'puppet/parser/ast/branch' + +# Any normal puppet object declaration. Can result in a class or a +# component, in addition to builtin types. +class Puppet::Parser::AST +class ResourceDef < AST::Branch + attr_accessor :title, :type, :exported, :virtual + attr_reader :params + + # probably not used at all + def []=(index,obj) + @params[index] = obj + end + + # probably not used at all + def [](index) + return @params[index] + end + + # Iterate across all of our children. + def each + [@type,@title,@params].flatten.each { |param| + #Puppet.debug("yielding param %s" % param) + yield param + } + end + + # Does not actually return an object; instead sets an object + # in the current scope. + def evaluate(hash) + scope = hash[:scope] + @scope = scope + hash = {} + + # Get our type and name. + objtype = @type + + # Disable definition inheritance, for now. 8/27/06, luke + #if objtype == "super" + # objtype = supertype() + # @subtype = true + #else + @subtype = false + #end + + # Evaluate all of the specified params. + paramobjects = @params.collect { |param| + param.safeevaluate(:scope => scope) + } + + # Now collect info from our parent. + parentname = nil + if @subtype + parentname = supersetup(hash) + end + + objtitles = nil + # Determine our name if we have one. + if self.title + objtitles = @title.safeevaluate(:scope => scope) + # it's easier to always use an array, even for only one name + unless objtitles.is_a?(Array) + objtitles = [objtitles] + end + else + if parentname + objtitles = [parentname] + else + # See if they specified the name as a parameter instead of + # as a normal name (i.e., before the colon). + unless object # we're a builtin + if objclass = Puppet::Type.type(objtype) + namevar = objclass.namevar + + tmp = hash["name"] || hash[namevar.to_s] + + if tmp + objtitles = [tmp] + end + else + # This isn't grammatically legal. + raise Puppet::ParseError, "Got a resource with no title" + end + end + end + end + + # This is where our implicit iteration takes place; if someone + # passed an array as the name, then we act just like the called us + # many times. + objtitles.collect { |objtitle| + exceptwrap :type => Puppet::ParseError do + obj = Puppet::Parser::Resource.new( + :type => objtype, + :title => objtitle, + :params => paramobjects, + :file => @file, + :line => @line, + :exported => self.exported || scope.exported, + :virtual => self.virtual, + :source => scope.source, + :scope => scope + ) + + # And then store the resource in the scope. + # XXX At some point, we need to switch all of this to return + # objects instead of storing them like this. + scope.setresource(obj) + obj + end + }.reject { |obj| obj.nil? } + end + + # Create our ResourceDef. Handles type checking for us. + def initialize(hash) + @checked = false + super + + #self.typecheck(@type.value) + end + + # Set the parameters for our object. + def params=(params) + if params.is_a?(AST::ASTArray) + @params = params + else + @params = AST::ASTArray.new( + :line => params.line, + :file => params.file, + :children => [params] + ) + end + end + + def supercomp + unless defined? @supercomp + if @scope and comp = @scope.inside + @supercomp = comp + else + error = Puppet::ParseError.new( + "'super' is only valid within definitions" + ) + error.line = self.line + error.file = self.file + raise error + end + end + @supercomp + end + + # Take all of the arguments of our parent and add them into our own, + # without overriding anything. + def supersetup(hash) + comp = supercomp() + + # Now check each of the arguments from the parent. + comp.arguments.each do |name, value| + unless hash.has_key? name + hash[name] = value + end + end + + # Return the parent name, so it can be used if appropriate. + return comp.name + end + + # Retrieve our supertype. + def supertype + unless defined? @supertype + if parent = supercomp.parentclass + @supertype = parent + else + error = Puppet::ParseError.new( + "%s does not have a parent class" % comp.type + ) + error.line = self.line + error.file = self.file + raise error + end + end + @supertype + end + + # Print this object out. + def tree(indent = 0) + return [ + @type.tree(indent + 1), + @title.tree(indent + 1), + ((@@indline * indent) + self.typewrap(self.pin)), + @params.collect { |param| + begin + param.tree(indent + 1) + rescue NoMethodError => detail + Puppet.err @params.inspect + error = Puppet::DevError.new( + "failed to tree a %s" % self.class + ) + error.set_backtrace detail.backtrace + raise error + end + }.join("\n") + ].join("\n") + end + + def to_s + return "%s => { %s }" % [@title, + @params.collect { |param| + param.to_s + }.join("\n") + ] + end +end +end + +# $Id$ diff --git a/lib/puppet/parser/ast/typedefaults.rb b/lib/puppet/parser/ast/resourcedefaults.rb index 2e11a1b94..44db4d465 100644 --- a/lib/puppet/parser/ast/typedefaults.rb +++ b/lib/puppet/parser/ast/resourcedefaults.rb @@ -1,32 +1,22 @@ class Puppet::Parser::AST - # A statement syntactically similar to an ObjectDef, but uses a + # A statement syntactically similar to an ResourceDef, but uses a # capitalized object type and cannot have a name. - class TypeDefaults < AST::Branch + class ResourceDefaults < AST::Branch attr_accessor :type, :params def each [@type,@params].each { |child| yield child } end - # As opposed to ObjectDef, this stores each default for the given + # As opposed to ResourceDef, this stores each default for the given # object type. def evaluate(hash) scope = hash[:scope] - type = @type.safeevaluate(:scope => scope) + type = @type.downcase params = @params.safeevaluate(:scope => scope) - begin - scope.setdefaults(type.downcase,params) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::ParseError.new(detail) - error.line = self.line - error.file = self.file - error.set_backtrace detail.backtrace - raise error + parsewrap do + scope.setdefaults(type, params) end end @@ -44,3 +34,5 @@ class Puppet::Parser::AST end end + +# $Id$ diff --git a/lib/puppet/parser/ast/resourceoverride.rb b/lib/puppet/parser/ast/resourceoverride.rb new file mode 100644 index 000000000..26d69ae97 --- /dev/null +++ b/lib/puppet/parser/ast/resourceoverride.rb @@ -0,0 +1,62 @@ +require 'puppet/parser/ast/resourcedef' + +class Puppet::Parser::AST + # Set a parameter on a resource specification created somewhere else in the + # configuration. The object is responsible for verifying that this is allowed. + class ResourceOverride < ResourceDef + attr_accessor :object + attr_reader :params + + # Iterate across all of our children. + def each + [@object,@params].flatten.each { |param| + #Puppet.debug("yielding param %s" % param) + yield param + } + end + + # Does not actually return an object; instead sets an object + # in the current scope. + def evaluate(hash) + scope = hash[:scope] + + # Get our object reference. + object = @object.safeevaluate(:scope => scope) + + hash = {} + + # Evaluate all of the specified params. + params = @params.collect { |param| + param.safeevaluate(:scope => scope) + } + + # Now we just create a normal resource, but we call a very different + # method on the scope. + obj = Puppet::Parser::Resource.new( + :type => object.type, + :title => object.title, + :params => params, + :file => @file, + :line => @line, + :source => scope.source, + :scope => scope + ) + + # Now we tell the scope that it's an override, and it behaves as + # necessary. + scope.setoverride(obj) + + obj + end + + # Create our ResourceDef. Handles type checking for us. + def initialize(hash) + @checked = false + super + + #self.typecheck(@type.value) + end + end +end + +# $Id$ diff --git a/lib/puppet/parser/ast/objectparam.rb b/lib/puppet/parser/ast/resourceparam.rb index 87c3e5e8e..248e91b32 100644 --- a/lib/puppet/parser/ast/objectparam.rb +++ b/lib/puppet/parser/ast/resourceparam.rb @@ -1,6 +1,8 @@ +require 'puppet/parser/ast/branch' + class Puppet::Parser::AST - # The AST object for the parameters inside ObjectDefs and Selectors. - class ObjectParam < AST::Branch + # The AST object for the parameters inside ResourceDefs and Selectors. + class ResourceParam < AST::Branch attr_accessor :value, :param def each @@ -10,9 +12,17 @@ class Puppet::Parser::AST # Return the parameter and the value. def evaluate(hash) scope = hash[:scope] - param = @param.safeevaluate(:scope => scope) + param = @param value = @value.safeevaluate(:scope => scope) - return [param, value] + + args = {:name => param, :value => value, :source => scope.source} + [:line, :file].each do |p| + if v = self.send(p) + args[p] = v + end + end + + return Puppet::Parser::Resource::Param.new(args) end def tree(indent = 0) @@ -27,5 +37,6 @@ class Puppet::Parser::AST return "%s => %s" % [@param,@value] end end - end + +# $Id$ diff --git a/lib/puppet/parser/ast/resourceref.rb b/lib/puppet/parser/ast/resourceref.rb new file mode 100644 index 000000000..b0fe5f6d7 --- /dev/null +++ b/lib/puppet/parser/ast/resourceref.rb @@ -0,0 +1,44 @@ +require 'puppet/parser/ast/branch' + +class Puppet::Parser::AST + # A reference to an object. Only valid as an rvalue. + class ResourceRef < AST::Branch + attr_accessor :title, :type + + def each + [@type,@title].flatten.each { |param| + #Puppet.debug("yielding param %s" % param) + yield param + } + end + + # Evaluate our object, but just return a simple array of the type + # and name. + def evaluate(hash) + scope = hash[:scope] + + # We want a lower-case type. + objtype = @type.downcase + + title = @title.safeevaluate(:scope => scope) + + return Puppet::Parser::Resource::Reference.new( + :type => objtype, :title => title + ) + end + + def tree(indent = 0) + return [ + @type.tree(indent + 1), + @title.tree(indent + 1), + ((@@indline * indent) + self.typewrap(self.pin)) + ].join("\n") + end + + def to_s + return "%s[%s]" % [@type,@title] + end + end +end + +# $Id$ diff --git a/lib/puppet/parser/ast/selector.rb b/lib/puppet/parser/ast/selector.rb index 5d1c41494..fe3fe9eaf 100644 --- a/lib/puppet/parser/ast/selector.rb +++ b/lib/puppet/parser/ast/selector.rb @@ -42,12 +42,8 @@ class Puppet::Parser::AST if default retvalue = default.value.safeevaluate(:scope => scope) else - error = Puppet::ParseError.new( + self.fail Puppet::ParseError, "No value for selector param '%s'" % paramvalue - ) - error.line = self.line - error.file = self.file - raise error end end diff --git a/lib/puppet/parser/ast/vardef.rb b/lib/puppet/parser/ast/vardef.rb index 79129f31a..30eef204a 100644 --- a/lib/puppet/parser/ast/vardef.rb +++ b/lib/puppet/parser/ast/vardef.rb @@ -10,18 +10,8 @@ class Puppet::Parser::AST name = @name.safeevaluate(:scope => scope) value = @value.safeevaluate(:scope => scope) - begin + parsewrap do scope.setvar(name,value) - rescue Puppet::ParseError => except - except.line = self.line - except.file = self.file - raise except - rescue => detail - error = Puppet::ParseError.new(detail) - error.line = self.line - error.file = self.file - error.set_backtrace detail.backtrace - raise error end end @@ -43,3 +33,5 @@ class Puppet::Parser::AST end end + +# $Id$ diff --git a/lib/puppet/parser/collector.rb b/lib/puppet/parser/collector.rb new file mode 100644 index 000000000..f3c7949de --- /dev/null +++ b/lib/puppet/parser/collector.rb @@ -0,0 +1,97 @@ +# An object that collects stored objects from the central cache and returns +# them to the current host, yo. +class Puppet::Parser::Collector + attr_accessor :type, :scope, :query, :form + + # Collect exported objects. + def collect_exported + require 'puppet/rails' + # First get everything from the export table. Just reuse our + # collect_virtual method but tell it to use 'exported? for the test. + resources = collect_virtual(true) + + count = resources.length + + # We're going to collect objects from rails, but we don't want any + # objects from this host. + host = Puppet::Rails::Host.find_by_name(@scope.host) + + args = {} + if host + args[:conditions] = "host_id != #{host.id}" + else + #Puppet.info "Host %s is uninitialized" % @scope.host + end + + # Now look them up in the rails db. When we support attribute comparison + # and such, we'll need to vary the conditions, but this works with no + # attributes, anyway. + Puppet::Util.benchmark(:debug, "Collected #{self.type} resources") do + Puppet::Rails::RailsResource.find_all_by_restype_and_exported(@type, true, + args + ).each do |obj| + count += 1 + resource = obj.to_resource(self.scope) + + # XXX Because the scopes don't expect objects to return values, + # we have to manually add our objects to the scope. This is + # uber-lame. + scope.setresource(resource) + + resources << resource + end + end + + scope.debug("Collected %s objects of type %s" % + [count, @convertedtype]) + + return resources + end + + # Collect just virtual objects, from our local configuration. + def collect_virtual(exported = false) + if exported + method = :exported? + else + method = :virtual? + end + scope.resources.find_all do |resource| + resource.type == @type and resource.send(method) and match?(resource) + end + end + + # Call the collection method, mark all of the returned objects as non-virtual, + # and then delete this object from the list of collections to evaluate. + def evaluate + method = "collect_#{@form.to_s}" + objects = send(method).each do |obj| + obj.virtual = false + end + + # And then remove us from the list of collections, since we've + # now been evaluated. + @scope.collections.delete(self) + + objects + end + + def initialize(scope, type, query, form) + @scope = scope + @type = type + @query = query + @form = form + @tests = [] + end + + # Does the resource match our tests? We don't yet support tests, + # so it's always true at the moment. + def match?(resource) + if self.query + return self.query.call(resource) + else + return true + end + end +end + +# $Id$ diff --git a/lib/puppet/parser/functions.rb b/lib/puppet/parser/functions.rb index 9562441bc..96fccd7cd 100644 --- a/lib/puppet/parser/functions.rb +++ b/lib/puppet/parser/functions.rb @@ -76,29 +76,24 @@ module Functions # Include the specified classes newfunction(:include) do |vals| - vals.each do |val| - if objecttype = lookuptype(val) - # It's a defined type, so set it into the scope so it can - # be evaluated. - setobject( - :type => val, - :arguments => {} - ) - else - raise Puppet::ParseError, "Unknown class %s" % val - end + klasses = evalclasses(*vals) + + missing = vals.find_all do |klass| + ! klass.include?(klass) + end + + # Throw an error if we didn't evaluate all of the classes. + if missing.length == 1 + self.fail Puppet::ParseError, + "Could not find class %s" % missing + elsif missing.length > 1 + self.fail Puppet::ParseError, + "Could not find classes %s" % missing.join(", ") end end # Tag the current scope with each passed name newfunction(:tag) do |vals| - vals.each do |val| - # Some hackery, because the tags are stored by object id - # for singletonness. - self.setclass(val.object_id, val) - end - - # Also add them as tags self.tag(*vals) end @@ -120,16 +115,13 @@ module Functions # Test whether a given class or definition is defined newfunction(:defined, :rvalue) do |vals| - retval = true - - vals.each do |val| - unless builtintype?(val) or lookuptype(val) - retval = false - break - end + # For some reason, it doesn't want me to return from here. + if vals.detect do |val| Puppet::Type.type(val) or finddefine(val) end + true + else + false end - return retval end newfunction(:fail, :statement) do |vals| @@ -151,7 +143,7 @@ module Functions # Use a wrapper, so the template can't get access to the full # Scope object. debug "Retrieving template %s" % file - wrapper = Puppet::Parser::Scope::TemplateWrapper.new(self, file) + wrapper = Puppet::Parser::TemplateWrapper.new(self, file) begin wrapper.result() diff --git a/lib/puppet/parser/grammar.ra b/lib/puppet/parser/grammar.ra index 3a1a78e50..5c91aabdc 100644 --- a/lib/puppet/parser/grammar.ra +++ b/lib/puppet/parser/grammar.ra @@ -5,38 +5,52 @@ class Puppet::Parser::Parser token LBRACK DQTEXT SQTEXT RBRACK LBRACE RBRACE SYMBOL FARROW COMMA TRUE -token FALSE EQUALS LESSEQUAL NOTEQUAL DOT COLON TYPE +token FALSE EQUALS LESSEQUAL NOTEQUAL DOT COLON TYPE LLCOLLECT RRCOLLECT token QMARK LPAREN RPAREN ISEQUAL GREATEREQUAL GREATERTHAN LESSTHAN token IF ELSE IMPORT DEFINE ELSIF VARIABLE CLASS INHERITS NODE BOOLEAN -token NAME SEMIC CASE DEFAULT AT LCOLLECT RCOLLECT +token NAME SEMIC CASE DEFAULT AT LCOLLECT RCOLLECT CLASSNAME CLASSREF +token NOT OR AND # We have 2 shift/reduce conflicts #expect 2 rule program: statements { - # Make sure we always return an array. - if val[0].is_a?(AST::ASTArray) - result = val[0] + if val[0] + # Make sure we always return an array. + if val[0].is_a?(AST::ASTArray) + if val[0].children.empty? + result = nil + else + result = val[0] + end + else + result = aryfy(val[0]) + end else - result = aryfy(val[0]) + result = nil end } - | nothing + | nil -statements: statement +statements: statement | statements statement { - if val[0].instance_of?(AST::ASTArray) - val[0].push(val[1]) - result = val[0] - else - result = ast AST::ASTArray, :children => [val[0],val[1]] + if val[0] and val[1] + if val[0].instance_of?(AST::ASTArray) + val[0].push(val[1]) + result = val[0] + else + result = ast AST::ASTArray, :children => [val[0],val[1]] + end + elsif obj = (val[0] || val[1]) + result = obj + else result = nil end } # The main list of valid statements -statement: object - | collectable +statement: resource + | virtualresource | collection | assignment | casestatement @@ -46,15 +60,16 @@ statement: object | definition | hostclass | nodedef + | resourceoverride -fstatement: NAME LPAREN classnames RPAREN { +fstatement: NAME LPAREN namestrings RPAREN { args = aryfy(val[2]) result = ast AST::Function, :name => val[0], :arguments => args, :ftype => :statement } - | NAME classnames { + | NAME namestrings { args = aryfy(val[1]) result = ast AST::Function, :name => val[0], @@ -62,108 +77,163 @@ fstatement: NAME LPAREN classnames RPAREN { :ftype => :statement } -# Includes are just syntactic sugar for classes with no names and -# no arguments. -#include: INCLUDE classnames { -# result = function_include(val[1]) -#} - -# Define a new tag. Both of these functions should really be done generically, -# but I'm not in a position to do that just yet. :/ -#tag: TAG classnames { -# result = function_tag(val[1]) -#} - -classnames: classname - | classnames COMMA classname { +namestrings: namestring + | namestrings COMMA namestring { result = aryfy(val[0], val[2]) result.line = @lexer.line result.file = @lexer.file } -classname: name +namestring: name | variable | quotedtext -#object: name LBRACE objectname COLON params endcomma RBRACE { -object: name LBRACE objectinstances endsemi RBRACE { +resource: NAME LBRACE resourceinstances endsemi RBRACE { if val[0].instance_of?(AST::ASTArray) - raise Puppet::ParseError, "Invalid name" + error "Invalid name" end array = val[2] - if array.instance_of?(AST::ObjectInst) + if array.instance_of?(AST::ResourceInst) array = [array] end result = ast AST::ASTArray - # this iterates across each specified objectinstance + # this iterates across each specified resourceinstance array.each { |instance| - unless instance.instance_of?(AST::ObjectInst) + unless instance.instance_of?(AST::ResourceInst) raise Puppet::Dev, "Got something that isn't an instance" end # now, i need to somehow differentiate between those things with # arrays in their names, and normal things - result.push ast(AST::ObjectDef, + result.push ast(AST::ResourceDef, :type => val[0], - :name => instance[0], + :title => instance[0], :params => instance[1]) } -} | name LBRACE params endcomma RBRACE { - if val[0].instance_of?(AST::ASTArray) - Puppet.notice "invalid name" - raise Puppet::ParseError, "Invalid name" - end - # an object but without a name - # this cannot be an instance of a library type - result = ast AST::ObjectDef, :type => val[0], :params => val[2] - -} | type LBRACE params endcomma RBRACE { +} | NAME LBRACE params endcomma RBRACE { + # This is a deprecated syntax. + error "All resource specifications require names" +} | TYPE LBRACE params endcomma RBRACE { # a template setting for a type if val[0].instance_of?(AST::ASTArray) - raise Puppet::ParseError, "Invalid type" + error "Invalid type" end - result = ast(AST::TypeDefaults, :type => val[0], :params => val[2]) + result = ast(AST::ResourceDefaults, :type => val[0], :params => val[2]) +} + +# Override a value set elsewhere in the configuration. +resourceoverride: resourceref LBRACE params RBRACE { + result = ast AST::ResourceOverride, :object => val[0], :params => val[2] } -# Collectable objects; these get stored in the database, instead of -# being passed to the client. -collectable: AT object { - unless Puppet[:storeconfigs] - raise Puppet::ParseError, "You cannot collect without storeconfigs being set" +# Exported and virtual resources; these don't get sent to the client +# unless they get collected elsewhere in the db. +virtualresource: at resource { + type = val[0] + + if type == :exported and ! Puppet[:storeconfigs] + error "You cannot collect without storeconfigs being set" end - if val[1].is_a? AST::TypeDefaults - raise Puppet::ParseError, "Defaults are not collectable" + if val[1].is_a? AST::ResourceDefaults + error "Defaults are not virtualizable" end - # Just mark our objects as collectable and pass them through. + method = type.to_s + "=" + + # Just mark our resources as exported and pass them through. if val[1].instance_of?(AST::ASTArray) val[1].each do |obj| - obj.collectable = true + obj.send(method, true) end else - val[1].collectable = true + val[1].send(method, true) end result = val[1] } +at: AT { result = :virtual } + | AT AT { result = :exported } + # A collection statement. Currently supports no arguments at all, but eventually # will, I assume. -collection: name LCOLLECT RCOLLECT { - unless Puppet[:storeconfigs] - raise Puppet::ParseError, "You cannot collect without storeconfigs being set" +collection: collectname collectrhand { + if val[0] =~ /^[a-z]/ + Puppet.warning addcontext("Collection names must now be capitalized") + end + type = val[0].downcase + args = {:type => type} + + if val[1].is_a?(AST::CollExpr) + args[:query] = val[1] + args[:query].type = type + args[:form] = args[:query].form + else + args[:form] = val[1] + end + if args[:form] == :exported and ! Puppet[:storeconfigs] + error "You cannot collect exported resources without storeconfigs being set" end - result = ast AST::Collection, :type => val[0] + result = ast AST::Collection, args } -objectinst: objectname COLON params endcomma { - result = ast AST::ObjectInst, :children => [val[0],val[2]] +collectname: TYPE | NAME + +collectrhand: LCOLLECT collstatements RCOLLECT { + if val[1] + result = val[1] + result.form = :virtual + else + result = :virtual + end } + | LLCOLLECT collstatements RRCOLLECT { + if val[1] + result = val[1] + result.form = :exported + else + result = :exported + end +} + +# A mini-language for handling collection comparisons. This is organized +# to avoid the need for precedence indications. +collstatements: nil + | collstatement + | collstatements colljoin collstatement { + result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] +} + +collstatement: collexpr + | LPAREN collstatements RPAREN { + result = val[1] + result.parens = true +} + +colljoin: AND | OR -objectinstances: objectinst - | objectinstances SEMIC objectinst { - if val[0].instance_of?(AST::ObjectInst) +collexpr: colllval ISEQUAL simplervalue { + result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] + #result = ast AST::CollExpr + #result.push *val +} + | colllval NOTEQUAL simplervalue { + result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] + #result = ast AST::CollExpr + #result.push *val +} + +colllval: variable + | name + +resourceinst: resourcename COLON params endcomma { + result = ast AST::ResourceInst, :children => [val[0],val[2]] +} + +resourceinstances: resourceinst + | resourceinstances SEMIC resourceinst { + if val[0].instance_of?(AST::ResourceInst) result = ast AST::ASTArray, :children => [val[0],val[2]] else val[0].push val[2] @@ -182,7 +252,7 @@ type: TYPE { result = ast AST::Type, :value => val[0] } -objectname: quotedtext +resourcename: quotedtext | name | type | selector @@ -191,7 +261,7 @@ objectname: quotedtext assignment: VARIABLE EQUALS rvalue { # this is distinct from referencing a variable - variable = ast AST::Name, :value => val[0].sub(/^\$/,'') + variable = ast AST::Name, :value => val[0] result = ast AST::VarDef, :name => variable, :value => val[2] } @@ -210,8 +280,7 @@ params: # nothing } param: NAME FARROW rvalue { - leaf = ast AST::String, :value => val[0] - result = ast AST::ObjectParam, :param => leaf, :value => val[2] + result = ast AST::ResourceParam, :param => val[0], :value => val[2] } rvalues: rvalue @@ -223,6 +292,13 @@ rvalues: rvalue end } +simplervalue: quotedtext + | name + | type + | boolean + | selector + | variable + rvalue: quotedtext | name | type @@ -230,11 +306,11 @@ rvalue: quotedtext | selector | variable | array - | objectref + | resourceref | funcrvalue # We currently require arguments in these functions. -funcrvalue: NAME LPAREN classnames RPAREN { +funcrvalue: NAME LPAREN namestrings RPAREN { args = aryfy(val[2]) result = ast AST::Function, :name => val[0], @@ -252,8 +328,11 @@ boolean: BOOLEAN { result = ast AST::Boolean, :value => val[0] } -objectref: name LBRACK rvalue RBRACK { - result = ast AST::ObjectRef, :type => val[0], :name => val[2] +resourceref: NAME LBRACK rvalue RBRACK { + Puppet.warning addcontext("Deprecation notice: Resource references should now be capitalized") + result = ast AST::ResourceRef, :type => val[0], :title => val[2] +} | TYPE LBRACK rvalue RBRACK { + result = ast AST::ResourceRef, :type => val[0], :title => val[2] } ifstatement: IF iftest LBRACE statements RBRACE else { @@ -333,7 +412,7 @@ sintvalues: selectval } selectval: selectlhand FARROW rvalue { - result = ast AST::ObjectParam, :param => val[0], :value => val[2] + result = ast AST::ResourceParam, :param => val[0], :value => val[2] } selectlhand: name @@ -378,7 +457,7 @@ import: IMPORT quotedtext { end files.each { |file| - parser = Puppet::Parser::Parser.new() + parser = Puppet::Parser::Parser.new(interp) parser.files = self.files Puppet.debug("importing '%s'" % file) @@ -396,8 +475,13 @@ import: IMPORT quotedtext { end # push the results into the main result array # We always return an array when we parse. - parser.parse.each do |child| - result.push child + ast = parser.parse + + # Things that just get added to the classtable or whatever return nil + if ast + ast.each do |child| + result.push child + end end } } @@ -405,165 +489,99 @@ import: IMPORT quotedtext { # Disable definition inheritance for now. 8/27/06, luke #definition: DEFINE NAME argumentlist parent LBRACE statements RBRACE { -definition: DEFINE NAME argumentlist LBRACE statements RBRACE { - args = { - :type => ast(AST::Name, :value => val[1]), - :args => val[2], - :code => val[4] # Switch to 5 for parents - } +definition: DEFINE fqname argumentlist LBRACE statements RBRACE { + interp.newdefine fqname(val[1]), :arguments => val[2], :code => val[4] + @lexer.indefine = false + result = nil - if val[3].instance_of?(AST::Name) - args[:parentclass] = val[3] - end - result = ast AST::CompDef, args #} | DEFINE NAME argumentlist parent LBRACE RBRACE { -} | DEFINE NAME argumentlist LBRACE RBRACE { - args = { - :type => ast(AST::Name, :value => val[1]), - :args => val[2], - :code => ast(AST::ASTArray) - } - - if val[3].instance_of?(AST::Name) - args[:parentclass] = val[3] - end - - result = ast AST::CompDef, args +} | DEFINE fqname argumentlist LBRACE RBRACE { + interp.newdefine fqname(val[1]), :arguments => val[2] + @lexer.indefine = false + result = nil } #hostclass: CLASS NAME argumentlist parent LBRACE statements RBRACE { -hostclass: CLASS NAME parent LBRACE statements RBRACE { - #:args => val[2], - args = { - :type => ast(AST::Name, :value => val[1]), - :code => val[4] - } - # It'll be an ASTArray if we didn't get a parent - if val[2].instance_of?(AST::Name) - args[:parentclass] = val[2] - end - result = ast AST::ClassDef, args -} | CLASS NAME parent LBRACE RBRACE { - args = { - :type => ast(AST::Name, :value => val[1]), - :code => ast(AST::ASTArray, :children => []) - } - # It'll be an ASTArray if we didn't get a parent - if val[2].instance_of?(AST::Name) - args[:parentclass] = val[2] - end - result = ast AST::ClassDef, args +hostclass: CLASS fqname parent LBRACE statements RBRACE { + # Our class gets defined in the parent namespace, not our own. + @lexer.namepop + interp.newclass fqname(val[1]), :code => val[4], :parent => val[2] + result = nil +} | CLASS fqname parent LBRACE RBRACE { + # Our class gets defined in the parent namespace, not our own. + @lexer.namepop + interp.newclass fqname(val[1]), :parent => val[2] + result = nil } nodedef: NODE hostnames parent LBRACE statements RBRACE { - unless val[1].instance_of?(AST::ASTArray) - val[1] = ast AST::ASTArray, - :line => val[1].line, - :file => val[1].file, - :children => [val[1]] - end - args = { - :names => val[1], - :code => val[4] - } - if val[2].instance_of?(AST::Name) - args[:parentclass] = val[2] - end - result = ast AST::NodeDef, args + interp.newnode val[1], :parent => val[2], :code => val[4] + result = nil } | NODE hostnames parent LBRACE RBRACE { - unless val[1].instance_of?(AST::ASTArray) - val[1] = ast AST::ASTArray, - :line => val[1].line, - :file => val[1].file, - :children => [val[1]] - end - args = { - :names => val[1], - :code => ast(AST::ASTArray, :children => []) - } - if val[2].instance_of?(AST::Name) - args[:parentclass] = val[2] - end - result = ast AST::NodeDef,args + interp.newnode val[1], :parent => val[2] + result = nil } -# Multiple hostnames, as used for node names. +fqname: NAME + | CLASSNAME + +# Multiple hostnames, as used for node names. These are all literal +# strings, not AST objects. hostnames: hostname | hostnames COMMA hostname { - if val[0].instance_of?(AST::ASTArray) - result = val[0] - result.push val[2] - else - result = ast AST::ASTArray, :children => [val[0], val[2]] - end + result = val[0] + result = [result] unless result.is_a?(Array) + result << val[2] } -hostname: NAME { - result = ast AST::HostName, :value => val[0] -} | SQTEXT { - result = ast AST::HostName, :value => val[0] -} | DEFAULT { - result = ast AST::Default, :value => val[0] +hostname: NAME + | SQTEXT + | DEFAULT + +nil: { + result = nil } nothing: { result = ast AST::ASTArray, :children => [] } -argumentlist: nothing +argumentlist: nil | LPAREN nothing RPAREN { - result = val[1] + result = nil } | LPAREN arguments RPAREN { - if val[1].instance_of?(AST::ASTArray) - result = val[1] - else - result = ast AST::ASTArray, :children => [val[1]] - end + result = val[1] + result = [result] unless result[0].is_a?(Array) } arguments: argument | arguments COMMA argument { - if val[0].instance_of?(AST::ASTArray) - val[0].push(val[2]) - result = val[0] - else - result = ast AST::ASTArray, :children => [val[0],val[2]] - end + result = val[0] + result = [result] unless result[0].is_a?(Array) + result << val[2] } -argument: name EQUALS rvalue { - msg = "Deprecation notice: #{val[0].value} must now include '$' in prototype" - msg += " at line %s" % @lexer.line - msg += " in file %s" % @lexer.file if @lexer.file - Puppet.warning msg - result = ast AST::CompArgument, :children => [val[0],val[2]] +argument: NAME EQUALS rvalue { + Puppet.warning addcontext("Deprecation notice: #{val[0].value} must now include '$' in prototype") + result = [val[0], val[2]] } - | name { - msg = "Deprecation notice: #{val[0].value} must now include '$' in prototype" - msg += " at line %s" % @lexer.line - msg += " in file %s" % @lexer.file if @lexer.file - Puppet.warning msg - result = ast AST::CompArgument, :children => [val[0]] -} | lvariable EQUALS rvalue { - result = ast AST::CompArgument, :children => [val[0],val[2]] -} | lvariable { - result = ast AST::CompArgument, :children => [val[0]] + | NAME { + Puppet.warning addcontext("Deprecation notice: #{val[0].value} must now include '$' in prototype") + result = [val[0]] +} | VARIABLE EQUALS rvalue { + result = [val[0], val[2]] +} | VARIABLE { + result = [val[0]] } -parent: nothing +parent: nil | INHERITS NAME { - result = ast AST::Name, :value => val[1] + result = val[1] } variable: VARIABLE { - name = val[0].sub(/^\$/,'') - result = ast AST::Variable, :value => name -} - -# This is variables as lvalues; we're assigning them, not deferencing them. -lvariable: VARIABLE { - result = ast AST::Name, :value => val[0].sub(/^\$/,'') + result = ast AST::Variable, :value => val[0] } array: LBRACK rvalues RBRACK { @@ -602,9 +620,21 @@ Puppet[:paramcheck] = true ---- inner ---- require 'puppet/parser/functions' -attr_reader :file +attr_reader :file, :interp attr_accessor :files +# Add context to a message; useful for error messages and such. +def addcontext(message, obj = nil) + obj ||= @lexer + + message += " on line %s" % obj.line + if file = obj.file + message += " in file %s" % file + end + + return message +end + # Create an AST array out of all of the args def aryfy(*args) if args[0].instance_of?(AST::ASTArray) @@ -636,6 +666,17 @@ def ast(klass, hash = nil) return klass.new(hash) end +# Raise a Parse error. +def error(message) + except = Puppet::ParseError.new(message) + except.line = @lexer.line + if @lexer.file + except.file = @lexer.file + end + + raise except +end + def file=(file) unless FileTest.exists?(file) unless file =~ /\.pp$/ @@ -653,23 +694,20 @@ def file=(file) end end -def initialize +def initialize(interpreter) + @interp = interpreter + initvars() +end + +# Initialize or reset all of our variables. +def initvars @lexer = Puppet::Parser::Lexer.new() @files = [] - #if Puppet[:debug] - # @yydebug = true - #end end -# Add a new file to be checked when we're checking to see if we should be -# reparsed. -def newfile(*files) - files.each do |file| - unless file.is_a? Puppet::LoadedFile - file = Puppet::LoadedFile.new(file) - end - @files << file - end +# The fully qualifed name, with the full namespace. +def fqname(name) + [@lexer.namespace, name].join("::").sub(/^::/, '') end def on_error(token,value,stack) @@ -693,7 +731,7 @@ def parse(string = nil) self.string = string end begin - yyparse(@lexer,:scan) + main = yyparse(@lexer,:scan) rescue Racc::ParseError => except error = Puppet::ParseError.new(except) error.line = @lexer.line @@ -720,6 +758,13 @@ def parse(string = nil) error.set_backtrace except.backtrace raise error end + if main + # Store the results as the top-level class. + interp.newclass("", :code => main) + return main + end +ensure + @lexer.clear end # See if any of the files have changed. diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index 26bf8104e..111fe2ed3 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -3,463 +3,774 @@ # and calls out to other objects. require 'puppet' +require 'timeout' require 'puppet/parser/parser' require 'puppet/parser/scope' +class Puppet::Parser::Interpreter + include Puppet::Util + + Puppet.setdefaults("ldap", + :ldapnodes => [false, + "Whether to search for node configurations in LDAP."], + :ldapssl => [false, + "Whether SSL should be used when searching for nodes. + Defaults to false because SSL usually requires certificates + to be set up on the client side."], + :ldaptls => [false, + "Whether TLS should be used when searching for nodes. + Defaults to false because TLS usually requires certificates + to be set up on the client side."], + :ldapserver => ["ldap", + "The LDAP server. Only used if ``ldapnodes`` is enabled."], + :ldapport => [389, + "The LDAP port. Only used if ``ldapnodes`` is enabled."], + :ldapstring => ["(&(objectclass=puppetClient)(cn=%s))", + "The search string used to find an LDAP node."], + :ldapattrs => ["puppetclass", + "The LDAP attributes to use to define Puppet classes. Values + should be comma-separated."], + :ldapparentattr => ["parentnode", + "The attribute to use to define the parent node."], + :ldapuser => ["", + "The user to use to connect to LDAP. Must be specified as a + full DN."], + :ldappassword => ["", + "The password to use to connect to LDAP."], + :ldapbase => ["", + "The search base for LDAP searches. It's impossible to provide + a meaningful default here, although the LDAP libraries might + have one already set. Generally, it should be the 'ou=Hosts' + branch under your main directory."] + ) + + Puppet.setdefaults(:puppetmaster, + :storeconfigs => [false, + "Whether to store each client's configuration. This + requires ActiveRecord from Ruby on Rails."] + ) + + attr_accessor :usenodes + + class << self + attr_writer :ldap + end + # just shorten the constant path a bit, using what amounts to an alias + AST = Puppet::Parser::AST + + include Puppet::Util::Errors + + # Create an ldap connection. This is a class method so others can call + # it and use the same variables and such. + def self.ldap + unless defined? @ldap and @ldap + if Puppet[:ldapssl] + @ldap = LDAP::SSLConn.new(Puppet[:ldapserver], Puppet[:ldapport]) + elsif Puppet[:ldaptls] + @ldap = LDAP::SSLConn.new( + Puppet[:ldapserver], Puppet[:ldapport], true + ) + else + @ldap = LDAP::Conn.new(Puppet[:ldapserver], Puppet[:ldapport]) + end + @ldap.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) + @ldap.set_option(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_ON) + @ldap.simple_bind(Puppet[:ldapuser], Puppet[:ldappassword]) + end -module Puppet - module Parser - class Interpreter - include Puppet::Util - - Puppet.setdefaults("ldap", - :ldapnodes => [false, - "Whether to search for node configurations in LDAP."], - :ldapssl => [false, - "Whether SSL should be used when searching for nodes. - Defaults to false because SSL usually requires certificates - to be set up on the client side."], - :ldaptls => [false, - "Whether TLS should be used when searching for nodes. - Defaults to false because TLS usually requires certificates - to be set up on the client side."], - :ldapserver => ["ldap", - "The LDAP server. Only used if ``ldapnodes`` is enabled."], - :ldapport => [389, - "The LDAP port. Only used if ``ldapnodes`` is enabled."], - :ldapstring => ["(&(objectclass=puppetClient)(cn=%s))", - "The search string used to find an LDAP node."], - :ldapattrs => ["puppetclass", - "The LDAP attributes to use to define Puppet classes. Values - should be comma-separated."], - :ldapparentattr => ["parentnode", - "The attribute to use to define the parent node."], - :ldapuser => ["", - "The user to use to connect to LDAP. Must be specified as a - full DN."], - :ldappassword => ["", - "The password to use to connect to LDAP."], - :ldapbase => ["", - "The search base for LDAP searches. It's impossible to provide - a meaningful default here, although the LDAP libraries might - have one already set. Generally, it should be the 'ou=Hosts' - branch under your main directory."] - ) - - Puppet.setdefaults(:puppetmaster, - :storeconfigs => [false, - "Whether to store each client's configuration. This - requires ActiveRecord from Ruby on Rails."] - ) + return @ldap + end - attr_accessor :ast + def clear + initparsevars + end - class << self - attr_writer :ldap - end - # just shorten the constant path a bit, using what amounts to an alias - AST = Puppet::Parser::AST - - # Create an ldap connection. This is a class method so others can call - # it and use the same variables and such. - def self.ldap - unless defined? @ldap and @ldap - if Puppet[:ldapssl] - @ldap = LDAP::SSLConn.new(Puppet[:ldapserver], Puppet[:ldapport]) - elsif Puppet[:ldaptls] - @ldap = LDAP::SSLConn.new( - Puppet[:ldapserver], Puppet[:ldapport], true - ) - else - @ldap = LDAP::Conn.new(Puppet[:ldapserver], Puppet[:ldapport]) + # Iteratively evaluate all of the objects. This finds all fo the + # objects that represent definitions and evaluates the definitions appropriately. + # It also adds defaults and overrides as appropriate. + def evaliterate(scope) + count = 0 + begin + timeout 300 do + while ary = scope.unevaluated + ary.each do |resource| + resource.evaluate end - @ldap.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) - @ldap.set_option(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_ON) - @ldap.simple_bind(Puppet[:ldapuser], Puppet[:ldappassword]) end - - return @ldap end + rescue Timeout::Error + raise Puppet::DevError, "Got a timeout trying to evaluate all definitions" + end + end - # create our interpreter - def initialize(hash) - if @code = hash[:Code] - @file = nil # to avoid warnings - elsif ! @file = hash[:Manifest] - raise Puppet::DevError, "You must provide code or a manifest" - end + # Evaluate a specific node. + def evalnode(client, scope, facts) + return unless self.usenodes - if hash.include?(:UseNodes) - @usenodes = hash[:UseNodes] - else - @usenodes = true - end + unless client + raise Puppet::Error, + "Cannot evaluate nodes with a nil client" + end + names = [client] + + # Make sure both the fqdn and the short name of the + # host can be used in the manifest + if client =~ /\./ + names << client.sub(/\..+/,'') + else + names << "#{client}.#{facts['domain']}" + end - # By default, we only search the parse tree. - @nodesources = [] + if names.empty? + raise Puppet::Error, + "Cannot evaluate nodes with a nil client" + end - if Puppet[:ldapnodes] - @nodesources << :ldap - end + # Look up our node object. + if nodeclass = nodesearch(*names) + nodeclass.safeevaluate :scope => scope + else + raise Puppet::Error, "Could not find %s with names %s" % + [client, names.join(", ")] + end + end - if hash[:NodeSources] - hash[:NodeSources].each do |src| - if respond_to? "nodesearch_#{src.to_s}" - @nodesources << src.to_s.intern - else - Puppet.warning "Node source '#{src}' not supported" - end - end - end + # Evaluate all of the code we can find that's related to our client. + def evaluate(client, facts) - @setup = false + scope = Puppet::Parser::Scope.new(:interp => self) # no parent scope + scope.name = "top" + scope.type = "main" - # Set it to either the value or nil. This is currently only used - # by the cfengine module. - @classes = hash[:Classes] || [] + scope.host = facts["hostname"] || Facter.value("hostname") - @local = hash[:Local] || false + classes = @classes.dup - if hash.include?(:ForkSave) - @forksave = hash[:ForkSave] - else - # This is just too dangerous right now. Sorry, it's going - # to have to be slow. - @forksave = false - end + # Okay, first things first. Set our facts. + scope.setfacts(facts) - if Puppet[:storeconfigs] - Puppet::Rails.init - end + # Everyone will always evaluate the top-level class, if there is one. + if klass = findclass("", "") + # Set the source, so objects can tell where they were defined. + scope.source = klass + klass.safeevaluate :scope => scope, :nosubscope => true + end - @files = [] + # Next evaluate the node + evalnode(client, scope, facts) - # Create our parser object - parsefiles + # If we were passed any classes, evaluate those. + if classes + classes.each do |klass| + if klassobj = findclass("", klass) + klassobj.safeevaluate :scope => scope + end end + end - # Search for our node in the various locations. This only searches - # locations external to the files; the scope is responsible for - # searching the parse tree. - def nodesearch(*nodes) - # At this point, stop at the first source that defines - # the node - @nodesources.each do |source| - method = "nodesearch_%s" % source - parent = nil - nodeclasses = nil - if self.respond_to? method - nodes.each do |node| - parent, nodeclasses = self.send(method, node) - - if parent or (nodeclasses and !nodeclasses.empty?) - Puppet.info "Found %s in %s" % [node, source] - return parent, nodeclasses - else - # Look for a default node. - parent, nodeclasses = self.send(method, "default") - if parent or (nodeclasses and !nodeclasses.empty?) - Puppet.info "Found default node for %s in %s" % - [node, source] - return parent, nodeclasses - end - end - end - end - end + # That was the first pass evaluation. Now iteratively evaluate + # until we've gotten rid of all of everything or thrown an error. + evaliterate(scope) + + # Now make sure we fail if there's anything left to do + failonleftovers(scope) - return nil, nil + # Now perform the collections + scope.collections.each do |coll| + coll.evaluate + end + + # Now finish everything. This recursively calls finish on the + # contained scopes and resources. + scope.finish + + # Store everything. We need to do this before translation, because + # it operates on resources, not transobjects. + if Puppet[:storeconfigs] + args = { + :resources => scope.resources, + :name => client, + :facts => facts + } + unless scope.classlist.empty? + args[:classes] = scope.classlist end - # Find the ldap node and extra the info, returning just - # the critical data. - def nodesearch_ldap(node) - unless defined? @ldap and @ldap - setup_ldap() - unless @ldap - Puppet.info "Skipping ldap source; no ldap connection" - return nil, [] - end - end + storeconfigs(args) + end - if node =~ /\./ - node = node.sub(/\..+/, '') - end + # Now, finally, convert our scope tree + resources into a tree of + # buckets and objects. + objects = scope.translate - filter = Puppet[:ldapstring] - attrs = Puppet[:ldapattrs].split("\s*,\s*") - sattrs = attrs.dup - pattr = nil - if pattr = Puppet[:ldapparentattr] - if pattr == "" - pattr = nil - else - sattrs << pattr - end - end + # Add the class list + unless scope.classlist.empty? + objects.classes = scope.classlist + end - if filter =~ /%s/ - filter = filter.gsub(/%s/, node) - end + return objects + end - parent = nil - classes = [] - - found = false - count = 0 - begin - # We're always doing a sub here; oh well. - @ldap.search(Puppet[:ldapbase], 2, filter, sattrs) do |entry| - found = true - if pattr - if values = entry.vals(pattr) - if values.length > 1 - raise Puppet::Error, - "Node %s has more than one parent: %s" % - [node, values.inspect] - end - unless values.empty? - parent = values.shift - end - end - end + # Fail if there any overrides left to perform. + def failonleftovers(scope) + overrides = scope.overrides + if overrides.empty? + return nil + else + fail Puppet::ParseError, + "Could not find object(s) %s" % overrides.collect { |o| + o.ref + }.join(", ") + end + end - attrs.each { |attr| - if values = entry.vals(attr) - values.each do |v| classes << v end - end - } - end - rescue => detail - if count == 0 - # Try reconnecting to ldap - @ldap = nil - setup_ldap() - retry - else - raise Puppet::Error, "LDAP Search failed: %s" % detail - end - end + # Find a class definition, relative to the current namespace. + def findclass(namespace, name) + fqfind namespace, name, @classtable + end - classes.flatten! + # Find a component definition, relative to the current namespace. + def finddefine(namespace, name) + fqfind namespace, name, @definetable + end - return parent, classes - end + # The recursive method used to actually look these objects up. + def fqfind(namespace, name, table) + if name =~ /^::/ or namespace == "" + return table[name.sub(/^::/, '')] + end + ary = namespace.split("::") - def parsedate - parsefiles() - @parsedate + while ary.length > 0 + newname = (ary + [name]).join("::").sub(/^::/, '') + if obj = table[newname] + return obj end - # Add a new file to check for updateness. - def newfile(file) - unless @files.find { |f| f.file == file } - @files << Puppet::LoadedFile.new(file) - end - end + # Delete the second to last object, which reduces our namespace by one. + ary.pop + end - # evaluate our whole tree - def run(client, facts) - # We have to leave this for after initialization because there - # seems to be a problem keeping ldap open after a fork. - unless @setup - @nodesources.each { |source| - method = "setup_%s" % source.to_s - if respond_to? method - begin - self.send(method) - rescue => detail - raise Puppet::Error, - "Could not set up node source %s" % source - end - end - } - end - parsefiles() + # If we've gotten to this point without finding it, see if the name + # exists at the top namespace + if obj = table[name] + return obj + end + + return nil + end + + # Create a new node, just from a list of names, classes, and an optional parent. + def gennode(name, hash) + facts = hash[:facts] + classes = hash[:classes] + parent = hash[:parentnode] + arghash = { + :name => name, + :code => AST::ASTArray.new(:pin => "[]"), + :interp => self, + :fqname => name + } + classes = [classes] unless classes.is_a?(Array) + + classcode = @parser.ast(AST::ASTArray, :children => classes.collect do |klass| + @parser.ast(AST::FlatString, :value => klass) + end) + + # Now generate a function call. + code = @parser.ast(AST::Function, + :name => "include", + :arguments => classcode, + :ftype => :statement + ) + + if parent + arghash[:parentclass] = parent + end + + # Create the node + return @parser.ast(AST::Node, arghash) + end + + # create our interpreter + def initialize(hash) + if @code = hash[:Code] + @file = nil # to avoid warnings + elsif ! @file = hash[:Manifest] + devfail "You must provide code or a manifest" + end + + if hash.include?(:UseNodes) + @usenodes = hash[:UseNodes] + else + @usenodes = true + end + + # By default, we only search for parsed nodes. + @nodesources = [:code] - # Really, we should stick multiple names in here - # but for now just make a simple array - names = [client] + if Puppet[:ldapnodes] + # Nodes in the file override nodes in ldap. + @nodesources << :ldap + end - # Make sure both the fqdn and the short name of the - # host can be used in the manifest - if client =~ /\./ - names << client.sub(/\..+/,'') + if hash[:NodeSources] + unless hash[:NodeSources].is_a?(Array) + hash[:NodeSources] = [hash[:NodeSources]] + end + hash[:NodeSources].each do |src| + if respond_to? "nodesearch_#{src.to_s}" + @nodesources << src.to_s.intern else - names << "#{client}.#{facts['domain']}" + Puppet.warning "Node source '#{src}' not supported" end + end + end - scope = Puppet::Parser::Scope.new() # no parent scope - scope.name = "top" - scope.type = "puppet" - scope.interp = self + @setup = false - classes = @classes.dup + initparsevars() - args = {:ast => @ast, :facts => facts, :classes => classes} + # Set it to either the value or nil. This is currently only used + # by the cfengine module. + @classes = hash[:Classes] || [] - if @usenodes - unless client - raise Puppet::Error, - "Cannot evaluate nodes with a nil client" - end + @local = hash[:Local] || false - args[:names] = names + if hash.include?(:ForkSave) + @forksave = hash[:ForkSave] + else + # This is just too dangerous right now. Sorry, it's going + # to have to be slow. + @forksave = false + end + + # The class won't always be defined during testing. + if Puppet[:storeconfigs] and defined? ActiveRecord::Base + Puppet::Rails.init + end + + @files = [] + + # Create our parser object + parsefiles + end + + # Initialize or reset the variables related to parsing. + def initparsevars + @classtable = {} + @namespace = "main" + + @nodetable = {} + + @definetable = {} + end + + # Find the ldap node and extra the info, returning just + # the critical data. + def ldapsearch(node) + unless defined? @ldap and @ldap + setup_ldap() + unless @ldap + Puppet.info "Skipping ldap source; no ldap connection" + return nil, [] + end + end - parent, nodeclasses = nodesearch(*names) + if node =~ /\./ + node = node.sub(/\..+/, '') + end - args[:classes] += nodeclasses if nodeclasses + filter = Puppet[:ldapstring] + attrs = Puppet[:ldapattrs].split("\s*,\s*") + sattrs = attrs.dup + pattr = nil + if pattr = Puppet[:ldapparentattr] + if pattr == "" + pattr = nil + else + sattrs << pattr + end + end - args[:parentnode] = parent if parent + if filter =~ /%s/ + filter = filter.gsub(/%s/, node) + end - if nodeclasses or parent - args[:searched] = true + parent = nil + classes = [] + + found = false + count = 0 + begin + # We're always doing a sub here; oh well. + @ldap.search(Puppet[:ldapbase], 2, filter, sattrs) do |entry| + found = true + if pattr + if values = entry.vals(pattr) + if values.length > 1 + raise Puppet::Error, + "Node %s has more than one parent: %s" % + [node, values.inspect] + end + unless values.empty? + parent = values.shift + end end end - begin - objects = scope.evaluate(args) - rescue Puppet::DevError, Puppet::Error, Puppet::ParseError => except - raise - rescue => except - error = Puppet::DevError.new("%s: %s" % - [except.class, except.message]) - error.set_backtrace except.backtrace - raise error - end + attrs.each { |attr| + if values = entry.vals(attr) + values.each do |v| classes << v end + end + } + end + rescue => detail + if count == 0 + # Try reconnecting to ldap + @ldap = nil + setup_ldap() + retry + else + raise Puppet::Error, "LDAP Search failed: %s" % detail + end + end - if Puppet[:storeconfigs] - storeconfigs( - :objects => objects, - :host => client, - :facts => facts - ) - end + classes.flatten! + + return parent, classes + end + + # Split an fq name into a namespace and name + def namesplit(fullname) + ary = fullname.split("::") + n = ary.pop || "" + ns = ary.join("::") + return ns, n + end - return objects + # Create a new class, or merge with an existing class. + def newclass(fqname, options = {}) + if @definetable.include?(fqname) + raise Puppet::ParseError, "Cannot redefine class %s as a definition" % + fqname + end + code = options[:code] + parent = options[:parent] + + # If the class is already defined, then add code to it. + if other = @classtable[fqname] + # Make sure the parents match + if parent and other.parentclass and (parent != other.parentclass) + @parser.error @parser.addcontext("Class %s is already defined" % fqname) + + " with parent %s" % [fqname, other.parentclass] end - # Connect to the LDAP Server - def setup_ldap - self.class.ldap = nil - begin - require 'ldap' - rescue LoadError - Puppet.notice( - "Could not set up LDAP Connection: Missing ruby/ldap libraries" - ) - @ldap = nil - return + # This might be dangerous... + if parent and ! other.parentclass + other.parentclass = parent + end + + # This might just be an empty, stub class. + if code + tmp = fqname + if tmp == "" + tmp = "main" end - begin - @ldap = self.class.ldap() - rescue => detail - raise Puppet::Error, "Could not connect to LDAP: %s" % detail + + Puppet.debug @parser.addcontext("Adding code to %s" % tmp) + # Else, add our code to it. + if other.code and code + other.code.children += code.children + else + other.code ||= code end end + else + # Define it anew. + ns, name = namesplit(fqname) + args = {:type => name, :namespace => ns, :fqname => fqname, :interp => self} + args[:code] = code if code + args[:parentclass] = parent if parent + @classtable[fqname] = @parser.ast AST::HostClass, args + end - def scope - return @scope - end + return @classtable[fqname] + end - private + # Create a new definition. + def newdefine(fqname, options = {}) + if @classtable.include?(fqname) + raise Puppet::ParseError, "Cannot redefine class %s as a definition" % + fqname + end + # Make sure our definition doesn't already exist + if other = @definetable[fqname] + @parser.error @parser.addcontext( + "%s is already defined at line %s" % [fqname, other.line], + other + ) + end - # Check whether any of our files have changed. - def checkfiles - if @files.find { |f| f.changed? } - @parsedate = Time.now.to_i - end + ns, name = namesplit(fqname) + args = { + :type => name, + :namespace => ns, + :arguments => options[:arguments], + :code => options[:code], + :fqname => fqname + } + + [:code, :arguments].each do |param| + args[param] = options[param] if options[param] + end + + @definetable[fqname] = @parser.ast AST::Component, args + end + + # Create a new node. Nodes are special, because they're stored in a global + # table, not according to namespaces. + def newnode(names, options = {}) + names = [names] unless names.instance_of?(Array) + names.collect do |name| + if other = @nodetable[name] + @parser.error @parser.addcontext("Node %s is already defined" % [other.name], other) + end + name = name.to_s if name.is_a?(Symbol) + args = { + :name => name, + } + if options[:code] + args[:code] = options[:code] + end + if options[:parent] + args[:parentclass] = options[:parent] end + @nodetable[name] = @parser.ast(AST::Node, args) + @nodetable[name].fqname = name + @nodetable[name] + @nodetable[name].interp = self + @nodetable[name] + end + end + + # Add a new file to be checked when we're checking to see if we should be + # reparsed. + def newfile(*files) + files.each do |file| + unless file.is_a? Puppet::LoadedFile + file = Puppet::LoadedFile.new(file) + end + @files << file + end + end - # Parse the files, generating our parse tree. This automatically - # reparses only if files are updated, so it's safe to call multiple - # times. - def parsefiles - # First check whether there are updates to any non-puppet files - # like templates. If we need to reparse, this will get quashed, - # but it needs to be done first in case there's no reparse - # but there are other file changes. - checkfiles() - - # Check if the parser should reparse. - if @file - if defined? @parser - if stamp = @parser.reparse? - Puppet.notice "Reloading files" - else - return false + # Search for our node in the various locations. + def nodesearch(*nodes) + # 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 + # Do an inverse sort on the length, so the longest match always + # wins + nodes.sort { |a,b| b.length <=> a.length }.each do |node| + node = node.to_s if node.is_a?(Symbol) + if obj = self.send(method, node) + nsource = obj.file || source + Puppet.info "Found %s in %s" % [node, nsource] + return obj + else + # Look for a default node. + if defobj = self.send(method, "default") + Puppet.info "Found default node for %s in %s" % + [node, source] + return defobj end end + end + end + end - unless FileTest.exists?(@file) - if @ast - return - else - raise Puppet::Error, "Manifest %s must exist" % @file - end + return nil + end + + # See if our node was defined in the code. + def nodesearch_code(name) + @nodetable[name] + end + + # Look for our node in ldap. + def nodesearch_ldap(node) + parent, classes = ldapsearch(node) + if parent or classes + args = {} + args[:classes] = classes if classes and ! classes.empty? + args[:parentnode] = parent if parent + return gennode(node, args) + else + return nil + end + end + + def parsedate + parsefiles() + @parsedate + end + + # evaluate our whole tree + def run(client, facts) + # We have to leave this for after initialization because there + # seems to be a problem keeping ldap open after a fork. + unless @setup + @nodesources.each { |source| + method = "setup_%s" % source.to_s + if respond_to? method + exceptwrap :type => Puppet::Error, + :message => "Could not set up node source %s" % source do + self.send(method) end end + } + end + parsefiles() + + # Evaluate all of the appropriate code. + objects = evaluate(client, facts) + + # And return it all. + return objects + end + + # Connect to the LDAP Server + def setup_ldap + self.class.ldap = nil + begin + require 'ldap' + rescue LoadError + Puppet.notice( + "Could not set up LDAP Connection: Missing ruby/ldap libraries" + ) + @ldap = nil + return + end + begin + @ldap = self.class.ldap() + rescue => detail + raise Puppet::Error, "Could not connect to LDAP: %s" % detail + end + end + + def scope + return @scope + end + + # Iteratively make sure that every object in the scope tree is translated. + def translate(scope) + end + + private - # should i be creating a new parser each time...? - @parser = Puppet::Parser::Parser.new() - if @code - @parser.string = @code + # Check whether any of our files have changed. + def checkfiles + if @files.find { |f| f.changed? } + @parsedate = Time.now.to_i + end + end + + # Parse the files, generating our parse tree. This automatically + # reparses only if files are updated, so it's safe to call multiple + # times. + def parsefiles + # First check whether there are updates to any non-puppet files + # like templates. If we need to reparse, this will get quashed, + # but it needs to be done first in case there's no reparse + # but there are other file changes. + checkfiles() + + # Check if the parser should reparse. + if @file + if defined? @parser + if stamp = @parser.reparse? + Puppet.notice "Reloading files" else - @parser.file = @file - # Mark when we parsed, so we can check freshness - @parsedate = File.stat(@file).ctime.to_i + return false end + end - if @local - @ast = @parser.parse + unless FileTest.exists?(@file) + # If we've already parsed, then we're ok. + if findclass("", "") + return else - benchmark(:info, "Parsed manifest") do - @ast = @parser.parse - end + raise Puppet::Error, "Manifest %s must exist" % @file end - @parsedate = Time.now.to_i end + end - # Store the configs into the database. - def storeconfigs(hash) - unless defined? ActiveRecord - require 'puppet/rails' - unless defined? ActiveRecord - raise LoadError, - "storeconfigs is enabled but rails is unavailable" - end - end + # Reset our parse tables. + clear() + + # Create a new parser, just to keep things fresh. + @parser = Puppet::Parser::Parser.new(self) + if @code + @parser.string = @code + else + @parser.file = @file + # Mark when we parsed, so we can check freshness + @parsedate = File.stat(@file).ctime.to_i + end - Puppet::Rails.init - - # Fork the storage, since we don't need the client waiting - # on that. How do I avoid this duplication? - if @forksave - fork { - # We store all of the objects, even the collectable ones - benchmark(:info, "Stored configuration for #{hash[:client]}") do - # Try to batch things a bit, by putting them into - # a transaction - Puppet::Rails::Host.transaction do - Puppet::Rails::Host.store(hash) - end - end - } - else - # We store all of the objects, even the collectable ones - benchmark(:info, "Stored configuration for #{hash[:client]}") do - Puppet::Rails::Host.transaction do - Puppet::Rails::Host.store(hash) - end + # Parsing stores all classes and defines and such in their + # various tables, so we don't worry about the return. + if @local + @parser.parse + else + benchmark(:info, "Parsed manifest") do + @parser.parse + end + end + @parsedate = Time.now.to_i + end + + # Store the configs into the database. + def storeconfigs(hash) + unless defined? ActiveRecord + require 'puppet/rails' + unless defined? ActiveRecord + raise LoadError, + "storeconfigs is enabled but rails is unavailable" + end + end + + Puppet::Rails.init + + # Fork the storage, since we don't need the client waiting + # on that. How do I avoid this duplication? + if @forksave + fork { + # We store all of the objects, even the collectable ones + benchmark(:info, "Stored configuration for #{hash[:name]}") do + # Try to batch things a bit, by putting them into + # a transaction + Puppet::Rails::Host.transaction do + Puppet::Rails::Host.store(hash) end end - - # Now that we've stored everything, we need to strip out - # the collectable objects so that they are not sent on - # to the host - hash[:objects].collectstrip! + } + else + # We store all of the objects, even the collectable ones + benchmark(:info, "Stored configuration for #{hash[:name]}") do + Puppet::Rails::Host.transaction do + Puppet::Rails::Host.store(hash) + end end end + + # Now that we've stored everything, we need to strip out + # the collectable objects so that they are not sent on + # to the host + #hash[:objects].collectstrip! end end diff --git a/lib/puppet/parser/lexer.rb b/lib/puppet/parser/lexer.rb index 80a8715ba..39fef8668 100644 --- a/lib/puppet/parser/lexer.rb +++ b/lib/puppet/parser/lexer.rb @@ -12,6 +12,8 @@ module Puppet class Lexer attr_reader :line, :last, :file + attr_accessor :indefine + #%r{\w+} => :WORD, @@tokens = { %r{#.*} => :COMMENT, @@ -31,10 +33,13 @@ module Puppet %r{<} => :LESSTHAN, %r{<=} => :LESSEQUAL, %r{!=} => :NOTEQUAL, + %r{!} => :NOT, %r{,} => :COMMA, %r{\.} => :DOT, %r{:} => :COLON, %r{@} => :AT, + %r{<<\|} => :LLCOLLECT, + %r{\|>>} => :RRCOLLECT, %r{<\|} => :LCOLLECT, %r{\|>} => :RCOLLECT, %r{;} => :SEMIC, @@ -42,6 +47,8 @@ module Puppet %r{\\} => :BACKSLASH, %r{=>} => :FARROW, %r{[a-z][-\w]*} => :NAME, + %r{([a-z][-\w]*::)+[a-z][-\w]*} => :CLASSNAME, + %r{([A-Z][-\w]*::)+[A-Z][-\w]*} => :CLASSREF, %r{[A-Z][-\w]*} => :TYPE, %r{[0-9]+} => :NUMBER, %r{\$\w+} => :VARIABLE @@ -59,9 +66,15 @@ module Puppet "else" => :ELSE, "inherits" => :INHERITS, "node" => :NODE, - "true" => :BOOLEAN + "true" => :BOOLEAN, + "and" => :AND, + "or" => :OR } + def clear + initvars + end + # scan the whole file # basically just used for testing def fullscan @@ -90,14 +103,47 @@ module Puppet } end + def indefine? + if defined? @indefine + @indefine + else + false + end + end + def initialize + initvars() + end + + def initvars @line = 1 @last = "" + @lasttoken = nil @scanner = nil @file = nil # AAARRGGGG! okay, regexes in ruby are bloody annoying # no one else has "\n" =~ /\s/ @skip = %r{[ \t]+} + + @namestack = [] + @indefine = false + end + + # Go up one in the namespace. + def namepop + @namestack.pop + end + + # Collect the current namespace. + def namespace + @namestack.join("::") + end + + # This value might have :: in it, but we don't care -- it'll be + # handled normally when joining, and when popping we want to pop + # this full value, however long the namespace is. + def namestack(value) + @namestack << value end def rest @@ -164,6 +210,7 @@ module Puppet # if this gets much more complicated, it should # be moved up to where the tokens themselves are defined # which will get me about 75% of the way to a lexer generator + ptoken = stoken case stoken when :NAME then wtoken = stoken @@ -171,36 +218,51 @@ module Puppet if @@keywords.include?(value) wtoken = @@keywords[value] #Puppet.debug("token '%s'" % wtoken) + if wtoken == :BOOLEAN + value = eval(value) + end end - yield [wtoken,value] - @last = value + ptoken = wtoken when :NUMBER then - yield [:NAME,value] - # just throw comments away + ptoken = :NAME when :COMMENT then # just throw comments away + next when :RETURN then @line += 1 @scanner.skip(@skip) + next when :SQUOTE then #Puppet.debug("searching '%s' after '%s'" % [self.rest,value]) value = self.slurpstring(value) - yield [:SQTEXT,value] - @last = value - #stoken = :DQTEXT + ptoken = :SQTEXT #Puppet.debug("got string '%s' => '%s'" % [:DQTEXT,value]) when :DQUOTE then - #Puppet.debug("searching '%s' after '%s'" % [self.rest,value]) value = self.slurpstring(value) - yield [:DQTEXT,value] - @last = value - #stoken = :DQTEXT - #Puppet.debug("got string '%s' => '%s'" % [:DQTEXT,value]) - else - #Puppet.debug("got token '%s' => '%s'" % [stoken,value]) - yield [stoken,value] - @last = value + ptoken = :DQTEXT + when :VARIABLE then + value = value.sub(/^\$/, '') + end + + yield [ptoken, value] + + if @lasttoken == :CLASS + namestack(value) end + + if @lasttoken == :DEFINE + if indefine? + self.indefine = false + raise Puppet::ParseError, + "Definitions cannot nest" + end + + @indefine = true + end + + @last = value + @lasttoken = ptoken + @scanner.skip(@skip) end @scanner = nil diff --git a/lib/puppet/parser/parser.rb b/lib/puppet/parser/parser.rb index 3cda43ee0..047dace3a 100644 --- a/lib/puppet/parser/parser.rb +++ b/lib/puppet/parser/parser.rb @@ -29,12 +29,24 @@ module Puppet class Parser < Racc::Parser -module_eval <<'..end grammar.ra modeval..idc4b6d943e3', 'grammar.ra', 603 +module_eval <<'..end grammar.ra modeval..ide506d3a623', 'grammar.ra', 621 require 'puppet/parser/functions' -attr_reader :file +attr_reader :file, :interp attr_accessor :files +# Add context to a message; useful for error messages and such. +def addcontext(message, obj = nil) + obj ||= @lexer + + message += " on line %s" % obj.line + if file = obj.file + message += " in file %s" % file + end + + return message +end + # Create an AST array out of all of the args def aryfy(*args) if args[0].instance_of?(AST::ASTArray) @@ -66,6 +78,17 @@ def ast(klass, hash = nil) return klass.new(hash) end +# Raise a Parse error. +def error(message) + except = Puppet::ParseError.new(message) + except.line = @lexer.line + if @lexer.file + except.file = @lexer.file + end + + raise except +end + def file=(file) unless FileTest.exists?(file) unless file =~ /\.pp$/ @@ -83,23 +106,20 @@ def file=(file) end end -def initialize +def initialize(interpreter) + @interp = interpreter + initvars() +end + +# Initialize or reset all of our variables. +def initvars @lexer = Puppet::Parser::Lexer.new() @files = [] - #if Puppet[:debug] - # @yydebug = true - #end end -# Add a new file to be checked when we're checking to see if we should be -# reparsed. -def newfile(*files) - files.each do |file| - unless file.is_a? Puppet::LoadedFile - file = Puppet::LoadedFile.new(file) - end - @files << file - end +# The fully qualifed name, with the full namespace. +def fqname(name) + [@lexer.namespace, name].join("::").sub(/^::/, '') end def on_error(token,value,stack) @@ -123,7 +143,7 @@ def parse(string = nil) self.string = string end begin - yyparse(@lexer,:scan) + main = yyparse(@lexer,:scan) rescue Racc::ParseError => except error = Puppet::ParseError.new(except) error.line = @lexer.line @@ -150,6 +170,13 @@ def parse(string = nil) error.set_backtrace except.backtrace raise error end + if main + # Store the results as the top-level class. + interp.newclass("", :code => main) + return main + end +ensure + @lexer.clear end # See if any of the files have changed. @@ -172,363 +199,413 @@ end # $Id$ -..end grammar.ra modeval..idc4b6d943e3 +..end grammar.ra modeval..ide506d3a623 ##### racc 1.4.5 generates ### racc_reduce_table = [ 0, 0, :racc_error, - 1, 44, :_reduce_1, - 1, 44, :_reduce_none, - 1, 45, :_reduce_none, - 2, 45, :_reduce_4, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 1, 47, :_reduce_none, - 4, 55, :_reduce_16, - 2, 55, :_reduce_17, - 1, 59, :_reduce_none, - 3, 59, :_reduce_19, - 1, 60, :_reduce_none, - 1, 60, :_reduce_none, - 1, 60, :_reduce_none, - 5, 48, :_reduce_23, - 5, 48, :_reduce_24, - 5, 48, :_reduce_25, - 2, 49, :_reduce_26, - 3, 50, :_reduce_27, - 4, 69, :_reduce_28, - 1, 64, :_reduce_none, - 3, 64, :_reduce_30, - 0, 65, :_reduce_none, - 1, 65, :_reduce_none, - 1, 61, :_reduce_33, - 1, 68, :_reduce_34, - 1, 70, :_reduce_none, - 1, 70, :_reduce_none, - 1, 70, :_reduce_none, - 1, 70, :_reduce_none, - 1, 70, :_reduce_none, - 1, 70, :_reduce_none, - 3, 51, :_reduce_41, - 0, 66, :_reduce_42, - 1, 66, :_reduce_43, - 3, 66, :_reduce_44, - 3, 74, :_reduce_45, - 1, 75, :_reduce_none, - 3, 75, :_reduce_47, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 1, 73, :_reduce_none, - 4, 79, :_reduce_57, - 1, 63, :_reduce_58, - 1, 63, :_reduce_59, - 1, 77, :_reduce_60, - 4, 78, :_reduce_61, - 6, 53, :_reduce_62, - 0, 81, :_reduce_none, - 4, 81, :_reduce_64, + 1, 51, :_reduce_1, + 1, 51, :_reduce_none, + 1, 52, :_reduce_none, + 2, 52, :_reduce_4, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 1, 54, :_reduce_none, + 4, 62, :_reduce_17, + 2, 62, :_reduce_18, + 1, 67, :_reduce_none, + 3, 67, :_reduce_20, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 1, 68, :_reduce_none, + 5, 55, :_reduce_24, + 5, 55, :_reduce_25, + 5, 55, :_reduce_26, + 4, 66, :_reduce_27, + 2, 56, :_reduce_28, + 1, 77, :_reduce_29, + 2, 77, :_reduce_30, + 2, 57, :_reduce_31, + 1, 78, :_reduce_none, + 1, 78, :_reduce_none, + 3, 79, :_reduce_34, + 3, 79, :_reduce_35, + 1, 80, :_reduce_none, 1, 80, :_reduce_none, - 5, 52, :_reduce_66, + 3, 80, :_reduce_38, + 1, 81, :_reduce_none, + 3, 81, :_reduce_40, 1, 82, :_reduce_none, - 2, 82, :_reduce_68, - 5, 83, :_reduce_69, - 4, 83, :_reduce_70, + 1, 82, :_reduce_none, + 3, 83, :_reduce_43, + 3, 83, :_reduce_44, + 1, 84, :_reduce_none, 1, 84, :_reduce_none, - 3, 84, :_reduce_72, - 3, 71, :_reduce_73, - 1, 86, :_reduce_none, - 3, 86, :_reduce_75, - 1, 88, :_reduce_none, - 3, 88, :_reduce_77, - 3, 87, :_reduce_78, + 4, 86, :_reduce_47, + 1, 72, :_reduce_none, + 3, 72, :_reduce_49, + 0, 73, :_reduce_none, + 1, 73, :_reduce_none, + 1, 69, :_reduce_52, + 1, 88, :_reduce_53, + 1, 87, :_reduce_none, + 1, 87, :_reduce_none, + 1, 87, :_reduce_none, + 1, 87, :_reduce_none, + 1, 87, :_reduce_none, + 1, 87, :_reduce_none, + 3, 58, :_reduce_60, + 0, 74, :_reduce_61, + 1, 74, :_reduce_62, + 3, 74, :_reduce_63, + 3, 92, :_reduce_64, + 1, 93, :_reduce_none, + 3, 93, :_reduce_66, 1, 85, :_reduce_none, 1, 85, :_reduce_none, 1, 85, :_reduce_none, 1, 85, :_reduce_none, 1, 85, :_reduce_none, 1, 85, :_reduce_none, - 1, 85, :_reduce_85, - 2, 54, :_reduce_86, - 6, 56, :_reduce_87, - 5, 56, :_reduce_88, - 6, 57, :_reduce_89, - 5, 57, :_reduce_90, - 6, 58, :_reduce_91, - 5, 58, :_reduce_92, 1, 91, :_reduce_none, - 3, 91, :_reduce_94, - 1, 92, :_reduce_95, - 1, 92, :_reduce_96, - 1, 92, :_reduce_97, - 0, 46, :_reduce_98, - 1, 89, :_reduce_none, - 3, 89, :_reduce_100, - 3, 89, :_reduce_101, - 1, 93, :_reduce_none, - 3, 93, :_reduce_103, - 3, 94, :_reduce_104, - 1, 94, :_reduce_105, - 3, 94, :_reduce_106, - 1, 94, :_reduce_107, - 1, 90, :_reduce_none, - 2, 90, :_reduce_109, - 1, 62, :_reduce_110, - 1, 95, :_reduce_111, - 3, 72, :_reduce_112, - 2, 72, :_reduce_113, - 1, 76, :_reduce_none, - 1, 76, :_reduce_none, - 0, 67, :_reduce_none, - 1, 67, :_reduce_117 ] - -racc_reduce_n = 118 - -racc_shift_n = 197 + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 4, 96, :_reduce_82, + 1, 71, :_reduce_83, + 1, 71, :_reduce_84, + 1, 95, :_reduce_85, + 4, 76, :_reduce_86, + 4, 76, :_reduce_87, + 6, 60, :_reduce_88, + 0, 98, :_reduce_none, + 4, 98, :_reduce_90, + 1, 97, :_reduce_none, + 5, 59, :_reduce_92, + 1, 99, :_reduce_none, + 2, 99, :_reduce_94, + 5, 100, :_reduce_95, + 4, 100, :_reduce_96, + 1, 101, :_reduce_none, + 3, 101, :_reduce_98, + 3, 89, :_reduce_99, + 1, 103, :_reduce_none, + 3, 103, :_reduce_101, + 1, 105, :_reduce_none, + 3, 105, :_reduce_103, + 3, 104, :_reduce_104, + 1, 102, :_reduce_none, + 1, 102, :_reduce_none, + 1, 102, :_reduce_none, + 1, 102, :_reduce_none, + 1, 102, :_reduce_none, + 1, 102, :_reduce_none, + 1, 102, :_reduce_111, + 2, 61, :_reduce_112, + 6, 63, :_reduce_113, + 5, 63, :_reduce_114, + 6, 64, :_reduce_115, + 5, 64, :_reduce_116, + 6, 65, :_reduce_117, + 5, 65, :_reduce_118, + 1, 106, :_reduce_none, + 1, 106, :_reduce_none, + 1, 109, :_reduce_none, + 3, 109, :_reduce_122, + 1, 110, :_reduce_none, + 1, 110, :_reduce_none, + 1, 110, :_reduce_none, + 0, 53, :_reduce_126, + 0, 111, :_reduce_127, + 1, 107, :_reduce_none, + 3, 107, :_reduce_129, + 3, 107, :_reduce_130, + 1, 112, :_reduce_none, + 3, 112, :_reduce_132, + 3, 113, :_reduce_133, + 1, 113, :_reduce_134, + 3, 113, :_reduce_135, + 1, 113, :_reduce_136, + 1, 108, :_reduce_none, + 2, 108, :_reduce_138, + 1, 70, :_reduce_139, + 3, 90, :_reduce_140, + 2, 90, :_reduce_141, + 1, 94, :_reduce_none, + 1, 94, :_reduce_none, + 0, 75, :_reduce_none, + 1, 75, :_reduce_145 ] + +racc_reduce_n = 146 + +racc_shift_n = 240 racc_action_table = [ - 48, 36, 38, 81, 162, 20, 48, 36, 38, 64, - 79, 161, 48, 36, 38, 86, 20, 36, 38, 75, - 36, 38, 20, 37, -79, 145, 36, 38, 20, 44, - 37, -82, -79, 49, 53, 44, 31, 55, 31, 49, - 53, 44, 72, 55, 65, 49, 53, -81, 44, 55, - 48, 36, 38, 37, 44, 145, 48, 36, 38, 37, - 37, 137, 48, 36, 38, 113, 20, 139, 29, 79, - 29, 33, 20, 33, -80, 79, 85, 170, 20, 44, - 154, 146, 102, 49, 53, 44, 115, 55, 171, 49, - 53, 44, 149, 55, 77, 49, 53, 36, 38, 55, - 48, 36, 38, 36, 38, 116, 48, 36, 38, 117, - 118, 88, 48, 36, 38, 179, 20, 117, 118, -81, - 45, 87, 20, 112, 155, 44, -82, 158, 20, 44, - 37, 44, 105, 49, 53, 44, 37, 55, 64, 49, - 53, 44, 163, 55, 77, 49, 53, 85, 84, 55, - 48, 36, 38, -80, 169, 72, 48, 36, 38, 172, - 173, -83, 48, 36, 38, -84, 20, 178, 108, 136, - 182, 77, 20, 36, 38, 112, 71, 160, 20, 44, - 109, 70, 69, 49, 53, 44, 113, 55, 20, 49, - 53, 44, 190, 55, 66, 49, 93, 36, 38, 55, - 112, 44, 35, 28, 166, 49, 53, 36, 38, 55, - 125, nil, 20, 36, 38, nil, nil, nil, nil, 36, - 38, nil, 20, nil, nil, 44, nil, nil, 20, 49, - 53, nil, nil, 55, 20, 44, nil, nil, nil, 49, - 53, 44, nil, 55, nil, 49, 53, 44, nil, 55, - nil, 49, 53, 36, 38, 55, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 20, 176, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 20, 44, nil, nil, 168, 49, 53, nil, 12, 55, - 16, 19, nil, 24, 26, 20, 3, nil, 9, 175, - 14, nil, 21, 12, nil, 16, 19, nil, 24, 26, - 20, 3, nil, 9, 196, 14, nil, 21, 12, nil, - 16, 19, nil, 24, 26, 20, 3, nil, 9, 153, - 14, nil, 21, 12, nil, 16, 19, nil, 24, 26, - 20, 3, nil, 9, 185, 14, nil, 21, 12, nil, - 16, 19, nil, 24, 26, 20, 3, nil, 9, 189, - 14, nil, 21, 12, nil, 16, 19, nil, 24, 26, - 20, 3, nil, 9, 195, 14, nil, 21, 12, nil, - 16, 19, nil, 24, 26, 20, 3, nil, 9, 148, - 14, nil, 21, 12, nil, 16, 19, nil, 24, 26, - 20, 3, nil, 9, 193, 14, nil, 21, 12, nil, - 16, 19, nil, 24, 26, 20, 3, nil, 9, nil, - 14, nil, 21, 12, nil, 16, 19, nil, 24, 26, - 20, 3, nil, 9, nil, 14, nil, 21, 12, nil, - 16, 19, nil, 24, 26, 20, 3, nil, 9, nil, - 14, nil, 21, 12, nil, 16, 19, nil, 24, 26, - 20, 3, nil, 9, nil, 14, nil, 21, 12, nil, - 16, 19, nil, 24, 26, 20, 3, nil, 9, nil, - 14, nil, 21, 12, nil, 16, 19, nil, 24, 26, - nil, 3, nil, 9, nil, 14, nil, 21 ] + 59, 50, 52, 50, 52, 97, 59, 50, 52, 33, + 146, 75, 59, 50, 52, 76, 69, -105, 59, 50, + 52, 171, 111, 120, 59, 50, 52, 117, 69, 32, + 84, 45, 38, 45, 69, 64, 67, 45, 54, 70, + 69, 64, 108, 45, 146, 70, 137, 64, 67, 45, + 150, 70, 121, 64, 67, 45, 39, 70, 178, 64, + 67, 50, 52, 70, 59, 50, 52, 50, 52, 122, + 59, 50, 52, 48, 134, 135, 59, 50, 52, 75, + 69, 99, 59, 50, 52, -110, 69, 124, 59, 50, + 52, 45, 69, 120, 143, 45, 54, 45, 69, 64, + 67, 45, 54, 70, 69, 64, 67, 45, 126, 70, + -107, 64, 67, 45, -108, 70, 139, 64, 67, 45, + 183, 70, 87, 64, 67, 138, 99, 70, 59, 50, + 52, -105, 173, 45, 59, 50, 52, 177, 54, 205, + 59, 50, 52, 119, 69, 134, 135, 50, 52, 197, + 69, 208, 173, 50, 52, 179, 111, 177, 180, 45, + 41, 215, 111, 64, 67, 45, 198, 70, 111, 64, + 67, 45, 41, 70, 214, 64, 163, 45, 124, 70, + 124, 64, 163, 45, -106, 70, 210, 64, 163, 50, + 52, 70, 35, 209, 42, 50, 52, 43, 202, 34, + 35, 120, 50, 52, 111, 164, 42, 34, 99, 43, + 111, 50, 52, 50, 52, 50, 52, 111, 150, 45, + 50, 52, 147, 64, 163, 45, 111, 70, 111, 64, + 163, -107, 45, 70, 152, 111, 64, 163, 154, 155, + 70, 45, -108, 45, 211, 64, 163, 64, 163, 70, + 45, 70, 212, 87, 64, 163, 87, 87, 70, 48, + 50, 52, 213, 56, 45, 219, 140, 45, 45, 54, + 134, 135, 54, 54, -105, 224, 17, 154, 155, 46, + 216, 84, 206, 124, 76, -106, 14, -109, 20, 23, + 45, 1, 5, 17, 9, 54, 12, -108, 16, 217, + 24, -107, -110, 14, 56, 20, 23, 150, 1, 5, + 17, 9, 82, 12, -106, 16, 220, 24, 225, 227, + 14, 81, 20, 23, 145, 1, 5, 17, 9, 127, + 12, 124, 16, 232, 24, 77, 141, 14, 234, 20, + 23, 131, 1, 5, 17, 9, 132, 12, 30, 16, + 235, 24, 151, nil, 14, nil, 20, 23, nil, 1, + 5, 17, 9, nil, 12, nil, 16, 193, 24, nil, + nil, 14, nil, 20, 23, nil, 1, 5, 17, 9, + nil, 12, nil, 16, 238, 24, nil, nil, 14, nil, + 20, 23, nil, 1, 5, 17, 9, nil, 12, nil, + 16, 181, 24, nil, nil, 14, nil, 20, 23, nil, + 1, 5, 17, 9, nil, 12, nil, 16, 239, 24, + nil, nil, 14, nil, 20, 23, nil, 1, 5, 17, + 9, nil, 12, nil, 16, nil, 24, nil, nil, 14, + nil, 20, 23, nil, 1, 5, 17, 9, nil, 12, + nil, 16, nil, 24, nil, nil, 14, nil, 20, 23, + nil, 1, 5, 17, 9, nil, 12, nil, 16, nil, + 24, nil, nil, 14, nil, 20, 23, nil, 1, 5, + 17, 9, nil, 12, nil, 16, nil, 24, nil, nil, + 14, nil, 20, 23, nil, 1, 5, 17, 9, nil, + 12, nil, 16, nil, 24, nil, nil, 14, nil, 20, + 23, nil, 1, 5, nil, 9, nil, 12, nil, 16, + nil, 24 ] racc_action_check = [ - 48, 48, 48, 48, 133, 21, 86, 86, 86, 17, - 39, 133, 137, 137, 137, 57, 48, 16, 16, 30, - 79, 79, 86, 21, 98, 170, 45, 45, 137, 48, - 170, 99, 57, 48, 48, 86, 3, 48, 75, 86, - 86, 137, 30, 86, 17, 137, 137, 100, 79, 137, - 69, 69, 69, 79, 45, 105, 119, 119, 119, 45, - 105, 101, 172, 172, 172, 93, 69, 103, 3, 121, - 75, 3, 119, 75, 92, 80, 93, 141, 172, 69, - 121, 107, 65, 69, 69, 119, 80, 69, 141, 119, - 119, 172, 111, 119, 112, 172, 172, 85, 85, 172, - 12, 12, 12, 9, 9, 83, 14, 14, 14, 83, - 83, 62, 113, 113, 113, 156, 12, 156, 156, 61, - 9, 60, 14, 90, 122, 85, 59, 128, 113, 12, - 85, 9, 66, 12, 12, 14, 9, 12, 68, 14, - 14, 113, 135, 14, 136, 113, 113, 53, 52, 113, - 173, 173, 173, 51, 140, 70, 158, 158, 158, 142, - 144, 50, 64, 64, 64, 46, 173, 153, 72, 95, - 161, 35, 158, 131, 131, 164, 28, 131, 64, 173, - 74, 26, 24, 173, 173, 158, 77, 173, 131, 158, - 158, 64, 178, 158, 19, 64, 64, 88, 88, 64, - 76, 131, 6, 2, 138, 131, 131, 87, 87, 131, - 87, nil, 88, 125, 125, nil, nil, nil, nil, 162, - 162, nil, 87, nil, nil, 88, nil, nil, 125, 88, - 88, nil, nil, 88, 162, 87, nil, nil, nil, 87, - 87, 125, nil, 87, nil, 125, 125, 162, nil, 125, - nil, 162, 162, 180, 180, 162, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 180, 147, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 147, 180, nil, nil, 139, 180, 180, nil, 147, 180, - 147, 147, nil, 147, 147, 139, 147, nil, 147, 146, - 147, nil, 147, 139, nil, 139, 139, nil, 139, 139, - 146, 139, nil, 139, 194, 139, nil, 139, 146, nil, - 146, 146, nil, 146, 146, 194, 146, nil, 146, 120, - 146, nil, 146, 194, nil, 194, 194, nil, 194, 194, - 120, 194, nil, 194, 167, 194, nil, 194, 120, nil, - 120, 120, nil, 120, 120, 167, 120, nil, 120, 174, - 120, nil, 120, 167, nil, 167, 167, nil, 167, 167, - 174, 167, nil, 167, 192, 167, nil, 167, 174, nil, - 174, 174, nil, 174, 174, 192, 174, nil, 174, 109, - 174, nil, 174, 192, nil, 192, 192, nil, 192, 192, - 109, 192, nil, 192, 182, 192, nil, 192, 109, nil, - 109, 109, nil, 109, 109, 182, 109, nil, 109, nil, - 109, nil, 109, 182, nil, 182, 182, nil, 182, 182, - 84, 182, nil, 182, nil, 182, nil, 182, 84, nil, - 84, 84, nil, 84, 84, 190, 84, nil, 84, nil, - 84, nil, 84, 190, nil, 190, 190, nil, 190, 190, - 5, 190, nil, 190, nil, 190, nil, 190, 5, nil, - 5, 5, nil, 5, 5, 0, 5, nil, 5, nil, - 5, nil, 5, 0, nil, 0, 0, nil, 0, 0, - nil, 0, nil, 0, nil, 0, nil, 0 ] + 75, 75, 75, 46, 46, 44, 56, 56, 56, 2, + 108, 17, 153, 153, 153, 17, 75, 187, 146, 146, + 146, 123, 56, 108, 59, 59, 59, 59, 153, 2, + 44, 75, 6, 46, 146, 75, 75, 56, 46, 75, + 59, 56, 56, 153, 124, 56, 89, 153, 153, 146, + 125, 153, 72, 146, 146, 59, 6, 146, 128, 59, + 59, 99, 99, 59, 30, 30, 30, 120, 120, 74, + 48, 48, 48, 67, 89, 89, 14, 14, 14, 69, + 30, 98, 16, 16, 16, 68, 48, 76, 202, 202, + 202, 99, 14, 67, 98, 30, 99, 120, 16, 30, + 30, 48, 120, 30, 202, 48, 48, 14, 77, 48, + 66, 14, 14, 16, 65, 14, 93, 16, 16, 202, + 133, 16, 136, 202, 202, 93, 165, 202, 212, 212, + 212, 63, 215, 136, 216, 216, 216, 215, 136, 165, + 147, 147, 147, 61, 212, 133, 133, 167, 167, 148, + 216, 167, 127, 210, 210, 130, 147, 127, 130, 212, + 97, 175, 167, 212, 212, 216, 149, 212, 210, 216, + 216, 147, 9, 216, 175, 147, 147, 167, 150, 147, + 151, 167, 167, 210, 57, 167, 169, 210, 210, 223, + 223, 210, 23, 169, 97, 122, 122, 97, 158, 23, + 5, 163, 119, 119, 223, 119, 9, 5, 47, 9, + 122, 139, 139, 138, 138, 20, 20, 119, 113, 223, + 164, 164, 109, 223, 223, 122, 139, 223, 138, 122, + 122, 107, 119, 122, 118, 164, 119, 119, 118, 118, + 119, 139, 106, 138, 172, 139, 139, 138, 138, 139, + 164, 138, 173, 87, 164, 164, 39, 38, 164, 12, + 12, 12, 174, 12, 87, 182, 95, 39, 38, 87, + 95, 95, 39, 38, 104, 204, 182, 204, 204, 12, + 177, 36, 166, 180, 33, 185, 182, 71, 182, 182, + 12, 182, 182, 166, 182, 12, 182, 188, 182, 178, + 182, 189, 190, 166, 32, 166, 166, 200, 166, 166, + 178, 166, 27, 166, 101, 166, 194, 166, 206, 209, + 178, 24, 178, 178, 100, 178, 178, 194, 178, 80, + 178, 82, 178, 218, 178, 19, 96, 194, 225, 194, + 194, 84, 194, 194, 218, 194, 86, 194, 1, 194, + 227, 194, 115, nil, 218, nil, 218, 218, nil, 218, + 218, 227, 218, nil, 218, nil, 218, 141, 218, nil, + nil, 227, nil, 227, 227, nil, 227, 227, 141, 227, + nil, 227, nil, 227, 236, 227, nil, nil, 141, nil, + 141, 141, nil, 141, 141, 236, 141, nil, 141, nil, + 141, 132, 141, nil, nil, 236, nil, 236, 236, nil, + 236, 236, 132, 236, nil, 236, nil, 236, 237, 236, + nil, nil, 132, nil, 132, 132, nil, 132, 132, 237, + 132, nil, 132, nil, 132, nil, 132, nil, nil, 237, + nil, 237, 237, nil, 237, 237, 22, 237, nil, 237, + nil, 237, nil, 237, nil, nil, 22, nil, 22, 22, + nil, 22, 22, 234, 22, nil, 22, nil, 22, nil, + 22, nil, nil, 234, nil, 234, 234, nil, 234, 234, + 121, 234, nil, 234, nil, 234, nil, 234, nil, nil, + 121, nil, 121, 121, nil, 121, 121, 0, 121, nil, + 121, nil, 121, nil, 121, nil, nil, 0, nil, 0, + 0, nil, 0, 0, nil, 0, nil, 0, nil, 0, + nil, 0 ] racc_action_pointer = [ - 457, nil, 203, 32, nil, 442, 196, nil, nil, 100, - nil, nil, 98, nil, 104, nil, 14, 3, nil, 158, - nil, -13, nil, nil, 169, nil, 145, nil, 176, nil, - 9, nil, nil, nil, nil, 135, nil, nil, nil, 0, - nil, nil, nil, nil, nil, 23, 146, nil, -2, nil, - 142, 134, 142, 127, nil, nil, nil, 13, nil, 107, - 102, 100, 105, nil, 160, 40, 112, nil, 132, 48, - 122, nil, 132, nil, 174, 34, 190, 177, nil, 17, - 65, nil, nil, 100, 412, 94, 4, 204, 194, nil, - 113, nil, 55, 56, nil, 152, nil, nil, 5, 12, - 28, 24, nil, 61, nil, 24, nil, 75, nil, 382, - nil, 85, 58, 110, nil, nil, nil, nil, nil, 54, - 322, 59, 119, nil, nil, 210, nil, nil, 118, nil, - nil, 170, nil, -6, nil, 135, 108, 10, 197, 277, - 133, 67, 146, nil, 147, nil, 292, 262, nil, nil, - nil, nil, nil, 140, nil, nil, 108, nil, 154, nil, - nil, 164, 216, nil, 165, nil, nil, 337, nil, nil, - -6, nil, 60, 148, 352, nil, nil, nil, 186, nil, - 250, nil, 397, nil, nil, nil, nil, nil, nil, nil, - 427, nil, 367, nil, 307, nil, nil ] + 479, 335, -9, nil, nil, 162, 13, nil, nil, 168, + nil, nil, 257, nil, 74, nil, 80, 9, nil, 335, + 212, nil, 428, 154, 279, nil, nil, 306, nil, nil, + 62, nil, 298, 278, nil, nil, 246, nil, 235, 234, + nil, nil, nil, nil, -5, nil, 0, 198, 68, nil, + nil, nil, nil, nil, nil, nil, 4, 163, nil, 22, + nil, 122, nil, 110, nil, 93, 89, 71, 64, 77, + nil, 266, 46, nil, 63, -2, 49, 108, nil, nil, + 307, nil, 293, nil, 303, nil, 340, 231, nil, 26, + nil, nil, nil, 101, nil, 222, 330, 156, 71, 58, + 319, 293, nil, nil, 253, nil, 221, 210, 1, 183, + nil, nil, nil, 208, nil, 335, nil, nil, 229, 199, + 64, 462, 192, 16, 35, 40, nil, 119, 52, nil, + 148, nil, 394, 97, nil, nil, 100, nil, 210, 208, + nil, 360, nil, nil, nil, nil, 16, 138, 142, 159, + 140, 142, nil, 10, nil, nil, nil, nil, 189, nil, + nil, nil, nil, 179, 217, 116, 275, 144, nil, 176, + nil, nil, 237, 239, 239, 151, nil, 267, 292, nil, + 245, nil, 258, nil, nil, 264, nil, -4, 276, 280, + 281, nil, nil, nil, 309, nil, nil, nil, nil, nil, + 297, nil, 86, nil, 268, nil, 289, nil, nil, 313, + 150, nil, 126, nil, nil, 99, 132, nil, 326, nil, + nil, nil, nil, 186, nil, 332, nil, 343, nil, nil, + nil, nil, nil, nil, 445, nil, 377, 411, nil, nil ] racc_action_default = [ - -98, -12, -118, -118, -13, -1, -118, -14, -2, -33, - -15, -3, -118, -5, -118, -6, -118, -118, -7, -118, - -34, -118, -8, -9, -118, -10, -118, -11, -118, -95, - -98, -96, -93, -97, -4, -42, -58, -33, -59, -17, - -18, -20, -21, -22, -110, -118, -51, -55, -118, -60, - -56, -50, -118, -33, -52, -85, -54, -49, -65, -53, - -118, -48, -118, -86, -42, -118, -98, -26, -118, -118, - -98, 197, -118, -108, -118, -118, -116, -118, -43, -118, - -118, -113, -46, -118, -118, -118, -118, -118, -118, -84, - -116, -83, -37, -33, -29, -118, -38, -40, -36, -39, - -35, -31, -27, -118, -99, -98, -41, -118, -109, -118, - -94, -118, -117, -118, -19, -16, -112, -114, -115, -118, - -118, -118, -118, -80, -79, -118, -82, -81, -118, -73, - -74, -118, -67, -118, -71, -118, -42, -32, -118, -118, - -118, -118, -105, -102, -107, -111, -118, -118, -92, -25, - -44, -45, -47, -63, -57, -61, -118, -76, -118, -68, - -66, -118, -118, -24, -116, -30, -23, -118, -88, -100, - -118, -101, -118, -118, -118, -90, -91, -62, -118, -75, - -118, -78, -118, -72, -28, -87, -103, -104, -106, -89, - -118, -77, -118, -70, -118, -69, -64 ] + -126, -146, -146, -16, -5, -146, -146, -6, -7, -146, + -8, -9, -33, -10, -146, -11, -146, -32, -12, -146, + -146, -13, -1, -146, -29, -14, -2, -146, -15, -3, + -146, -28, -146, -146, -120, -119, -126, -31, -126, -126, + -121, -124, -123, -125, -126, -139, -146, -18, -146, -19, + -83, -21, -84, -22, -52, -23, -61, -75, -77, -146, + -79, -146, -91, -74, -85, -78, -73, -52, -76, -53, + -111, -81, -146, -80, -146, -146, -61, -146, -112, -4, + -126, -30, -61, -60, -146, -137, -146, -126, -46, -146, + -45, -37, -39, -146, -36, -146, -146, -146, -146, -146, + -146, -56, -57, -59, -55, -62, -58, -54, -52, -50, + -110, -53, -109, -144, -48, -146, -65, -141, -146, -146, + -146, -146, -146, -146, -146, -144, 240, -127, -146, -128, + -146, -138, -146, -146, -42, -41, -146, -35, -146, -146, + -34, -146, -122, -17, -20, -86, -146, -51, -146, -146, + -145, -61, -140, -146, -142, -143, -106, -105, -146, -108, + -99, -107, -100, -52, -146, -146, -146, -146, -93, -146, + -97, -87, -146, -136, -146, -146, -131, -134, -146, -27, + -146, -116, -146, -40, -38, -69, -71, -68, -72, -67, + -70, -43, -44, -118, -146, -64, -49, -24, -25, -63, + -144, -66, -146, -102, -146, -82, -89, -94, -92, -146, + -146, -26, -146, -129, -130, -146, -146, -114, -146, -115, + -117, -47, -104, -146, -101, -146, -88, -146, -98, -135, + -132, -133, -113, -103, -146, -96, -146, -146, -95, -90 ] racc_goto_table = [ - 5, 76, 34, 43, 32, 8, 58, 42, 62, 111, - 63, 132, 39, 143, 150, 51, 52, 51, 130, 94, - 74, 128, 134, 135, 177, 96, 89, 119, 97, 131, - 90, 83, 138, 129, 101, 156, 103, 114, 30, 43, - 67, 141, 82, 42, 2, nil, nil, nil, 80, 89, - 89, 51, nil, nil, 159, nil, 157, nil, 100, 128, - 107, nil, 99, 106, nil, 134, nil, 92, nil, nil, - nil, 104, 51, 43, nil, nil, 110, 42, 186, 43, - 122, 127, 127, 42, 120, 126, 126, 89, 121, 51, - 123, 123, 165, 89, nil, nil, 183, 184, 96, 89, - 180, 97, 164, nil, nil, nil, nil, 151, nil, 147, - 140, 191, nil, 152, 128, nil, 51, 34, nil, 127, - nil, nil, 51, 126, 89, 127, nil, nil, 123, 126, - nil, 100, nil, 41, 123, 99, 57, nil, 57, 167, - 92, nil, 89, nil, 34, 68, 174, nil, nil, nil, - nil, nil, 181, nil, nil, nil, 127, nil, nil, nil, - 126, 51, nil, nil, 34, 123, 187, 188, nil, 41, - nil, 34, 57, 91, 127, 51, 51, nil, 126, nil, - nil, nil, 192, 123, nil, nil, nil, nil, 98, 34, - 194, 34, nil, 57, nil, nil, 91, 91, nil, nil, - nil, nil, nil, 41, nil, nil, nil, nil, nil, 41, - 57, 124, 124, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 142, - nil, nil, nil, nil, 91, nil, nil, 57, nil, nil, - 91, nil, nil, 57, nil, nil, 91, nil, nil, 124, - nil, nil, nil, nil, nil, 124, nil, nil, nil, nil, - nil, 98, nil, nil, nil, nil, nil, nil, nil, nil, - nil, 91, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 57, nil, nil, nil, 124, nil, nil, 91, - nil, nil, nil, nil, 142, nil, 57, 57, nil, nil, - nil, nil, nil, nil, 124 ] + 22, 79, 51, 162, 102, 47, 113, 55, 62, 40, + 74, 114, 103, 153, 158, 78, 176, 170, 109, 149, + 168, 89, 95, 72, 83, 199, 125, 36, 88, 88, + 86, 172, 130, 191, 192, 226, 51, 167, 96, 98, + 26, 55, 100, 148, 144, 80, 104, 73, 203, 73, + 160, 107, 31, 116, 204, 199, 53, 118, 128, 158, + 37, 44, 170, 73, 184, 207, 174, 175, 19, 123, + 133, nil, nil, nil, nil, nil, 85, 88, nil, nil, + nil, 73, 90, 90, 85, nil, 186, 186, nil, 51, + 53, nil, 73, nil, 55, 102, nil, 142, nil, 223, + 106, 200, 196, 103, 230, 228, 221, 233, 73, 157, + 51, 112, 157, 165, 161, 55, nil, 161, 158, nil, + 129, 166, nil, nil, nil, nil, 88, nil, 187, 187, + nil, 90, 182, 189, 189, nil, nil, 104, nil, nil, + 195, 194, 107, 53, nil, 79, nil, 201, nil, nil, + nil, nil, nil, nil, 157, nil, nil, 157, nil, 161, + 110, 79, 161, 159, 53, 101, 159, nil, nil, nil, + nil, nil, nil, 79, 112, nil, nil, 112, 218, 73, + 90, nil, 188, 188, nil, nil, 73, nil, nil, nil, + nil, 106, nil, 112, 112, nil, 222, 79, nil, nil, + 157, nil, 112, nil, nil, 161, 229, nil, 159, nil, + 231, 159, nil, 157, nil, 79, 79, nil, 161, 112, + nil, nil, 112, 110, nil, nil, 110, 236, 156, nil, + nil, 156, nil, nil, 237, 73, nil, nil, nil, nil, + nil, nil, 190, 190, nil, 73, nil, 185, 185, 73, + nil, 110, nil, nil, 159, nil, 101, nil, nil, nil, + nil, nil, nil, nil, nil, 112, nil, 159, 110, nil, + nil, 110, nil, 156, nil, nil, 156, nil, 112, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, 110, nil, nil, nil, nil, 156, + nil, nil, nil, nil, nil, nil, nil, 110, nil, nil, + nil, nil, 156 ] racc_goto_check = [ - 2, 23, 4, 20, 49, 3, 30, 19, 30, 24, - 20, 40, 16, 51, 31, 25, 37, 25, 44, 26, - 47, 42, 42, 24, 38, 28, 34, 33, 29, 39, - 23, 32, 22, 43, 21, 45, 46, 17, 48, 20, - 5, 50, 30, 19, 1, nil, nil, nil, 16, 34, - 34, 25, nil, nil, 40, nil, 44, nil, 20, 42, - 47, nil, 19, 30, nil, 42, nil, 25, nil, nil, - nil, 3, 25, 20, nil, nil, 49, 19, 51, 20, - 30, 20, 20, 19, 2, 19, 19, 34, 16, 25, - 25, 25, 26, 34, nil, nil, 42, 24, 28, 34, - 33, 29, 23, nil, nil, nil, nil, 30, nil, 2, - 3, 44, nil, 30, 42, nil, 25, 4, nil, 20, - nil, nil, 25, 19, 34, 20, nil, nil, 25, 19, - nil, 20, nil, 18, 25, 19, 18, nil, 18, 2, - 25, nil, 34, nil, 4, 18, 2, nil, nil, nil, - nil, nil, 30, nil, nil, nil, 20, nil, nil, nil, - 19, 25, nil, nil, 4, 25, 30, 30, nil, 18, - nil, 4, 18, 36, 20, 25, 25, nil, 19, nil, - nil, nil, 2, 25, nil, nil, nil, nil, 18, 4, - 2, 4, nil, 18, nil, nil, 36, 36, nil, nil, - nil, nil, nil, 18, nil, nil, nil, nil, nil, 18, - 18, 18, 18, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 18, - nil, nil, nil, nil, 36, nil, nil, 18, nil, nil, - 36, nil, nil, 18, nil, nil, 36, nil, nil, 18, - nil, nil, nil, nil, nil, 18, nil, nil, nil, nil, - nil, 18, nil, nil, nil, nil, nil, nil, nil, nil, - nil, 36, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 18, nil, nil, nil, 18, nil, nil, 36, - nil, nil, nil, nil, 18, nil, 18, 18, nil, nil, - nil, nil, nil, nil, 18 ] + 2, 4, 19, 54, 39, 17, 24, 21, 41, 60, + 41, 36, 40, 44, 52, 21, 63, 52, 22, 25, + 50, 30, 30, 47, 41, 42, 24, 56, 19, 19, + 58, 25, 24, 35, 35, 48, 19, 49, 58, 17, + 3, 21, 41, 23, 18, 56, 19, 26, 54, 26, + 53, 21, 5, 41, 55, 42, 20, 43, 57, 52, + 29, 59, 52, 26, 31, 50, 61, 62, 1, 41, + 30, nil, nil, nil, nil, nil, 3, 19, nil, nil, + nil, 26, 20, 20, 3, nil, 39, 39, nil, 19, + 20, nil, 26, nil, 21, 39, nil, 60, nil, 44, + 20, 24, 36, 40, 63, 52, 25, 54, 26, 19, + 19, 46, 19, 17, 21, 21, nil, 21, 52, nil, + 3, 2, nil, nil, nil, nil, 19, nil, 19, 19, + nil, 20, 2, 21, 21, nil, nil, 19, nil, nil, + 41, 2, 21, 20, nil, 4, nil, 41, nil, nil, + nil, nil, nil, nil, 19, nil, nil, 19, nil, 21, + 45, 4, 21, 20, 20, 38, 20, nil, nil, nil, + nil, nil, nil, 4, 46, nil, nil, 46, 2, 26, + 20, nil, 20, 20, nil, nil, 26, nil, nil, nil, + nil, 20, nil, 46, 46, nil, 41, 4, nil, nil, + 19, nil, 46, nil, nil, 21, 41, nil, 20, nil, + 41, 20, nil, 19, nil, 4, 4, nil, 21, 46, + nil, nil, 46, 45, nil, nil, 45, 2, 38, nil, + nil, 38, nil, nil, 2, 26, nil, nil, nil, nil, + nil, nil, 45, 45, nil, 26, nil, 38, 38, 26, + nil, 45, nil, nil, 20, nil, 38, nil, nil, nil, + nil, nil, nil, nil, nil, 46, nil, 20, 45, nil, + nil, 45, nil, 38, nil, nil, 38, nil, 46, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, 45, nil, nil, nil, nil, 38, + nil, nil, nil, nil, nil, nil, nil, 45, nil, nil, + nil, nil, 38 ] racc_goto_pointer = [ - nil, 44, 0, 5, -3, 19, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, 3, -42, 124, -2, - -6, -30, -69, -34, -67, 3, -45, nil, -39, -36, - -6, -98, -17, -56, -38, nil, 109, 4, -129, -59, - -77, nil, -66, -54, -69, -90, -30, -10, 35, 1, - -64, -92, nil ] + nil, 68, 0, 40, -21, 50, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, -7, -55, -10, + 44, -5, -38, -66, -50, -94, 33, nil, nil, 54, + -17, -72, nil, nil, nil, -105, -45, nil, 109, -52, + -44, -6, -125, -2, -105, 104, 55, 9, -171, -85, + -102, nil, -105, -69, -116, -110, 22, -22, -6, 52, + 0, -61, -60, -111 ] racc_goto_default = [ - nil, nil, nil, 73, 11, 13, 15, 18, 22, 23, - 25, 27, 1, 4, 7, 10, nil, 40, 17, 59, - 61, nil, nil, nil, nil, 6, nil, 95, 54, 56, - nil, 78, nil, nil, 46, 47, 50, nil, nil, nil, - nil, 133, 60, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 144 ] + nil, nil, nil, 94, 29, 4, 7, 8, 10, 11, + 13, 15, 18, 21, 25, 28, 3, nil, 49, 63, + 65, 66, nil, nil, nil, nil, 27, 2, 6, nil, + nil, 91, 136, 92, 93, nil, nil, 115, 57, 58, + 60, nil, 105, nil, nil, 68, 71, nil, nil, nil, + nil, 169, 61, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil ] racc_token_table = { false => 0, @@ -550,34 +627,41 @@ racc_token_table = { :DOT => 16, :COLON => 17, :TYPE => 18, - :QMARK => 19, - :LPAREN => 20, - :RPAREN => 21, - :ISEQUAL => 22, - :GREATEREQUAL => 23, - :GREATERTHAN => 24, - :LESSTHAN => 25, - :IF => 26, - :ELSE => 27, - :IMPORT => 28, - :DEFINE => 29, - :ELSIF => 30, - :VARIABLE => 31, - :CLASS => 32, - :INHERITS => 33, - :NODE => 34, - :BOOLEAN => 35, - :NAME => 36, - :SEMIC => 37, - :CASE => 38, - :DEFAULT => 39, - :AT => 40, - :LCOLLECT => 41, - :RCOLLECT => 42 } + :LLCOLLECT => 19, + :RRCOLLECT => 20, + :QMARK => 21, + :LPAREN => 22, + :RPAREN => 23, + :ISEQUAL => 24, + :GREATEREQUAL => 25, + :GREATERTHAN => 26, + :LESSTHAN => 27, + :IF => 28, + :ELSE => 29, + :IMPORT => 30, + :DEFINE => 31, + :ELSIF => 32, + :VARIABLE => 33, + :CLASS => 34, + :INHERITS => 35, + :NODE => 36, + :BOOLEAN => 37, + :NAME => 38, + :SEMIC => 39, + :CASE => 40, + :DEFAULT => 41, + :AT => 42, + :LCOLLECT => 43, + :RCOLLECT => 44, + :CLASSNAME => 45, + :CLASSREF => 46, + :NOT => 47, + :OR => 48, + :AND => 49 } racc_use_result_var = true -racc_nt_base = 43 +racc_nt_base = 50 Racc_arg = [ racc_action_table, @@ -615,6 +699,8 @@ Racc_token_to_s_table = [ 'DOT', 'COLON', 'TYPE', +'LLCOLLECT', +'RRCOLLECT', 'QMARK', 'LPAREN', 'RPAREN', @@ -639,13 +725,18 @@ Racc_token_to_s_table = [ 'AT', 'LCOLLECT', 'RCOLLECT', +'CLASSNAME', +'CLASSREF', +'NOT', +'OR', +'AND', '$start', 'program', 'statements', -'nothing', +'nil', 'statement', -'object', -'collectable', +'resource', +'virtualresource', 'collection', 'assignment', 'casestatement', @@ -655,18 +746,29 @@ Racc_token_to_s_table = [ 'definition', 'hostclass', 'nodedef', -'classnames', -'classname', +'resourceoverride', +'namestrings', +'namestring', 'name', 'variable', 'quotedtext', -'objectinstances', +'resourceinstances', 'endsemi', 'params', 'endcomma', +'resourceref', +'at', +'collectname', +'collectrhand', +'collstatements', +'collstatement', +'colljoin', +'collexpr', +'colllval', +'simplervalue', +'resourceinst', +'resourcename', 'type', -'objectinst', -'objectname', 'selector', 'array', 'rvalue', @@ -674,7 +776,6 @@ Racc_token_to_s_table = [ 'rvalues', 'comma', 'boolean', -'objectref', 'funcrvalue', 'iftest', 'else', @@ -685,13 +786,14 @@ Racc_token_to_s_table = [ 'svalues', 'selectval', 'sintvalues', +'fqname', 'argumentlist', 'parent', 'hostnames', 'hostname', +'nothing', 'arguments', -'argument', -'lvariable'] +'argument'] Racc_debug_parser = false @@ -699,13 +801,21 @@ Racc_debug_parser = false # reduce 0 omitted -module_eval <<'.,.,', 'grammar.ra', 24 +module_eval <<'.,.,', 'grammar.ra', 33 def _reduce_1( val, _values, result ) - # Make sure we always return an array. - if val[0].is_a?(AST::ASTArray) - result = val[0] + if val[0] + # Make sure we always return an array. + if val[0].is_a?(AST::ASTArray) + if val[0].children.empty? + result = nil + else + result = val[0] + end + else + result = aryfy(val[0]) + end else - result = aryfy(val[0]) + result = nil end result end @@ -715,13 +825,18 @@ module_eval <<'.,.,', 'grammar.ra', 24 # reduce 3 omitted -module_eval <<'.,.,', 'grammar.ra', 35 +module_eval <<'.,.,', 'grammar.ra', 49 def _reduce_4( val, _values, result ) - if val[0].instance_of?(AST::ASTArray) - val[0].push(val[1]) - result = val[0] - else - result = ast AST::ASTArray, :children => [val[0],val[1]] + if val[0] and val[1] + if val[0].instance_of?(AST::ASTArray) + val[0].push(val[1]) + result = val[0] + else + result = ast AST::ASTArray, :children => [val[0],val[1]] + end + elsif obj = (val[0] || val[1]) + result = obj + else result = nil end result end @@ -749,8 +864,10 @@ module_eval <<'.,.,', 'grammar.ra', 35 # reduce 15 omitted -module_eval <<'.,.,', 'grammar.ra', 56 - def _reduce_16( val, _values, result ) + # reduce 16 omitted + +module_eval <<'.,.,', 'grammar.ra', 71 + def _reduce_17( val, _values, result ) args = aryfy(val[2]) result = ast AST::Function, :name => val[0], @@ -760,8 +877,8 @@ module_eval <<'.,.,', 'grammar.ra', 56 end .,., -module_eval <<'.,.,', 'grammar.ra', 63 - def _reduce_17( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 78 + def _reduce_18( val, _values, result ) args = aryfy(val[1]) result = ast AST::Function, :name => val[0], @@ -771,10 +888,10 @@ module_eval <<'.,.,', 'grammar.ra', 63 end .,., - # reduce 18 omitted + # reduce 19 omitted -module_eval <<'.,.,', 'grammar.ra', 82 - def _reduce_19( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 85 + def _reduce_20( val, _values, result ) result = aryfy(val[0], val[2]) result.line = @lexer.line result.file = @lexer.file @@ -782,80 +899,86 @@ module_eval <<'.,.,', 'grammar.ra', 82 end .,., - # reduce 20 omitted - # reduce 21 omitted # reduce 22 omitted -module_eval <<'.,.,', 'grammar.ra', 111 - def _reduce_23( val, _values, result ) + # reduce 23 omitted + +module_eval <<'.,.,', 'grammar.ra', 113 + def _reduce_24( val, _values, result ) if val[0].instance_of?(AST::ASTArray) - raise Puppet::ParseError, "Invalid name" + error "Invalid name" end array = val[2] - if array.instance_of?(AST::ObjectInst) + if array.instance_of?(AST::ResourceInst) array = [array] end result = ast AST::ASTArray - # this iterates across each specified objectinstance + # this iterates across each specified resourceinstance array.each { |instance| - unless instance.instance_of?(AST::ObjectInst) + unless instance.instance_of?(AST::ResourceInst) raise Puppet::Dev, "Got something that isn't an instance" end # now, i need to somehow differentiate between those things with # arrays in their names, and normal things - result.push ast(AST::ObjectDef, + result.push ast(AST::ResourceDef, :type => val[0], - :name => instance[0], + :title => instance[0], :params => instance[1]) } result end .,., -module_eval <<'.,.,', 'grammar.ra', 120 - def _reduce_24( val, _values, result ) - if val[0].instance_of?(AST::ASTArray) - Puppet.notice "invalid name" - raise Puppet::ParseError, "Invalid name" - end - # an object but without a name - # this cannot be an instance of a library type - result = ast AST::ObjectDef, :type => val[0], :params => val[2] +module_eval <<'.,.,', 'grammar.ra', 116 + def _reduce_25( val, _values, result ) + # This is a deprecated syntax. + error "All resource specifications require names" result end .,., -module_eval <<'.,.,', 'grammar.ra', 126 - def _reduce_25( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 122 + def _reduce_26( val, _values, result ) # a template setting for a type if val[0].instance_of?(AST::ASTArray) - raise Puppet::ParseError, "Invalid type" + error "Invalid type" end - result = ast(AST::TypeDefaults, :type => val[0], :params => val[2]) + result = ast(AST::ResourceDefaults, :type => val[0], :params => val[2]) result end .,., -module_eval <<'.,.,', 'grammar.ra', 149 - def _reduce_26( val, _values, result ) - unless Puppet[:storeconfigs] - raise Puppet::ParseError, "You cannot collect without storeconfigs being set" +module_eval <<'.,.,', 'grammar.ra', 127 + def _reduce_27( val, _values, result ) + result = ast AST::ResourceOverride, :object => val[0], :params => val[2] + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 154 + def _reduce_28( val, _values, result ) + type = val[0] + + if type == :exported and ! Puppet[:storeconfigs] + error "You cannot collect without storeconfigs being set" end - if val[1].is_a? AST::TypeDefaults - raise Puppet::ParseError, "Defaults are not collectable" + if val[1].is_a? AST::ResourceDefaults + error "Defaults are not virtualizable" end - # Just mark our objects as collectable and pass them through. + method = type.to_s + "=" + + # Just mark our resources as exported and pass them through. if val[1].instance_of?(AST::ASTArray) val[1].each do |obj| - obj.collectable = true + obj.send(method, true) end else - val[1].collectable = true + val[1].send(method, true) end result = val[1] @@ -863,28 +986,130 @@ module_eval <<'.,.,', 'grammar.ra', 149 end .,., -module_eval <<'.,.,', 'grammar.ra', 158 - def _reduce_27( val, _values, result ) - unless Puppet[:storeconfigs] - raise Puppet::ParseError, "You cannot collect without storeconfigs being set" +module_eval <<'.,.,', 'grammar.ra', 155 + def _reduce_29( val, _values, result ) + result = :virtual + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 156 + def _reduce_30( val, _values, result ) + result = :exported + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 179 + def _reduce_31( val, _values, result ) + if val[0] =~ /^[a-z]/ + Puppet.warning addcontext("Collection names must now be capitalized") + end + type = val[0].downcase + args = {:type => type} + + if val[1].is_a?(AST::CollExpr) + args[:query] = val[1] + args[:query].type = type + args[:form] = args[:query].form + else + args[:form] = val[1] + end + if args[:form] == :exported and ! Puppet[:storeconfigs] + error "You cannot collect exported resources without storeconfigs being set" end - result = ast AST::Collection, :type => val[0] + result = ast AST::Collection, args result end .,., -module_eval <<'.,.,', 'grammar.ra', 162 - def _reduce_28( val, _values, result ) - result = ast AST::ObjectInst, :children => [val[0],val[2]] + # reduce 32 omitted + + # reduce 33 omitted + +module_eval <<'.,.,', 'grammar.ra', 190 + def _reduce_34( val, _values, result ) + if val[1] + result = val[1] + result.form = :virtual + else + result = :virtual + end result end .,., - # reduce 29 omitted +module_eval <<'.,.,', 'grammar.ra', 198 + def _reduce_35( val, _values, result ) + if val[1] + result = val[1] + result.form = :exported + else + result = :exported + end + result + end +.,., -module_eval <<'.,.,', 'grammar.ra', 172 - def _reduce_30( val, _values, result ) - if val[0].instance_of?(AST::ObjectInst) + # reduce 36 omitted + + # reduce 37 omitted + +module_eval <<'.,.,', 'grammar.ra', 206 + def _reduce_38( val, _values, result ) + result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] + result + end +.,., + + # reduce 39 omitted + +module_eval <<'.,.,', 'grammar.ra', 212 + def _reduce_40( val, _values, result ) + result = val[1] + result.parens = true + result + end +.,., + + # reduce 41 omitted + + # reduce 42 omitted + +module_eval <<'.,.,', 'grammar.ra', 220 + def _reduce_43( val, _values, result ) + result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] + #result = ast AST::CollExpr + #result.push *val + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 225 + def _reduce_44( val, _values, result ) + result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] + #result = ast AST::CollExpr + #result.push *val + result + end +.,., + + # reduce 45 omitted + + # reduce 46 omitted + +module_eval <<'.,.,', 'grammar.ra', 232 + def _reduce_47( val, _values, result ) + result = ast AST::ResourceInst, :children => [val[0],val[2]] + result + end +.,., + + # reduce 48 omitted + +module_eval <<'.,.,', 'grammar.ra', 242 + def _reduce_49( val, _values, result ) + if val[0].instance_of?(AST::ResourceInst) result = ast AST::ASTArray, :children => [val[0],val[2]] else val[0].push val[2] @@ -894,61 +1119,61 @@ module_eval <<'.,.,', 'grammar.ra', 172 end .,., - # reduce 31 omitted + # reduce 50 omitted - # reduce 32 omitted + # reduce 51 omitted -module_eval <<'.,.,', 'grammar.ra', 179 - def _reduce_33( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 249 + def _reduce_52( val, _values, result ) result = ast AST::Name, :value => val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 183 - def _reduce_34( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 253 + def _reduce_53( val, _values, result ) result = ast AST::Type, :value => val[0] result end .,., - # reduce 35 omitted + # reduce 54 omitted - # reduce 36 omitted + # reduce 55 omitted - # reduce 37 omitted + # reduce 56 omitted - # reduce 38 omitted + # reduce 57 omitted - # reduce 39 omitted + # reduce 58 omitted - # reduce 40 omitted + # reduce 59 omitted -module_eval <<'.,.,', 'grammar.ra', 196 - def _reduce_41( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 266 + def _reduce_60( val, _values, result ) # this is distinct from referencing a variable - variable = ast AST::Name, :value => val[0].sub(/^\$/,'') + variable = ast AST::Name, :value => val[0] result = ast AST::VarDef, :name => variable, :value => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 201 - def _reduce_42( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 271 + def _reduce_61( val, _values, result ) result = ast AST::ASTArray result end .,., -module_eval <<'.,.,', 'grammar.ra', 201 - def _reduce_43( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 271 + def _reduce_62( val, _values, result ) result = val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 210 - def _reduce_44( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 280 + def _reduce_63( val, _values, result ) if val[0].instance_of?(AST::ASTArray) val[0].push(val[2]) result = val[0] @@ -959,18 +1184,17 @@ module_eval <<'.,.,', 'grammar.ra', 210 end .,., -module_eval <<'.,.,', 'grammar.ra', 215 - def _reduce_45( val, _values, result ) - leaf = ast AST::String, :value => val[0] - result = ast AST::ObjectParam, :param => leaf, :value => val[2] +module_eval <<'.,.,', 'grammar.ra', 284 + def _reduce_64( val, _values, result ) + result = ast AST::ResourceParam, :param => val[0], :value => val[2] result end .,., - # reduce 46 omitted + # reduce 65 omitted -module_eval <<'.,.,', 'grammar.ra', 224 - def _reduce_47( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 293 + def _reduce_66( val, _values, result ) if val[0].instance_of?(AST::ASTArray) result = val[0].push(val[2]) else @@ -980,26 +1204,38 @@ module_eval <<'.,.,', 'grammar.ra', 224 end .,., - # reduce 48 omitted + # reduce 67 omitted - # reduce 49 omitted + # reduce 68 omitted - # reduce 50 omitted + # reduce 69 omitted - # reduce 51 omitted + # reduce 70 omitted - # reduce 52 omitted + # reduce 71 omitted - # reduce 53 omitted + # reduce 72 omitted - # reduce 54 omitted + # reduce 73 omitted - # reduce 55 omitted + # reduce 74 omitted - # reduce 56 omitted + # reduce 75 omitted + + # reduce 76 omitted + + # reduce 77 omitted + + # reduce 78 omitted + + # reduce 79 omitted -module_eval <<'.,.,', 'grammar.ra', 243 - def _reduce_57( val, _values, result ) + # reduce 80 omitted + + # reduce 81 omitted + +module_eval <<'.,.,', 'grammar.ra', 319 + def _reduce_82( val, _values, result ) args = aryfy(val[2]) result = ast AST::Function, :name => val[0], @@ -1009,36 +1245,44 @@ module_eval <<'.,.,', 'grammar.ra', 243 end .,., -module_eval <<'.,.,', 'grammar.ra', 247 - def _reduce_58( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 323 + def _reduce_83( val, _values, result ) result = ast AST::String, :value => val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 249 - def _reduce_59( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 325 + def _reduce_84( val, _values, result ) result = ast AST::FlatString, :value => val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 253 - def _reduce_60( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 329 + def _reduce_85( val, _values, result ) result = ast AST::Boolean, :value => val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 257 - def _reduce_61( val, _values, result ) - result = ast AST::ObjectRef, :type => val[0], :name => val[2] +module_eval <<'.,.,', 'grammar.ra', 334 + def _reduce_86( val, _values, result ) + Puppet.warning addcontext("Deprecation notice: Resource references should now be capitalized") + result = ast AST::ResourceRef, :type => val[0], :title => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 270 - def _reduce_62( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 336 + def _reduce_87( val, _values, result ) + result = ast AST::ResourceRef, :type => val[0], :title => val[2] + result + end +.,., + +module_eval <<'.,.,', 'grammar.ra', 349 + def _reduce_88( val, _values, result ) args = { :test => val[1], :statements => val[3] @@ -1053,19 +1297,19 @@ module_eval <<'.,.,', 'grammar.ra', 270 end .,., - # reduce 63 omitted + # reduce 89 omitted -module_eval <<'.,.,', 'grammar.ra', 275 - def _reduce_64( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 354 + def _reduce_90( val, _values, result ) result = ast AST::Else, :statements => val[2] result end .,., - # reduce 65 omitted + # reduce 91 omitted -module_eval <<'.,.,', 'grammar.ra', 287 - def _reduce_66( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 366 + def _reduce_92( val, _values, result ) options = val[3] unless options.instance_of?(AST::ASTArray) options = ast AST::ASTArray, :children => [val[3]] @@ -1075,10 +1319,10 @@ module_eval <<'.,.,', 'grammar.ra', 287 end .,., - # reduce 67 omitted + # reduce 93 omitted -module_eval <<'.,.,', 'grammar.ra', 297 - def _reduce_68( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 376 + def _reduce_94( val, _values, result ) if val[0].instance_of?(AST::ASTArray) val[0].push val[1] result = val[0] @@ -1089,15 +1333,15 @@ module_eval <<'.,.,', 'grammar.ra', 297 end .,., -module_eval <<'.,.,', 'grammar.ra', 301 - def _reduce_69( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 380 + def _reduce_95( val, _values, result ) result = ast AST::CaseOpt, :value => val[0], :statements => val[3] result end .,., -module_eval <<'.,.,', 'grammar.ra', 306 - def _reduce_70( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 385 + def _reduce_96( val, _values, result ) result = ast(AST::CaseOpt, :value => val[0], :statements => ast(AST::ASTArray) @@ -1106,10 +1350,10 @@ module_eval <<'.,.,', 'grammar.ra', 306 end .,., - # reduce 71 omitted + # reduce 97 omitted -module_eval <<'.,.,', 'grammar.ra', 316 - def _reduce_72( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 395 + def _reduce_98( val, _values, result ) if val[0].instance_of?(AST::ASTArray) val[0].push(val[2]) result = val[0] @@ -1120,26 +1364,26 @@ module_eval <<'.,.,', 'grammar.ra', 316 end .,., -module_eval <<'.,.,', 'grammar.ra', 320 - def _reduce_73( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 399 + def _reduce_99( val, _values, result ) result = ast AST::Selector, :param => val[0], :values => val[2] result end .,., - # reduce 74 omitted + # reduce 100 omitted -module_eval <<'.,.,', 'grammar.ra', 322 - def _reduce_75( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 401 + def _reduce_101( val, _values, result ) result = val[1] result end .,., - # reduce 76 omitted + # reduce 102 omitted -module_eval <<'.,.,', 'grammar.ra', 333 - def _reduce_77( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 412 + def _reduce_103( val, _values, result ) if val[0].instance_of?(AST::ASTArray) val[0].push(val[2]) result = val[0] @@ -1150,34 +1394,34 @@ module_eval <<'.,.,', 'grammar.ra', 333 end .,., -module_eval <<'.,.,', 'grammar.ra', 337 - def _reduce_78( val, _values, result ) - result = ast AST::ObjectParam, :param => val[0], :value => val[2] +module_eval <<'.,.,', 'grammar.ra', 416 + def _reduce_104( val, _values, result ) + result = ast AST::ResourceParam, :param => val[0], :value => val[2] result end .,., - # reduce 79 omitted + # reduce 105 omitted - # reduce 80 omitted + # reduce 106 omitted - # reduce 81 omitted + # reduce 107 omitted - # reduce 82 omitted + # reduce 108 omitted - # reduce 83 omitted + # reduce 109 omitted - # reduce 84 omitted + # reduce 110 omitted -module_eval <<'.,.,', 'grammar.ra', 347 - def _reduce_85( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 426 + def _reduce_111( val, _values, result ) result = ast AST::Default, :value => val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 404 - def _reduce_86( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 488 + def _reduce_112( val, _values, result ) # importing files # yuk, i hate keywords # we'll probably have to have some kind of search path eventually @@ -1209,7 +1453,7 @@ module_eval <<'.,.,', 'grammar.ra', 404 end files.each { |file| - parser = Puppet::Parser::Parser.new() + parser = Puppet::Parser::Parser.new(interp) parser.files = self.files Puppet.debug("importing '%s'" % file) @@ -1227,8 +1471,13 @@ module_eval <<'.,.,', 'grammar.ra', 404 end # push the results into the main result array # We always return an array when we parse. - parser.parse.each do |child| - result.push child + ast = parser.parse + + # Things that just get added to the classtable or whatever return nil + if ast + ast.each do |child| + result.push child + end end } } @@ -1236,249 +1485,173 @@ module_eval <<'.,.,', 'grammar.ra', 404 end .,., -module_eval <<'.,.,', 'grammar.ra', 420 - def _reduce_87( val, _values, result ) - args = { - :type => ast(AST::Name, :value => val[1]), - :args => val[2], - :code => val[4] # Switch to 5 for parents - } +module_eval <<'.,.,', 'grammar.ra', 498 + def _reduce_113( val, _values, result ) + interp.newdefine fqname(val[1]), :arguments => val[2], :code => val[4] + @lexer.indefine = false + result = nil - if val[3].instance_of?(AST::Name) - args[:parentclass] = val[3] - end - result = ast AST::CompDef, args #} | DEFINE NAME argumentlist parent LBRACE RBRACE { result end .,., -module_eval <<'.,.,', 'grammar.ra', 432 - def _reduce_88( val, _values, result ) - args = { - :type => ast(AST::Name, :value => val[1]), - :args => val[2], - :code => ast(AST::ASTArray) - } - - if val[3].instance_of?(AST::Name) - args[:parentclass] = val[3] - end - - result = ast AST::CompDef, args +module_eval <<'.,.,', 'grammar.ra', 502 + def _reduce_114( val, _values, result ) + interp.newdefine fqname(val[1]), :arguments => val[2] + @lexer.indefine = false + result = nil result end .,., -module_eval <<'.,.,', 'grammar.ra', 446 - def _reduce_89( val, _values, result ) - #:args => val[2], - args = { - :type => ast(AST::Name, :value => val[1]), - :code => val[4] - } - # It'll be an ASTArray if we didn't get a parent - if val[2].instance_of?(AST::Name) - args[:parentclass] = val[2] - end - result = ast AST::ClassDef, args +module_eval <<'.,.,', 'grammar.ra', 510 + def _reduce_115( val, _values, result ) + # Our class gets defined in the parent namespace, not our own. + @lexer.namepop + interp.newclass fqname(val[1]), :code => val[4], :parent => val[2] + result = nil result end .,., -module_eval <<'.,.,', 'grammar.ra', 456 - def _reduce_90( val, _values, result ) - args = { - :type => ast(AST::Name, :value => val[1]), - :code => ast(AST::ASTArray, :children => []) - } - # It'll be an ASTArray if we didn't get a parent - if val[2].instance_of?(AST::Name) - args[:parentclass] = val[2] - end - result = ast AST::ClassDef, args +module_eval <<'.,.,', 'grammar.ra', 515 + def _reduce_116( val, _values, result ) + # Our class gets defined in the parent namespace, not our own. + @lexer.namepop + interp.newclass fqname(val[1]), :parent => val[2] + result = nil result end .,., -module_eval <<'.,.,', 'grammar.ra', 473 - def _reduce_91( val, _values, result ) - unless val[1].instance_of?(AST::ASTArray) - val[1] = ast AST::ASTArray, - :line => val[1].line, - :file => val[1].file, - :children => [val[1]] - end - args = { - :names => val[1], - :code => val[4] - } - if val[2].instance_of?(AST::Name) - args[:parentclass] = val[2] - end - result = ast AST::NodeDef, args +module_eval <<'.,.,', 'grammar.ra', 520 + def _reduce_117( val, _values, result ) + interp.newnode val[1], :parent => val[2], :code => val[4] + result = nil result end .,., -module_eval <<'.,.,', 'grammar.ra', 488 - def _reduce_92( val, _values, result ) - unless val[1].instance_of?(AST::ASTArray) - val[1] = ast AST::ASTArray, - :line => val[1].line, - :file => val[1].file, - :children => [val[1]] - end - args = { - :names => val[1], - :code => ast(AST::ASTArray, :children => []) - } - if val[2].instance_of?(AST::Name) - args[:parentclass] = val[2] - end - result = ast AST::NodeDef,args +module_eval <<'.,.,', 'grammar.ra', 523 + def _reduce_118( val, _values, result ) + interp.newnode val[1], :parent => val[2] + result = nil result end .,., - # reduce 93 omitted + # reduce 119 omitted -module_eval <<'.,.,', 'grammar.ra', 499 - def _reduce_94( val, _values, result ) - if val[0].instance_of?(AST::ASTArray) - result = val[0] - result.push val[2] - else - result = ast AST::ASTArray, :children => [val[0], val[2]] - end - result - end -.,., + # reduce 120 omitted -module_eval <<'.,.,', 'grammar.ra', 503 - def _reduce_95( val, _values, result ) - result = ast AST::HostName, :value => val[0] - result - end -.,., + # reduce 121 omitted -module_eval <<'.,.,', 'grammar.ra', 505 - def _reduce_96( val, _values, result ) - result = ast AST::HostName, :value => val[0] +module_eval <<'.,.,', 'grammar.ra', 535 + def _reduce_122( val, _values, result ) + result = val[0] + result = [result] unless result.is_a?(Array) + result << val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 507 - def _reduce_97( val, _values, result ) - result = ast AST::Default, :value => val[0] + # reduce 123 omitted + + # reduce 124 omitted + + # reduce 125 omitted + +module_eval <<'.,.,', 'grammar.ra', 543 + def _reduce_126( val, _values, result ) + result = nil result end .,., -module_eval <<'.,.,', 'grammar.ra', 511 - def _reduce_98( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 547 + def _reduce_127( val, _values, result ) result = ast AST::ASTArray, :children => [] result end .,., - # reduce 99 omitted + # reduce 128 omitted -module_eval <<'.,.,', 'grammar.ra', 516 - def _reduce_100( val, _values, result ) - result = val[1] +module_eval <<'.,.,', 'grammar.ra', 552 + def _reduce_129( val, _values, result ) + result = nil result end .,., -module_eval <<'.,.,', 'grammar.ra', 523 - def _reduce_101( val, _values, result ) - if val[1].instance_of?(AST::ASTArray) - result = val[1] - else - result = ast AST::ASTArray, :children => [val[1]] - end +module_eval <<'.,.,', 'grammar.ra', 556 + def _reduce_130( val, _values, result ) + result = val[1] + result = [result] unless result[0].is_a?(Array) result end .,., - # reduce 102 omitted + # reduce 131 omitted -module_eval <<'.,.,', 'grammar.ra', 533 - def _reduce_103( val, _values, result ) - if val[0].instance_of?(AST::ASTArray) - val[0].push(val[2]) - result = val[0] - else - result = ast AST::ASTArray, :children => [val[0],val[2]] - end +module_eval <<'.,.,', 'grammar.ra', 563 + def _reduce_132( val, _values, result ) + result = val[0] + result = [result] unless result[0].is_a?(Array) + result << val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 541 - def _reduce_104( val, _values, result ) - msg = "Deprecation notice: #{val[0].value} must now include '$' in prototype" - msg += " at line %s" % @lexer.line - msg += " in file %s" % @lexer.file if @lexer.file - Puppet.warning msg - result = ast AST::CompArgument, :children => [val[0],val[2]] +module_eval <<'.,.,', 'grammar.ra', 568 + def _reduce_133( val, _values, result ) + Puppet.warning addcontext("Deprecation notice: #{val[0].value} must now include '$' in prototype") + result = [val[0], val[2]] result end .,., -module_eval <<'.,.,', 'grammar.ra', 548 - def _reduce_105( val, _values, result ) - msg = "Deprecation notice: #{val[0].value} must now include '$' in prototype" - msg += " at line %s" % @lexer.line - msg += " in file %s" % @lexer.file if @lexer.file - Puppet.warning msg - result = ast AST::CompArgument, :children => [val[0]] +module_eval <<'.,.,', 'grammar.ra', 572 + def _reduce_134( val, _values, result ) + Puppet.warning addcontext("Deprecation notice: #{val[0].value} must now include '$' in prototype") + result = [val[0]] result end .,., -module_eval <<'.,.,', 'grammar.ra', 550 - def _reduce_106( val, _values, result ) - result = ast AST::CompArgument, :children => [val[0],val[2]] +module_eval <<'.,.,', 'grammar.ra', 574 + def _reduce_135( val, _values, result ) + result = [val[0], val[2]] result end .,., -module_eval <<'.,.,', 'grammar.ra', 552 - def _reduce_107( val, _values, result ) - result = ast AST::CompArgument, :children => [val[0]] +module_eval <<'.,.,', 'grammar.ra', 576 + def _reduce_136( val, _values, result ) + result = [val[0]] result end .,., - # reduce 108 omitted + # reduce 137 omitted -module_eval <<'.,.,', 'grammar.ra', 557 - def _reduce_109( val, _values, result ) - result = ast AST::Name, :value => val[1] +module_eval <<'.,.,', 'grammar.ra', 581 + def _reduce_138( val, _values, result ) + result = val[1] result end .,., -module_eval <<'.,.,', 'grammar.ra', 562 - def _reduce_110( val, _values, result ) - name = val[0].sub(/^\$/,'') - result = ast AST::Variable, :value => name +module_eval <<'.,.,', 'grammar.ra', 585 + def _reduce_139( val, _values, result ) + result = ast AST::Variable, :value => val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 567 - def _reduce_111( val, _values, result ) - result = ast AST::Name, :value => val[0].sub(/^\$/,'') - result - end -.,., - -module_eval <<'.,.,', 'grammar.ra', 575 - def _reduce_112( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 593 + def _reduce_140( val, _values, result ) if val[1].instance_of?(AST::ASTArray) result = val[1] else @@ -1488,21 +1661,21 @@ module_eval <<'.,.,', 'grammar.ra', 575 end .,., -module_eval <<'.,.,', 'grammar.ra', 577 - def _reduce_113( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 595 + def _reduce_141( val, _values, result ) result = ast AST::ASTArray result end .,., - # reduce 114 omitted + # reduce 142 omitted - # reduce 115 omitted + # reduce 143 omitted - # reduce 116 omitted + # reduce 144 omitted -module_eval <<'.,.,', 'grammar.ra', 582 - def _reduce_117( val, _values, result ) +module_eval <<'.,.,', 'grammar.ra', 600 + def _reduce_145( val, _values, result ) result = nil result end diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb new file mode 100644 index 000000000..5d09eaa9b --- /dev/null +++ b/lib/puppet/parser/resource.rb @@ -0,0 +1,324 @@ +# A resource that we're managing. This handles making sure that only subclasses +# can set parameters. +class Puppet::Parser::Resource + require 'puppet/parser/resource/param' + require 'puppet/parser/resource/reference' + ResParam = Struct.new :name, :value, :source, :line, :file + include Puppet::Util + include Puppet::Util::MethodHelper + include Puppet::Util::Errors + include Puppet::Util::Logging + + attr_accessor :source, :line, :file, :scope + attr_accessor :virtual, :override, :params, :translated + + attr_reader :exported + + attr_writer :tags + + # Proxy a few methods to our @ref object. + [:builtin?, :type, :title].each do |method| + define_method(method) do + @ref.send(method) + end + end + + # Set up some boolean test methods + [:exported, :translated, :override].each do |method| + newmeth = (method.to_s + "?").intern + define_method(newmeth) do + self.send(method) + end + end + + def [](param) + param = symbolize(param) + if param == :title + return self.title + end + if @params.has_key?(param) + @params[param].value + else + nil + end + end + + # Add default values from our definition. + def adddefaults + defaults = scope.lookupdefaults(self.type) + + defaults.each do |name, param| + unless @params.include?(param.name) + self.debug "Adding default for %s" % param.name + + @params[param.name] = param + end + end + end + + # Add any metaparams defined in our scope. This actually adds any metaparams + # from any parent scope, and there's currently no way to turn that off. + def addmetaparams + Puppet::Type.eachmetaparam do |name| + if val = scope.lookupvar(name.to_s, false) + unless val == :undefined + set Param.new(:name => name, :value => val, :source => scope.source) + end + end + end + end + + # Add any overrides for this object. + def addoverrides + overrides = scope.lookupoverrides(self) + + overrides.each do |over| + self.merge(over) + end + + overrides.clear + end + + def builtin=(bool) + @ref.builtin = bool + end + + # Retrieve the associated definition and evaluate it. + def evaluate + if builtin? + devfail "Cannot evaluate a builtin type" + end + + unless klass = scope.finddefine(self.type) + self.fail "Cannot find definition %s" % self.type + end + + finish() + + scope.deleteresource(self) + + return klass.evaluate(:scope => scope, + :type => self.type, + :name => self.title, + :arguments => self.to_hash, + :scope => self.scope, + :exported => self.exported + ) + ensure + @evaluated = true + end + + def exported=(value) + if value + @virtual = true + @exported = value + else + @exported = value + end + end + + def evaluated? + if defined? @evaluated and @evaluated + true + else + false + end + end + + # Do any finishing work on this object, called before evaluation or + # before storage/translation. + def finish + addoverrides() + adddefaults() + addmetaparams() + end + + def initialize(options) + options = symbolize_options(options) + + # Collect the options necessary to make the reference. + refopts = [:type, :title].inject({}) do |hash, param| + hash[param] = options[param] || + devfail("%s must be passed to Resources" % param) + options.delete(param) + hash + end + + @params = {} + tmpparams = nil + if tmpparams = options[:params] + options.delete(:params) + end + + # Now set the rest of the options. + set_options(options) + + @ref = Reference.new(refopts) + + requiredopts(:scope, :source) + + @ref.scope = self.scope + + if tmpparams + tmpparams.each do |param| + # We use the method here, because it does type-checking. + set(param) + end + end + end + + # Merge an override resource in. + def merge(resource) + # Some of these might fail, but they'll fail in the way we want. + resource.params.each do |name, param| + set(param) + end + end + + # Verify that all passed parameters are valid. This throws an error if there's + # a problem, so we don't have to worry about the return value. + def paramcheck(param) + # This defaults to true + unless Puppet[:paramcheck] + return + end + + return if param == "name" or param == "title" # always allow these + + # FIXME We might need to do more here eventually. Metaparams + # behave strangely on containers. + return if Puppet::Type.metaparam?(param) + + # Now make sure it's a valid argument to our class. + unless @ref.typeclass.validattr?(param) + self.fail Puppet::ParseError, "Invalid parameter '%s' for type '%s'" % + [param.inspect, @ref.type] + end + end + + # A temporary occasion, until I get paths in the scopes figured out. + def path + to_s + end + + # Return the short version of our name. + def ref + @ref.to_s + end + + # You have to pass a Resource::Param to this. + def set(param) + # Because definitions are now parse-time, I can paramcheck immediately. + paramcheck(param.name) + + if current = @params[param.name] + # XXX Should we ignore any settings that have the same values? + if param.source.child_of?(current.source) + # Replace it, keeping all of its info. + @params[param.name] = param + else + fail Puppet::ParseError, "Parameter %s is already set on %s by %s" % + [param.name, self.to_s, param.source] + end + else + if self.source == param.source or param.source.child_of?(self.source) + @params[param.name] = param + else + fail Puppet::ParseError, "Only subclasses can set parameters" + end + end + end + + # Store our object as a Rails object. We need the host object we're storing it + # with. + def store(host) + args = {} + %w{type title tags file line exported}.each do |param| + if value = self.send(param) + args[param] = value + end + end + + # 'type' isn't a valid column name, so we have to use something else. + args = symbolize_options(args) + args[:restype] = args[:type] + args.delete(:type) + + # Let's see if the object exists + #if obj = host.rails_resources.find_by_type_and_title(self.type, self.title) + if obj = host.rails_resources.find_by_restype_and_title(self.type, self.title) + # We exist + args.each do |param, value| + obj[param] = value + end + else + # Else create it anew + obj = host.rails_resources.build(args) + end + + # Either way, now add our parameters + @params.each do |name, param| + param.store(obj) + end + + return obj + end + + def tags + unless defined? @tags + @tags = scope.tags + @tags << self.type + end + @tags + end + + def to_hash + @params.inject({}) do |hash, ary| + param = ary[1] + hash[param.name] = param.value + hash + end + end + + def to_s + self.ref + end + + # Translate our object to a transportable object. + def to_trans + unless builtin? + devfail "Tried to translate a non-builtin resource" + end + + return nil if virtual? + + # Now convert to a transobject + obj = Puppet::TransObject.new(@ref.title, @ref.type) + to_hash.each do |p, v| + if v.is_a?(Reference) + v = v.to_ref + elsif v.is_a?(Array) + v = v.collect { |av| + if av.is_a?(Reference) + av = av.to_ref + end + av + } + end + obj[p.to_s] = v + end + + obj.file = self.file + obj.line = self.line + + obj.tags = self.tags + + return obj + end + + def virtual? + self.virtual + end +end + +# $Id$ diff --git a/lib/puppet/parser/resource/param.rb b/lib/puppet/parser/resource/param.rb new file mode 100644 index 000000000..259e935d1 --- /dev/null +++ b/lib/puppet/parser/resource/param.rb @@ -0,0 +1,44 @@ +# The parameters we stick in Resources. +class Puppet::Parser::Resource::Param + attr_accessor :name, :value, :source, :line, :file + include Puppet::Util::Errors + include Puppet::Util::MethodHelper + + def initialize(hash) + set_options(hash) + requiredopts(:name, :value, :source) + @name = @name.intern if @name.is_a?(String) + end + + def inspect + "#<#{self.class} @name => #{self.name}, @value => #{self.value}, @source => #{self.source.type}>" + end + + # Store this parameter in a Rails db. + def store(resource) + args = {} + [:name, :value, :line, :file].each do |var| + if val = self.send(var) + args[var] = val + end + end + args[:name] = args[:name].to_s + if obj = resource.rails_parameters.find_by_name(self.name) + # We exist + args.each do |p, v| + obj[p] = v + end + else + # Else create it anew + obj = resource.rails_parameters.build(args) + end + + return obj + end + + def to_s + "%s => %s" % [self.name, self.value] + end +end + +# $Id$ diff --git a/lib/puppet/parser/resource/reference.rb b/lib/puppet/parser/resource/reference.rb new file mode 100644 index 000000000..2210b71c2 --- /dev/null +++ b/lib/puppet/parser/resource/reference.rb @@ -0,0 +1,68 @@ +# A reference to a resource. Mostly just the type and title. +class Puppet::Parser::Resource::Reference + include Puppet::Util::MethodHelper + include Puppet::Util::Errors + + attr_accessor :type, :title, :builtin, :file, :line, :scope + + # Are we a builtin type? + def builtin? + unless defined? @builtin + if builtintype() + @builtin = true + else + @builtin = false + end + end + + self.builtin + end + + def builtintype + if t = Puppet::Type.type(self.type) and t.name != :component + t + else + nil + end + end + + # Return the defined type for our obj. + def definedtype + unless defined? @definedtype + if tmp = @scope.finddefine(self.type) + @definedtype = tmp + else + fail Puppet::ParseError, "Could not find definition %s" % self.type + end + end + + @definedtype + end + + def initialize(hash) + set_options(hash) + requiredopts(:type, :title) + end + + def to_ref + return [type.to_s,title.to_s] + end + + def to_s + "%s[%s]" % [type, title] + end + + def typeclass + unless defined? @typeclass + if tmp = builtintype || definedtype + @typeclass = tmp + else + fail Puppet::ParseError, "Could not find type %s" % self.type + end + end + + @typeclass + end +end + +# $Id$ diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index a26ec938d..9b59ebd64 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -2,1191 +2,645 @@ # such. require 'puppet/parser/parser' +require 'puppet/parser/templatewrapper' require 'puppet/transportable' -module Puppet::Parser - class Scope - class ScopeObj < Hash - attr_accessor :file, :line, :type, :name - end - - # A simple wrapper for templates, so they don't have full access to - # the scope objects. - class TemplateWrapper - attr_accessor :scope, :file - include Puppet::Util - Puppet::Util.logmethods(self) - - def initialize(scope, file) - @scope = scope - if file =~ /^#{File::SEPARATOR}/ - @file = file - else - @file = File.join(Puppet[:templatedir], file) - end +class Puppet::Parser::Scope + require 'puppet/parser/resource' - unless FileTest.exists?(@file) - raise Puppet::ParseError, - "Could not find template %s" % file - end + AST = Puppet::Parser::AST - # We'll only ever not have an interpreter in testing, but, eh. - if @scope.interp - @scope.interp.newfile(@file) - end - end + # This doesn't actually work right now. + Puppet.config.setdefaults(:puppet, + :lexical => [false, "Whether to use lexical scoping (vs. dynamic)."], + :templatedir => ["$vardir/templates", + "Where Puppet looks for template files." + ] + ) - # Ruby treats variables like methods, so we can cheat here and - # trap missing vars like they were missing methods. - def method_missing(name, *args) - # We have to tell lookupvar to return :undefined to us when - # appropriate; otherwise it converts to "". - value = @scope.lookupvar(name.to_s, false) - if value != :undefined - return value - else - # Just throw an error immediately, instead of searching for - # other missingmethod things or whatever. - raise Puppet::ParseError, - "Could not find value for '%s'" % name - end - end - - def result - result = nil - benchmark(:debug, "Interpolated template #{@file}") do - template = ERB.new(File.read(@file), 0, "-") - result = template.result(binding) - end - - result - end - - def to_s - "template[%s]" % @file - end - end + Puppet::Util.logmethods(self) - # This doesn't actually work right now. - Puppet.config.setdefaults(:puppet, - :lexical => [false, "Whether to use lexical scoping (vs. dynamic)."], - :templatedir => ["$vardir/templates", - "Where Puppet looks for template files." - ] - ) + include Enumerable + include Puppet::Util::Errors + attr_accessor :parent, :level, :interp, :source, :host + attr_accessor :name, :type, :topscope, :base, :keyword, :namespace + attr_accessor :top, :context, :translated, :exported - Puppet::Util.logmethods(self) + # Whether we behave declaratively. Note that it's a class variable, + # so all scopes behave the same. + @@declarative = true - include Enumerable - attr_accessor :parent, :level, :interp - attr_accessor :name, :type, :topscope, :base, :keyword + # Retrieve and set the declarative setting. + def self.declarative + return @@declarative + end - attr_accessor :top, :context, :translated, :collectable + def self.declarative=(val) + @@declarative = val + end - # 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, :exportable + # This handles the shared tables that all scopes have. They're effectively + # global tables, except that they're only global for a single scope tree, + # which is why I can't use class variables for them. + def self.sharedtable(*names) + attr_accessor(*names) + @@sharedtables ||= [] + @@sharedtables += names + end - # Whether we behave declaratively. Note that it's a class variable, - # so all scopes behave the same. - @@declarative = true + # This is probably not all that good of an idea, but... + # This way a parent can share its tables with all of its children. + sharedtable :classtable, :definedtable, :exportable, :overridetable, :collecttable - # Retrieve and set the declarative setting. - def self.declarative - return @@declarative + # Is the value true? This allows us to control the definition of truth + # in one place. + def self.true?(value) + if value == false or value == "" + return false + else + return true end + end - def self.declarative=(val) - @@declarative = val + # Is the type a builtin type? + def builtintype?(type) + if typeklass = Puppet::Type.type(type) + return typeklass + else + return false end + end - # Is the value true? This allows us to control the definition of truth - # in one place. - def self.true?(value) - if value == false or value == "" - return false - else - return true - end + # Create a new child scope. + def child=(scope) + @children.push(scope) + + # Copy all of the shared tables over to the child. + @@sharedtables.each do |name| + scope.send(name.to_s + "=", self.send(name)) end + end - # Add all of the defaults for a given object to that object. - def adddefaults(obj) - defaults = lookupdefaults(obj.type) + # Verify that the given object isn't defined elsewhere. + def chkobjectclosure(obj) + if @definedtable.include?(obj.ref) + typeklass = Puppet::Type.type(obj.type) + if typeklass and ! typeklass.isomorphic? + Puppet.info "Allowing duplicate %s" % type + else + exobj = @definedtable[obj.ref] - defaults.each do |var, value| - unless obj[var] - self.debug "Adding default %s for %s" % - [var, obj.type] + # Either it's a defined type, which are never + # isomorphic, or it's a non-isomorphic type. + msg = "Duplicate definition: %s is already defined" % obj.ref - obj[var] = value + if exobj.file and exobj.line + msg << " in file %s at line %s" % + [exobj.file, exobj.line] end - end - 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] - - 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 + if obj.line or obj.file + msg << "; cannot redefine" end - } - end - # Is the type a builtin type? - def builtintype?(type) - if typeklass = Puppet::Type.type(type) - return typeklass - else - return false + raise Puppet::ParseError.new(msg) end end - # 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 - else - exobj = @definedtable[type][name] - - # 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] - - if exobj.file and exobj.line - msg << " in file %s at line %s" % - [exobj.file, exobj.line] - end - - if hash[:line] or hash[:file] - msg << "; cannot redefine" - end - - error = Puppet::ParseError.new(msg) - raise error - end - end - - return true - end - - def declarative=(val) - self.class.declarative = val - end - - def declarative - self.class.declarative - 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("' '")] - } - } - end + return true + end - # Create a new child scope. - def child=(scope) - @children.push(scope) + # Return the list of collections. + def collections + @collecttable + end - if defined? @nodetable - scope.nodetable = @nodetable - else - raise Puppet::DevError, "No nodetable has been defined" - end + def declarative=(val) + self.class.declarative = val + end - if defined? @classtable - scope.classtable = @classtable - else - raise Puppet::DevError, "No classtable has been defined" - end + def declarative + self.class.declarative + end - if defined? @exportable - scope.exportable = @exportable - else - raise Puppet::DevError, "No exportable has been defined" - 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 - if defined? @definedtable - scope.definedtable = @definedtable - else - raise Puppet::DevError, "No definedtable has been defined" - end - end + # Remove a specific child. + def delete(child) + @children.delete(child) + 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 + # Remove a resource from the various tables. This is only used when + # a resource maps to a definition and gets evaluated. + def deleteresource(resource) + if @definedtable[resource.ref] + @definedtable.delete(resource.ref) end - # Remove a specific child. - def delete(child) - @children.delete(child) + if @children.include?(resource) + @children.delete(resource) end + end - # Are we the top scope? - def topscope? - @level == 1 - end + # Are we the top scope? + def topscope? + @level == 1 + end - # 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.collect { |id, klass| - # The class table can contain scopes or strings as its values - # so support them accordingly. - if klass.is_a? Scope - klass.type - else - klass - end - } + # 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.keys.reject { |k| k == "" } + end - # Yield each child scope in turn - def each - @children.each { |child| - yield child - } - end + # Yield each child scope in turn + def each + @children.each { |child| + yield child + } + end - # Evaluate a list of classes. - def evalclasses(classes) - return unless classes - classes.each do |klass| - if code = lookuptype(klass) - # Just reuse the 'include' function, since that's the equivalent - # of what we're doing here. - function_include(klass) - end + # Evaluate a list of classes. + def evalclasses(*classes) + retval = [] + classes.each do |klass| + if obj = findclass(klass) + obj.safeevaluate :scope => self + retval << klass end end + 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(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] - - # Always add "default" to our name list, so we're always searching - # for a default node. - names << "default" - - 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] - - if node == "default" - Puppet.info "Using default node" - end - 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 + def exported? + self.exported + 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. + def findclass(name) + interp.findclass(namespace, name) + end - # 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) + def finddefine(name) + interp.finddefine(namespace, name) + end - scope.evalclasses(classes) + def findresource(string, name = nil) + if name + string = "%s[%s]" % [string, name] end - # The top-level evaluate, used to evaluate a whole AST tree. This is - # a strange method, in that it turns around and calls evaluate() on its - # :ast argument. - def evaluate(hash) - objects = hash[:ast] - facts = hash[:facts] || {} - - @@done = [] - - 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. - result = objects.safeevaluate(:scope => self) - - # If they've provided a name or a parent, we assume they're looking - # for nodes. - if hash[:searched] - # 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 - end - - bucket = self.to_trans - - # Add our class list - unless self.classlist.empty? - bucket.classes = self.classlist - end - - # Now clean up after ourselves - [@@done].each do |table| - table.clear - end + @definedtable[string] + end - return bucket + # Recursively complete the whole tree, in preparation for + # translation or storage. + def finish + self.each do |obj| + obj.finish end + end - # Return the hash of objects that we specifically exported. We return - # a hash to make it easy for the caller to deduplicate based on name. - def exported(type) - if @exportable.include?(type) - return @exportable[type].dup + # 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 - return {} - end - end - - # Store our object in the central export table. - def exportobject(obj) - if @exportable.include?(obj.type) and - @exportable[obj.type].include?(obj.name) - raise Puppet::ParseError, "Object %s[%s] is already exported" % - [obj.type, obj.name] + raise Puppet::DevError, "Invalid scope argument %s" % name end + } - debug "Exporting %s[%s]" % [obj.type, obj.name] + @tags = [] - @exportable[obj.type][obj.name] = obj + if @parent.nil? + unless hash.include?(:declarative) + hash[:declarative] = true + end + self.istop(hash[:declarative]) + @inside = nil + else + # This is here, rather than in newchild(), so that all + # of the later variable initialization works. + @parent.child = self - return obj + @level = @parent.level + 1 + @interp = @parent.interp + @source = hash[:source] || @parent.source + @topscope = @parent.topscope + @context = @parent.context + @inside = @parent.inside + @host = @parent.host + @type ||= @parent.type 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. 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[:parentnode] - name = names.shift - arghash = { - :type => name, - :code => AST::ASTArray.new(:pin => "[]") - } + # Our child scopes and objects + @children = [] - #Puppet.notice "hash is %s" % - # hash.inspect - #Puppet.notice "Classes are %s, parent is %s" % - # [classes.inspect, parent.inspect] + # The symbol table for this scope + @symtable = Hash.new(nil) - if parent - arghash[:parentclass] = parent - end + # 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) + } - # Create the node - node = AST::Node.new(arghash) - node.keyword = "node" + # Map the names to the tables. + @map = { + "variable" => @symtable, + "defaults" => @defaultstable + } - # 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) + unless @interp + raise Puppet::DevError, "Scopes require an interpreter" end + 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 - } - - @tags = [] - - if @parent.nil? - unless hash.include?(:declarative) - hash[:declarative] = true - end - self.istop(hash[:declarative]) - @inside = nil - else - # This is here, rather than in newchild(), so that all - # of the later variable initialization works. - @parent.child = self - - @level = @parent.level + 1 - @interp = @parent.interp - @topscope = @parent.topscope - @context = @parent.context - @inside = @parent.inside - end - - # Our child scopes and objects - @children = [] - - # The symbol table for this scope - @symtable = Hash.new(nil) + # Associate the object directly with the scope, so that contained objects + # can look up what container they're running within. + def inside(arg = nil) + return @inside unless arg + + old = @inside + @inside = arg + yield + ensure + #Puppet.warning "exiting %s" % @inside.name + @inside = old + end - # The type table for this scope - @typetable = 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 - # 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 table for storing class singletons. This will only actually + # be used by top scopes and node scopes. + @classtable = 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) - } + self.class.declarative = declarative - # 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) - } + # The table for all defined objects. + @definedtable = {} - # Map the names to the tables. - @map = { - "variable" => @symtable, - "type" => @typetable, - "node" => @nodetable, - "object" => @objectable, - "defaults" => @defaultstable - } - end + # The list of objects that will available for export. + @exportable = {} - # Associate the object directly with the scope, so that contained objects - # can look up what container they're running within. - def inside(arg = nil) - return @inside unless arg - - old = @inside - @inside = arg - yield - ensure - #Puppet.warning "exiting %s" % @inside.name - @inside = old + # The list of overrides. This is used to cache overrides on objects + # that don't exist yet. We store an array of each override. + @overridetable = Hash.new do |overs, ref| + overs[ref] = [] 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) + # 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 - self.class.declarative = declarative + @namespace = "" - # The table for all defined objects. - @definedtable = Hash.new { |types, type| - types[type] = {} - } - - # A table for storing nodes. - @nodetable = Hash.new(nil) + # The list of collections that have been created. This is a global list, + # but they each refer back to the scope that created them. + @collecttable = [] - # The list of objects that will available for export. - @exportable = Hash.new { |types, type| - types[type] = {} - } + @context = nil + @topscope = self + @type = "puppet" + @name = "top" + end - # 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] = [] - } + # 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 } - - @context = nil - @topscope = self - @type = "puppet" - @name = "top" - 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] 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 - end - - # Look up all of the exported objects of a given type. Just like - # lookupobject, this only searches up through parent classes, not - # the whole scope tree. - def lookupexported(type) - found = [] - sub = proc { |table| - # We always return nil so that it will search all the way - # up the scope tree. - if table.has_key?(type) - table[type].each do |name, obj| - found << obj - end - nil - else - info table.keys.inspect - nil - 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 } - - value = lookup("object",sub, false) - - return found end - # 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 + #Puppet.debug "Got defaults for %s: %s" % + # [type,values.inspect] + return values + 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 + # Look up all of the exported objects of a given type. Just like + # lookupobject, this only searches up through parent classes, not + # the whole scope tree. + def lookupexported(type) + @definedtable.find_all do |name, r| + r.type == type and r.exported? end + end - # 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 = lookup("object",sub, true) - if value == :undefined - return nil - else - return value - end - end + def lookupoverrides(obj) + @overridetable[obj.ref] + end - # Look up a variable. The simplest value search we do. Default to returning - # an empty string for missing values, but support returning a constant. - def lookupvar(name, usestring = true) - value = lookup("variable", name) - if value == :undefined - if usestring - return "" - else - return :undefined - end + # Look up a defined type. + def lookuptype(name) + finddefine(name) || findclass(name) + end + + # Look up a variable. The simplest value search we do. Default to returning + # an empty string for missing values, but support returning a constant. + def lookupvar(name, usestring = true) + value = lookup("variable", name) + if value == :undefined + if usestring + return "" else - return value + return :undefined end + else + return value end + end - def newcollection(coll) - @children << coll - 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) - - obj = nil - - obj = Puppet::TransObject.new(hash[:name], hash[:type]) - - @children << obj + # Add a collection to the global list. + def newcollection(coll) + @collecttable << coll + end - @objectable[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 - @definedtable[hash[:type]][hash[:name]] = obj + # Return the list of remaining overrides. + def overrides + @overridetable.collect { |name, overs| overs }.flatten + end - return obj - end + def resources + @definedtable.values + end - # 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) + def setclass?(obj) + if obj.respond_to?(:fqname) + @classtable.has_key?(obj.fqname) + else + @classtable[obj] end + 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] + # 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(obj) + if obj.is_a?(AST::HostClass) + unless obj.fqname + raise Puppet::DevError, "Got a %s with no fully qualified name" % + obj.class + end + @classtable[obj.fqname] = obj + else + raise Puppet::DevError, "Invalid class %s" % obj.inspect end + end - # 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) - unless name =~ /^[a-z][\w-]*$/ - raise Puppet::ParseError, "Invalid class name '%s'" % name - end + # Set all of our facts in the top-level scope. + def setfacts(facts) + facts.each { |var, value| + self.setvar(var, value) + } + end - @classtable[id] = name - end + # Add a new object to our object table and the global list, and do any necessary + # checks. + def setresource(obj) + self.chkobjectclosure(obj) - # Store the scope for each class, so that other subclasses can look - # them up. - def setscope(id, scope) - @classtable[id] = scope - end + @children << obj - # Set defaults for a type. The typename should already be downcased, - # so that the syntax is isolated. - def setdefaults(type,params) - table = @defaultstable[type] + # The global table + @definedtable[obj.ref] = obj - # if we got a single param, it'll be in its own array - unless params[0].is_a?(Array) - params = [params] - end + return obj + end - 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] - } + # Override a parameter in an existing object. If the object does not yet + # exist, then cache the override in a global table, so it can be flushed + # at the end. + def setoverride(resource) + resource.override = true + if obj = @definedtable[resource.ref] + obj.merge(resource) + else + @overridetable[resource.ref] << resource end + 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 + # Set defaults for a type. The typename should already be downcased, + # so that the syntax is isolated. We don't do any kind of type-checking + # here; instead we let the resource do it when the defaults are used. + def setdefaults(type, params) + table = @defaultstable[type] + + # if we got a single param, it'll be in its own array + params = [params] unless params.is_a?(Array) + + params.each { |param| + #Puppet.debug "Default for %s is %s => %s" % + # [type,ary[0].inspect,ary[1].inspect] + if @@declarative + if table.include?(param.name) + self.fail "Default already defined for %s { %s }" % + [type,param.name] + end else - #Puppet.warning "Setting node %s at level %s" % [name, @level] - - # 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. - code.scope = self - @nodetable[name] = { - :scope => self, - :node => code - } + if table.include?(param.name) + # we should maybe allow this warning to be turned off... + Puppet.warning "Replacing default for %s { %s }" % + [type,param.name] + end end - end + table[param.name] = param + } + end - # Define our type. - def settype(name,ltype) - unless name - raise Puppet::DevError, "Got told to set type with a nil type" - end - unless ltype - raise Puppet::DevError, "Got told to set type with a nil object" - end - # Don't let them redefine the class in this scope. - if @typetable.include?(name) - raise Puppet::ParseError, - "%s is already defined" % name - else - ltype.scope = self - @typetable[name] = ltype + # 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 - # 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] - - collectable = hash[:collectable] || self.collectable - - # 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 - } + # Return an interpolated string. + def strinterp(string) + newstring = string.gsub(/\\\$|\$\{(\w+)\}|\$(\w+)/) do |value| + # If it matches the backslash, then just retun the dollar sign. + if value == '\\$' + '$' + else # look the variable up + var = $1 || $2 + lookupvar($1 || $2) end + end - # First look for it in a parent scope - obj = lookupobject(:name => name, :type => type) - - if obj - unless collectable == obj.collectable - msg = nil - if collectable - msg = "Exported %s[%s] cannot override local objects" - [type, name] - else - msg = "Local %s[%s] cannot override exported objects" - [type, name] - end - - error = Puppet::ParseError.new(msg) - error.line = line - error.file = file - raise error - end - end + return newstring.gsub(/\\t/, "\t").gsub(/\\n/, "\n").gsub(/\\s/, "\s") + end - unless obj and obj != :undefined - unless obj = @objectable[type][name] - obj = self.newobject( - :type => type, - :name => name, - :line => line, - :file => file - ) - - obj.collectable = collectable - - # 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 + # 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| + unless tag =~ /^\w[-\w]+$/ + fail Puppet::ParseError, "Invalid tag %s" % tag end - - # 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| - # Add it to our found object - obj[var] = value - } - - # This is only used for override verification -- the local object - # table does not have transobjects or whatever in it, it just has - # simple hashes. This is necessary because setobject can modify - # our object table or a parent class's object table, and we - # still need to make sure param settings cannot be duplicated - # within our scope. - @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 + if tag.nil? or tag == "" + puts caller + Puppet.debug "got told to tag with %s" % tag.inspect + next end - end - - # Return an interpolated string. - def strinterp(string) - newstring = string.gsub(/\\\$|\$\{(\w+)\}|\$(\w+)/) do |value| - # If it matches the backslash, then just retun the dollar sign. - if value == '\\$' - '$' - else # look the variable up - var = $1 || $2 - lookupvar($1 || $2) - end + tag = tag.to_s + unless @tags.include?(tag) + #Puppet.info "Tagging scope %s with %s" % [self.object_id, tag] + @tags << tag end + } + end - 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| + # Return the tags associated with this scope. It's basically + # just our parents' tags, plus our type. We don't cache this value + # because our parent tags might change between calls. + def tags + tmp = [] + @tags + unless ! defined? @type or @type.nil? or @type == "" + tmp << @type.to_s + end + if @parent + #info "Looking for tags in %s" % @parent.type + @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.sort.uniq + 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 - #info "Looking for tags in %s" % @parent.type - @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 self.name + return "%s[%s]" % [@type, @name] + else + return self.type.to_s end + end - # Used mainly for logging - def to_s - if @name - return "%s[%s]" % [@type, @name] + # Convert all of our objects as necessary. + def translate + ret = @children.collect do |child| + case child + when self.class + child.translate + when Puppet::Parser::Resource + child.to_trans else - return @type.to_s + devfail "Got %s for translation" % child.class end + end.reject { |o| o.nil? } + bucket = Puppet::TransBucket.new ret + unless self.type + devfail "A Scope with no type" end + if @type == "" + bucket.type = "main" + else + bucket.type = @type + end + if self.name + bucket.name = self.name + end + return bucket + end - # Convert our scope to a TransBucket. Everything in our @localobjecttable - # gets converted to either an evaluated definition, or a TransObject - def to_trans - results = [] - - # Set this on entry, just in case someone tries to get all weird - @translated = true - - @children.dup.each do |child| - if @@done.include?(child) - raise Puppet::DevError, "Already translated %s" % - child.object_id - else - @@done << child - end - #warning "Working on %s of type %s with id %s" % - # [child.type, child.class, child.object_id] - - # If it's a scope, then it can only be a subclass's scope, so - # convert it to a transbucket and store it in our results list - result = nil - case child - when Scope - result = child.to_trans - when Puppet::TransObject - # These objects can map to defined types or builtin types. - # Builtin types should be passed out as they are, but defined - # types need to be evaluated. We have to wait until this - # point so that subclass overrides can happen. - - # 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) - - # Now that all that is done, check to see what kind of object - # it is. - if objecttype = lookuptype(child.type) - # It's a defined type, so evaluate it. Retain whether - # the object is collectable. If the object is collectable, - # then it will store all of its contents into the - # @exportable table, rather than returning them. - result = objecttype.safeevaluate( - :name => child.name, - :type => child.type, - :arguments => child.to_hash, - :scope => self, - :collectable => child.collectable - ) - else - # If it's collectable, then store it. It will be - # stripped out in the interpreter using the collectstrip - # method. If we don't do this, then these objects - # don't get stored in the DB. - if child.collectable - exportobject(child) - end - result = child - end - # This is pretty hackish, but the collection has to actually - # be performed after all of the classes and definitions are - # evaluated, otherwise we won't catch objects that are exported - # in them. I think this will still be pretty limited in some - # cases, especially those where you are both exporting and - # collecting, but it's the best I can do for now. - when Puppet::Parser::AST::Collection - child.perform(self).each do |obj| - results << obj - end - else - raise Puppet::DevError, - "Puppet::Parse::Scope cannot handle objects of type %s" % - child.class - end - - # Skip nil objects or empty transbuckets - if result - unless result.is_a? Puppet::TransBucket and result.empty? - results << result - end - end - end - - # Get rid of any nil objects. - results.reject! { |child| - child.nil? - } + # Undefine a variable; only used for testing. + def unsetvar(var) + if @symtable.include?(var) + @symtable.delete(var) + end + 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 + # Return an array of all of the unevaluated objects + def unevaluated + ary = @definedtable.find_all do |name, object| + ! object.builtin? and ! object.evaluated? + end.collect { |name, object| object } - if defined? @name and @name - bucket.name = @name - end - # it'd be nice not to have to do this... - results.each { |result| - #Puppet.warning "Result type is %s" % result.class - bucket.push(result) - } - bucket.type = @type - - 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 - else - Puppet.debug "typeless scope; just returning a list" - return results - end + if ary.empty? + return nil + else + return ary end + end - # Undefine a variable; only used for testing. - def unsetvar(var) - if @symtable.include?(var) - @symtable.delete(var) - end - end + protected - 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 + # 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? + self.fail "Could not retrieve %s table at level %s" % + [type,self.level] + end - if sub.is_a?(Proc) and obj = sub.call(table) - return obj - elsif table.include?(sub) - return table[sub] - elsif ! @parent.nil? - # Context is used for retricting overrides. - if usecontext and self.context != @parent.context - return :undefined - else - return @parent.lookup(type,sub, usecontext) - end - else + if sub.is_a?(Proc) and obj = sub.call(table) + return obj + elsif table.include?(sub) + return table[sub] + elsif ! @parent.nil? + # Context is used for retricting overrides. + if usecontext and self.context != @parent.context return :undefined + else + return @parent.lookup(type,sub, usecontext) end + else + return :undefined end end end diff --git a/lib/puppet/parser/templatewrapper.rb b/lib/puppet/parser/templatewrapper.rb new file mode 100644 index 000000000..62b45852b --- /dev/null +++ b/lib/puppet/parser/templatewrapper.rb @@ -0,0 +1,58 @@ +# A simple wrapper for templates, so they don't have full access to +# the scope objects. +class Puppet::Parser::TemplateWrapper + attr_accessor :scope, :file + include Puppet::Util + Puppet::Util.logmethods(self) + + def initialize(scope, file) + @scope = scope + if file =~ /^#{File::SEPARATOR}/ + @file = file + else + @file = File.join(Puppet[:templatedir], file) + end + + unless FileTest.exists?(@file) + raise Puppet::ParseError, + "Could not find template %s" % file + end + + # We'll only ever not have an interpreter in testing, but, eh. + if @scope.interp + @scope.interp.newfile(@file) + end + end + + # Ruby treats variables like methods, so we can cheat here and + # trap missing vars like they were missing methods. + def method_missing(name, *args) + # We have to tell lookupvar to return :undefined to us when + # appropriate; otherwise it converts to "". + value = @scope.lookupvar(name.to_s, false) + if value != :undefined + return value + else + # Just throw an error immediately, instead of searching for + # other missingmethod things or whatever. + raise Puppet::ParseError, + "Could not find value for '%s'" % name + end + end + + def result + result = nil + benchmark(:debug, "Interpolated template #{@file}") do + template = ERB.new(File.read(@file), 0, "-") + result = template.result(binding) + end + + result + end + + def to_s + "template[%s]" % @file + end +end + +# $Id$ diff --git a/lib/puppet/rails/database.rb b/lib/puppet/rails/database.rb index caf87cbb8..8be05bd88 100644 --- a/lib/puppet/rails/database.rb +++ b/lib/puppet/rails/database.rb @@ -6,20 +6,23 @@ class Puppet::Rails::Database < ActiveRecord::Migration ActiveRecord::Migration.verbose = false end - create_table :rails_objects do |table| - table.column :name, :string, :null => false - table.column :ptype, :string, :null => false + # 'type' cannot be a column name, apparently + create_table :rails_resources do |table| + table.column :title, :string, :null => false + table.column :restype, :string, :null => false table.column :tags, :string table.column :file, :string table.column :line, :integer table.column :host_id, :integer - table.column :collectable, :boolean + table.column :exported, :boolean end create_table :rails_parameters do |table| table.column :name, :string, :null => false table.column :value, :string, :null => false - table.column :rails_object_id, :integer + table.column :file, :string + table.column :line, :integer + table.column :rails_resource_id, :integer end create_table :hosts do |table| @@ -33,8 +36,10 @@ class Puppet::Rails::Database < ActiveRecord::Migration end def self.down - drop_table :rails_objects + drop_table :rails_resources drop_table :rails_parameters drop_table :hosts end end + +# $Id$ diff --git a/lib/puppet/rails/host.rb b/lib/puppet/rails/host.rb index 77123c871..ccda1af64 100644 --- a/lib/puppet/rails/host.rb +++ b/lib/puppet/rails/host.rb @@ -1,11 +1,12 @@ -require 'puppet/rails/rails_object' +require 'puppet/rails/rails_resource' #RailsObject = Puppet::Rails::RailsObject class Puppet::Rails::Host < ActiveRecord::Base serialize :facts, Hash serialize :classes, Array - has_many :rails_objects, :dependent => :delete_all + has_many :rails_resources, :dependent => :delete_all, + :include => :rails_parameters # If the host already exists, get rid of its objects def self.clean(host) @@ -19,40 +20,43 @@ class Puppet::Rails::Host < ActiveRecord::Base # Store our host in the database. def self.store(hash) - name = hash[:host] || "localhost" - ip = hash[:ip] || "127.0.0.1" - facts = hash[:facts] || {} - objects = hash[:objects] + unless hash[:name] + raise ArgumentError, "You must specify the hostname for storage" + end - unless objects - raise ArgumentError, "You must pass objects" + args = {} + [:name, :facts, :classes].each do |param| + if hash[param] + args[param] = hash[param] + end end - hostargs = { - :name => name, - :ip => ip, - :facts => facts, - :classes => objects.classes - } + if hash[:facts].include?("ipaddress") + args[:ip] = hash[:facts]["ipaddress"] + end - objects = objects.flatten + unless hash[:resources] + raise ArgumentError, "You must pass resources" + end - host = nil - if host = clean(name) - [:name, :facts, :classes].each do |param| - unless host[param] == hostargs[param] - host[param] = hostargs[param] + if host = self.find_by_name(hash[:name]) + args.each do |param, value| + unless host[param] == args[param] + host[param] = args[param] end end - host.addobjects(objects) else - host = self.new(hostargs) do |hostobj| - hostobj.addobjects(objects) - end + # Create it anew + host = self.new(args) end + hash[:resources].each do |res| + res.store(host) + end - host.save + Puppet::Util.benchmark(:info, "Saved host to database") do + host.save + end return host end diff --git a/lib/puppet/rails/rails_object.rb b/lib/puppet/rails/rails_object.rb deleted file mode 100644 index d1e58e453..000000000 --- a/lib/puppet/rails/rails_object.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'puppet' -require 'puppet/rails/rails_parameter' - -#RailsParameter = Puppet::Rails::RailsParameter -class Puppet::Rails::RailsObject < ActiveRecord::Base - has_many :rails_parameters, :dependent => :delete_all - serialize :tags, Array - - belongs_to :host - - # Add a set of parameters. - def addparams(params) - params.each do |pname, pvalue| - rails_parameters.build( - :name => pname, - :value => pvalue - ) - - #self.rails_parameters << pobj - end - end - - # Convert our object to a trans_object. Do not retain whether the object - # is collectable, though, since that would cause it to get stripped - # from the configuration. - def to_trans - obj = Puppet::TransObject.new(name(), ptype()) - - [:file, :line, :tags].each do |method| - if val = send(method) - obj.send(method.to_s + "=", val) - end - end - rails_parameters.each do |param| - obj[param.name] = param.value - end - - return obj - end -end - -# $Id$ diff --git a/lib/puppet/rails/rails_parameter.rb b/lib/puppet/rails/rails_parameter.rb index a1966e3dc..295662146 100644 --- a/lib/puppet/rails/rails_parameter.rb +++ b/lib/puppet/rails/rails_parameter.rb @@ -1,5 +1,13 @@ class Puppet::Rails::RailsParameter < ActiveRecord::Base - belongs_to :rails_objects + belongs_to :rails_resources + + def to_resourceparam(source) + hash = self.attributes + hash[:source] = source + hash.delete("rails_resource_id") + hash.delete("id") + Puppet::Parser::Resource::Param.new hash + end end # $Id$ diff --git a/lib/puppet/rails/rails_resource.rb b/lib/puppet/rails/rails_resource.rb new file mode 100644 index 000000000..caad1b460 --- /dev/null +++ b/lib/puppet/rails/rails_resource.rb @@ -0,0 +1,34 @@ +require 'puppet' +require 'puppet/rails/rails_parameter' + +#RailsParameter = Puppet::Rails::RailsParameter +class Puppet::Rails::RailsResource < ActiveRecord::Base + has_many :rails_parameters, :dependent => :delete_all + serialize :tags, Array + + belongs_to :host + + # Convert our object to a resource. Do not retain whether the object + # is collectable, though, since that would cause it to get stripped + # from the configuration. + def to_resource(scope) + hash = self.attributes + hash["type"] = hash["restype"] + hash.delete("restype") + hash.delete("host_id") + hash.delete("id") + hash.each do |p, v| + hash.delete(p) if v.nil? + end + hash[:scope] = scope + hash[:source] = scope.source + obj = Puppet::Parser::Resource.new(hash) + rails_parameters.each do |param| + obj.set(param.to_resourceparam(scope.source)) + end + + return obj + end +end + +# $Id$ diff --git a/lib/puppet/transportable.rb b/lib/puppet/transportable.rb index 90bc70fc2..4b43b9825 100644 --- a/lib/puppet/transportable.rb +++ b/lib/puppet/transportable.rb @@ -141,8 +141,8 @@ module Puppet end.flatten end - def initialize - @children = [] + def initialize(children = []) + @children = children end def push(*args) @@ -219,7 +219,7 @@ module Puppet else #Puppet.debug "%s[%s] has no parameters" % [@type, @name] end - container = Puppet.type(:component).create(trans) + container = Puppet::Type::Component.create(trans) else hash = { :name => self.name, @@ -238,7 +238,7 @@ module Puppet #if parent # hash[:parent] = parent #end - container = Puppet.type(:component).create(hash) + container = Puppet::Type::Component.create(hash) end #Puppet.info container.inspect @@ -267,8 +267,10 @@ module Puppet begin child.to_type(container) rescue => detail - # We don't do anything on failures, since we assume it's - # been logged elsewhere. + if Puppet[:trace] and ! detail.is_a?(Puppet::Error) + puts detail.backtrace + end + Puppet.err detail.to_s end } diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 320aa83a0..55421eb4c 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -856,9 +856,7 @@ class Type < Puppet::Element end def self.validattr?(name) - if name.is_a?(String) - name = name.intern - end + name = symbolize(name) if self.validstate?(name) or self.validparameter?(name) or self.metaparam?(name) return true else @@ -1017,32 +1015,6 @@ class Type < Puppet::Element } end - def devfail(msg) - self.fail(Puppet::DevError, msg) - end - - # Throw an error, defaulting to a Puppet::Error - def fail(*args) - type = nil - if args[0].is_a?(Class) - type = args.shift - else - type = Puppet::Error - end - - error = type.new(args.join(" ")) - - if defined? @line and @line - error.line = @line - end - - if defined? @file and @file - error.file = @file - end - - raise error - end - # retrieve the 'is' value for a specified state def is(state) if @states.include?(state) @@ -2141,6 +2113,7 @@ class Type < Puppet::Element # Is the parameter in question a meta-parameter? def self.metaparam?(param) + param = symbolize(param) @@metaparamhash.include?(param) end diff --git a/lib/puppet/util.rb b/lib/puppet/util.rb index d8b8c812f..755c254d2 100644 --- a/lib/puppet/util.rb +++ b/lib/puppet/util.rb @@ -456,11 +456,13 @@ module Util end end +require 'puppet/util/errors' require 'puppet/util/methodhelper' require 'puppet/util/metaid' require 'puppet/util/classgen' require 'puppet/util/docs' require 'puppet/util/execution' +require 'puppet/util/logging' require 'puppet/util/package' require 'puppet/util/warnings' diff --git a/lib/puppet/util/errors.rb b/lib/puppet/util/errors.rb new file mode 100644 index 000000000..ad129ee34 --- /dev/null +++ b/lib/puppet/util/errors.rb @@ -0,0 +1,55 @@ +# Some helper methods for throwing errors. +module Puppet::Util::Errors + # Throw a dev error. + def devfail(msg) + self.fail(Puppet::DevError, msg) + end + + # Add line and file info if available and appropriate. + def adderrorcontext(error, other = nil) + error.line ||= self.line if self.respond_to?(:line) and self.line + error.file ||= self.file if self.respond_to?(:file) and self.file + + if other and other.respond_to?(:backtrace) + error.set_backtrace other.backtrace + end + + return error + end + + # Wrap a call in such a way that we always throw the right exception and keep + # as much context as possible. + def exceptwrap(options = {}) + options[:type] ||= Puppet::DevError + begin + retval = yield + rescue Puppet::Error => detail + raise adderrorcontext(detail) + rescue => detail + message = options[:message] || "%s failed with error %s: %s" % + [self.class, detail.class, detail.to_s] + + error = options[:type].new(message) + # We can't use self.fail here because it always expects strings, + # not exceptions. + raise adderrorcontext(error, detail) + end + + return retval + end + + # Throw an error, defaulting to a Puppet::Error. + def fail(*args) + if args[0].is_a?(Class) + type = args.shift + else + type = Puppet::Error + end + + error = adderrorcontext(type.new(args.join(" "))) + + raise error + end +end + +# $Id$ diff --git a/lib/puppet/util/logging.rb b/lib/puppet/util/logging.rb new file mode 100644 index 000000000..1245e24de --- /dev/null +++ b/lib/puppet/util/logging.rb @@ -0,0 +1,20 @@ +# A module to make logging a bit easier. +require 'puppet/log' + +module Puppet::Util::Logging + # Create a method for each log level. + Puppet::Log.eachlevel do |level| + define_method(level) do |args| + if args.is_a?(Array) + args = args.join(" ") + end + Puppet::Log.create( + :level => level, + :source => self, + :message => args + ) + end + end +end + +# $Id$ diff --git a/lib/puppet/util/methodhelper.rb b/lib/puppet/util/methodhelper.rb index 5643ac245..c229e8efb 100644 --- a/lib/puppet/util/methodhelper.rb +++ b/lib/puppet/util/methodhelper.rb @@ -1,5 +1,26 @@ # Where we store helper methods related to, um, methods. module Puppet::Util::MethodHelper + def requiredopts(*names) + names.each do |name| + if self.send(name).nil? + devfail("%s is a required option for %s" % [name, self.class]) + end + end + end + + # Iterate over a hash, treating each member as an attribute. + def set_options(options) + options.dup.each do |param,value| + method = param.to_s + "=" + unless self.respond_to?(method) + self.fail "Invalid parameter %s to object class %s" % + [param,self.class.to_s] + end + + self.send(method,value) + end + end + # Take a hash and convert all of the keys to symbols if possible. def symbolize_options(options) options.inject({}) do |hash, opts| diff --git a/examples/code/failers/badclassnoparam b/test/data/failers/badclassnoparam index a0397aacc..a0397aacc 100644 --- a/examples/code/failers/badclassnoparam +++ b/test/data/failers/badclassnoparam diff --git a/examples/code/failers/badclassparam b/test/data/failers/badclassparam index 4c9ff6199..4c9ff6199 100644 --- a/examples/code/failers/badclassparam +++ b/test/data/failers/badclassparam diff --git a/examples/code/failers/badcompnoparam b/test/data/failers/badcompnoparam index fd25c9445..fd25c9445 100644 --- a/examples/code/failers/badcompnoparam +++ b/test/data/failers/badcompnoparam diff --git a/examples/code/failers/badcompparam b/test/data/failers/badcompparam index 346e64b25..346e64b25 100644 --- a/examples/code/failers/badcompparam +++ b/test/data/failers/badcompparam diff --git a/examples/code/failers/badtypeparam b/test/data/failers/badtypeparam index 4634f2052..4634f2052 100644 --- a/examples/code/failers/badtypeparam +++ b/test/data/failers/badtypeparam diff --git a/examples/code/failers/noobjectrvalue b/test/data/failers/noobjectrvalue index ef6064740..ef6064740 100644 --- a/examples/code/failers/noobjectrvalue +++ b/test/data/failers/noobjectrvalue diff --git a/examples/code/snippets/aliastest.pp b/test/data/snippets/aliastest.pp index 2a8fc9cb9..2a8fc9cb9 100644 --- a/examples/code/snippets/aliastest.pp +++ b/test/data/snippets/aliastest.pp diff --git a/examples/code/snippets/argumentdefaults b/test/data/snippets/argumentdefaults index eac9dd757..eac9dd757 100644 --- a/examples/code/snippets/argumentdefaults +++ b/test/data/snippets/argumentdefaults diff --git a/examples/code/snippets/casestatement.pp b/test/data/snippets/casestatement.pp index 4fcf2e408..4fcf2e408 100644 --- a/examples/code/snippets/casestatement.pp +++ b/test/data/snippets/casestatement.pp diff --git a/examples/code/snippets/classheirarchy.pp b/test/data/snippets/classheirarchy.pp index 36619d8b9..36619d8b9 100644 --- a/examples/code/snippets/classheirarchy.pp +++ b/test/data/snippets/classheirarchy.pp diff --git a/examples/code/snippets/classincludes.pp b/test/data/snippets/classincludes.pp index 927800599..927800599 100644 --- a/examples/code/snippets/classincludes.pp +++ b/test/data/snippets/classincludes.pp diff --git a/examples/code/snippets/classpathtest b/test/data/snippets/classpathtest index 68610958b..68610958b 100644 --- a/examples/code/snippets/classpathtest +++ b/test/data/snippets/classpathtest diff --git a/examples/code/snippets/componentmetaparams.pp b/test/data/snippets/componentmetaparams.pp index 1d2c020c3..7d9f0c2c1 100644 --- a/examples/code/snippets/componentmetaparams.pp +++ b/test/data/snippets/componentmetaparams.pp @@ -7,5 +7,5 @@ define thing { } thing { "/tmp/component2": - require => file["/tmp/component1"] + require => File["/tmp/component1"] } diff --git a/examples/code/snippets/deepclassheirarchy.pp b/test/data/snippets/deepclassheirarchy.pp index 249e6334d..249e6334d 100644 --- a/examples/code/snippets/deepclassheirarchy.pp +++ b/test/data/snippets/deepclassheirarchy.pp diff --git a/examples/code/snippets/defineoverrides.pp b/test/data/snippets/defineoverrides.pp index 1b6561668..a87573a57 100644 --- a/examples/code/snippets/defineoverrides.pp +++ b/test/data/snippets/defineoverrides.pp @@ -11,7 +11,7 @@ class base { } class sub inherits base { - myfile { $file: mode => 755 } + Myfile[$file] { mode => 755 } } include sub diff --git a/examples/code/snippets/dirchmod b/test/data/snippets/dirchmod index dc2cc8949..dc2cc8949 100644 --- a/examples/code/snippets/dirchmod +++ b/test/data/snippets/dirchmod diff --git a/examples/code/snippets/emptyclass.pp b/test/data/snippets/emptyclass.pp index 48047e748..48047e748 100644 --- a/examples/code/snippets/emptyclass.pp +++ b/test/data/snippets/emptyclass.pp diff --git a/examples/code/snippets/emptyexec.pp b/test/data/snippets/emptyexec.pp index 847a30d18..847a30d18 100644 --- a/examples/code/snippets/emptyexec.pp +++ b/test/data/snippets/emptyexec.pp diff --git a/examples/code/snippets/failmissingexecpath.pp b/test/data/snippets/failmissingexecpath.pp index ca5b25f4c..aae1a09fa 100644 --- a/examples/code/snippets/failmissingexecpath.pp +++ b/test/data/snippets/failmissingexecpath.pp @@ -3,11 +3,11 @@ define distloc($path) { ensure => file } exec { "touch $path": - subscribe => file["/tmp/exectesting1"], + subscribe => File["/tmp/exectesting1"], refreshonly => true } } -distloc { +distloc { yay: path => "/tmp/execdisttesting", } diff --git a/examples/code/snippets/falsevalues.pp b/test/data/snippets/falsevalues.pp index 2143b79a7..2143b79a7 100644 --- a/examples/code/snippets/falsevalues.pp +++ b/test/data/snippets/falsevalues.pp diff --git a/examples/code/snippets/filecreate b/test/data/snippets/filecreate index d7972c234..d7972c234 100644 --- a/examples/code/snippets/filecreate +++ b/test/data/snippets/filecreate diff --git a/examples/code/snippets/implicititeration b/test/data/snippets/implicititeration index 6f34cb29c..6f34cb29c 100644 --- a/examples/code/snippets/implicititeration +++ b/test/data/snippets/implicititeration diff --git a/examples/code/snippets/multipleinstances b/test/data/snippets/multipleinstances index 2f9b3c2e8..2f9b3c2e8 100644 --- a/examples/code/snippets/multipleinstances +++ b/test/data/snippets/multipleinstances diff --git a/test/data/snippets/multisubs.pp b/test/data/snippets/multisubs.pp new file mode 100644 index 000000000..bcec69e2a --- /dev/null +++ b/test/data/snippets/multisubs.pp @@ -0,0 +1,13 @@ +class base { + file { "/tmp/multisubtest": content => "base", mode => 644 } +} + +class sub1 inherits base { + File["/tmp/multisubtest"] { mode => 755 } +} + +class sub2 inherits base { + File["/tmp/multisubtest"] { content => sub2 } +} + +include sub1, sub2 diff --git a/examples/code/snippets/namevartest b/test/data/snippets/namevartest index dbee1c356..dbee1c356 100644 --- a/examples/code/snippets/namevartest +++ b/test/data/snippets/namevartest diff --git a/examples/code/snippets/scopetest b/test/data/snippets/scopetest index 3d3b31d8a..331491766 100644 --- a/examples/code/snippets/scopetest +++ b/test/data/snippets/scopetest @@ -2,12 +2,12 @@ $mode = 640 define thing { - file { "/tmp/scopetest": ensure => file, mode => $mode } + file { "/tmp/$name": ensure => file, mode => $mode } } class testing { $mode = 755 - thing {} + thing {scopetest: } } include testing diff --git a/examples/code/snippets/selectorvalues.pp b/test/data/snippets/selectorvalues.pp index cd8cf77a7..cd8cf77a7 100644 --- a/examples/code/snippets/selectorvalues.pp +++ b/test/data/snippets/selectorvalues.pp diff --git a/examples/code/snippets/simpledefaults b/test/data/snippets/simpledefaults index 63d199a68..63d199a68 100644 --- a/examples/code/snippets/simpledefaults +++ b/test/data/snippets/simpledefaults diff --git a/examples/code/snippets/simpleselector b/test/data/snippets/simpleselector index 8b9bc7292..8b9bc7292 100644 --- a/examples/code/snippets/simpleselector +++ b/test/data/snippets/simpleselector diff --git a/examples/code/snippets/singleary.pp b/test/data/snippets/singleary.pp index 1a6aebb21..9ce56dd89 100644 --- a/examples/code/snippets/singleary.pp +++ b/test/data/snippets/singleary.pp @@ -10,10 +10,10 @@ file { "/tmp/singleary2": file { "/tmp/singleary3": ensure => file, - require => [file["/tmp/singleary1"], file["/tmp/singleary2"]] + require => [File["/tmp/singleary1"], File["/tmp/singleary2"]] } file { "/tmp/singleary4": ensure => file, - require => [file["/tmp/singleary1"]] + require => [File["/tmp/singleary1"]] } diff --git a/examples/code/snippets/singlequote.pp b/test/data/snippets/singlequote.pp index dc876a2f8..dc876a2f8 100644 --- a/examples/code/snippets/singlequote.pp +++ b/test/data/snippets/singlequote.pp diff --git a/examples/code/snippets/singleselector.pp b/test/data/snippets/singleselector.pp index 520a14017..520a14017 100644 --- a/examples/code/snippets/singleselector.pp +++ b/test/data/snippets/singleselector.pp diff --git a/examples/code/snippets/tag.pp b/test/data/snippets/tag.pp index e6e770dd9..e6e770dd9 100644 --- a/examples/code/snippets/tag.pp +++ b/test/data/snippets/tag.pp diff --git a/examples/code/snippets/tagged.pp b/test/data/snippets/tagged.pp index e3ca93838..7bf90a645 100644 --- a/examples/code/snippets/tagged.pp +++ b/test/data/snippets/tagged.pp @@ -3,7 +3,7 @@ tag testing tag(funtest) -define tagdefine { +class tagdefine { $path = tagged(tagdefine) ? { true => "true", false => "false" } @@ -11,7 +11,7 @@ define tagdefine { file { "/tmp/taggeddefine$path": ensure => file } } -tagdefine {} +include tagdefine $yayness = tagged(yayness) ? { true => "true", false => "false" diff --git a/test/executables/puppetmodule.rb b/test/executables/puppetmodule.rb index 0752fed14..6ffd84be3 100755 --- a/test/executables/puppetmodule.rb +++ b/test/executables/puppetmodule.rb @@ -34,16 +34,19 @@ class TestPuppetModule < Test::Unit::TestCase if Puppet[:debug] cmd += " --logdest %s" % "console" cmd += " --debug" + cmd += " --trace" else cmd += " --logdest %s" % "/dev/null" end ENV["CFALLCLASSES"] = "yaytest:all" + libsetup + out = nil assert_nothing_raised { - %x{#{cmd + " " + file} 2>&1} + out = %x{#{cmd + " " + file} 2>&1} } - assert($? == 0, "Puppet module exited with code %s" % $?.to_i) + assert($? == 0, "Puppet module exited with code %s: %s" % [$?.to_i, out]) assert(FileTest.exists?(createdfile), "Failed to create config'ed file") end diff --git a/test/language/ast.rb b/test/language/ast.rb index ab528a49d..731b37cb8 100755 --- a/test/language/ast.rb +++ b/test/language/ast.rb @@ -1,13 +1,19 @@ #!/usr/bin/ruby require 'puppet' +require 'puppet/rails' require 'puppet/parser/interpreter' require 'puppet/parser/parser' require 'puppet/client' require 'puppettest' +require 'puppettest/resourcetesting' +require 'puppettest/parsertesting' +require 'puppettest/railstesting' class TestAST < Test::Unit::TestCase + include PuppetTest::RailsTesting include PuppetTest::ParserTesting + include PuppetTest::ResourceTesting # A fake class that we can use for testing evaluation. class FakeAST @@ -28,910 +34,465 @@ class TestAST < Test::Unit::TestCase end end - # Test that classes behave like singletons - def test_classsingleton - parent = child1 = child2 = nil - children = [] - - # create the parent class - children << classobj("parent") - - # Create child class one - children << classobj("child1", :parentclass => nameobj("parent")) + if defined? ActiveRecord + # Verify that our collection stuff works. + def test_collection + collectable = [] + non = [] + # First put some objects into the database. + bucket = mk_transtree do |object, depth, width| + # and mark some of them collectable + if width % 2 == 1 + object.collectable = true + collectable << object + else + non << object + end + end - # Create child class two - children << classobj("child2", :parentclass => nameobj("parent")) - classes = %w{parent child1 child2} + # Now collect our facts + facts = {} + Facter.each do |fact, value| facts[fact] = value end - # Now call the two classes - assert_nothing_raised("Could not add AST nodes for calling") { - children << AST::ObjectDef.new( - :type => nameobj("child1"), - :name => nameobj("yayness"), - :params => astarray() - ) - children << AST::ObjectDef.new( - :type => nameobj("child2"), - :name => nameobj("booness"), - :params => astarray() - ) + assert_nothing_raised { + Puppet::Rails.init } - top = nil - assert_nothing_raised("Could not create top object") { - top = AST::ASTArray.new( - :children => children + # Now try storing our crap + assert_nothing_raised { + host = Puppet::Rails::Host.store( + :objects => bucket, + :facts => facts, + :host => facts["hostname"] ) } - scope = nil - objects = nil - assert_nothing_raised("Could not evaluate") { - scope = Puppet::Parser::Scope.new() - objects = scope.evaluate(:ast => top) + # Now create an ast tree that collects that. They should all be files. + coll = nil + assert_nothing_raised { + coll = AST::Collection.new( + :type => nameobj("file") + ) } - assert_instance_of(Puppet::TransBucket, objects) - - assert_equal(1, scope.find_all { |child| - if child.is_a? Puppet::Parser::Scope - child.lookupobject(:name => "/parent", :type => "file") - else - nil - end - }.length, "Found incorrect number of '/parent' objects") - - assert_equal(classes.sort, scope.classlist.sort) - end - - # Test that 'tagobject' collects all of an object's parameters and stores - # them in one TransObject, rather than many. This is probably a bad idea. - def test_tagobject top = nil - children = [ - fileobj("/etc", "owner" => "root"), - fileobj("/etc", "group" => "root") - ] assert_nothing_raised("Could not create top object") { top = AST::ASTArray.new( - :children => children + :children => [coll] ) } - scope = Puppet::Parser::Scope.new() + objects = nil assert_nothing_raised("Could not evaluate") { - top.evaluate(:scope => scope) - } - - obj = nil - assert_nothing_raised("Could not retrieve file object") { - obj = scope.lookupobject(:name => "/etc", :type => "file") - } - - assert(obj, "could not retrieve file object") - - %w{owner group}.each { |param| - assert(obj.include?(param), "Object did not include %s" % param) + scope = mkscope + objects = scope.evaluate(:ast => top).flatten } + assert(objects.length > 0, "Did not receive any collected objects") + end + else + $stderr.puts "No ActiveRecord -- skipping collection tests" end - # Verify that objects can only have parents of the same type. - def test_validparent - parent = child1 = nil - children = [] - - # create the parent class - children << compobj("parent", :args => AST::ASTArray.new(:children => [])) - - # Create child class one - children << classobj("child1", :parentclass => nameobj("parent")) + def test_if + astif = nil + astelse = nil + fakeelse = FakeAST.new(:else) + faketest = FakeAST.new(true) + fakeif = FakeAST.new(:if) - # Now call the two classes - assert_nothing_raised("Could not add AST nodes for calling") { - children << AST::ObjectDef.new( - :type => nameobj("child1"), - :name => nameobj("yayness"), - :params => astarray() - ) + assert_nothing_raised { + astelse = AST::Else.new(:statements => fakeelse) } - - top = nil - assert_nothing_raised("Could not create top object") { - top = AST::ASTArray.new( - :children => children + assert_nothing_raised { + astif = AST::IfStatement.new( + :test => faketest, + :statements => fakeif, + :else => astelse ) } - scope = nil - assert_raise(Puppet::ParseError, "Invalid parent type was allowed") { - scope = Puppet::Parser::Scope.new() - objects = scope.evaluate(:ast => top) + # We initialized it to true, so we should get that first + ret = nil + assert_nothing_raised { + ret = astif.evaluate(:scope => "yay") } - end - - # Verify that nodes don't evaluate code in other node scopes but that their - # facts work outside their scopes. - def test_nodescopes - parent = child1 = nil - topchildren = [] - - # create the parent class - topchildren << classobj("everyone") - - topchildren << classobj("parent") - - - classes = %w{everyone parent} + assert_equal(:if, ret) - # And a variable, so we verify the facts get set at the top + # Now set it to false and check that + faketest.evaluate = false assert_nothing_raised { - children = [] - children << varobj("yaytest", "$hostname") + ret = astif.evaluate(:scope => "yay") } + assert_equal(:else, ret) + end - nodes = [] - - 3.times do |i| - children = [] - - # Create a child class - topchildren << classobj("perchild#{i}", :parentclass => nameobj("parent")) - classes << "perchild%s" - - # Create a child class - children << classobj("child", :parentclass => nameobj("parent")) - - classes << "child" - - ["child", "everyone", "perchild#{i}"].each do |name| - # Now call our child class - assert_nothing_raised { - children << AST::ObjectDef.new( - :type => nameobj(name), - :params => astarray() - ) - } - end + # Make sure our override object behaves "correctly" + def test_override + interp, scope, source = mkclassframing - # and another variable - assert_nothing_raised { - children << varobj("rahtest", "$hostname") - } - - # create the node - nodename = "node#{i}" - nodes << nodename - assert_nothing_raised("Could not create parent object") { - topchildren << AST::NodeDef.new( - :names => nameobj(nodename), - :code => AST::ASTArray.new( - :children => children - ) - ) - } + ref = nil + assert_nothing_raised do + ref = resourceoverride("resource", "yaytest", "one" => "yay", "two" => "boo") end - # Create the wrapper object - top = nil - assert_nothing_raised("Could not create top object") { - top = AST::ASTArray.new( - :children => topchildren - ) - } - - nodes.each_with_index do |node, i| - # Evaluate the parse tree - scope = Puppet::Parser::Scope.new() - args = {:names => [node], :facts => {"hostname" => node}, :ast => top} + ret = nil + assert_nothing_raised do + ret = ref.evaluate :scope => scope + end - # verify that we can evaluate it okay - trans = nil - assert_nothing_raised("Could not retrieve node definition") { - trans = scope.evaluate(args) - } + assert_instance_of(Puppet::Parser::Resource, ret) - assert_equal(node, scope.lookupvar("hostname")) + assert(ret.override?, "Resource was not an override resource") - assert(trans, "Could not retrieve trans objects") + assert(scope.overridetable[ret.ref].include?(ret), + "Was not stored in the override table") + end - # and that we can convert them to type objects - objects = nil - assert_nothing_raised("Could not retrieve node definition") { - objects = trans.to_type - } + # make sure our resourcedefaults ast object works correctly. + def test_resourcedefaults + interp, scope, source = mkclassframing - assert(objects, "Could not retrieve trans objects") + # Now make some defaults for files + args = {:source => "/yay/ness", :group => "yayness"} + assert_nothing_raised do + obj = defaultobj "file", args + obj.evaluate :scope => scope + end - count = 0 - # Make sure the node name gets into the path correctly. - Puppet.type(:file).each { |obj| - count += 1 - assert(obj.path !~ /#{node}\[#{node}\]/, - "Node name appears twice") - } + hash = nil + assert_nothing_raised do + hash = scope.lookupdefaults("file") + end - assert(count > 0, "Did not create any files") + hash.each do |name, value| + assert_instance_of(Symbol, name) # params always convert + assert_instance_of(Puppet::Parser::Resource::Param, value) + end - classes.each do |name| - if name =~ /%s/ - name = name % i - end - assert(Puppet::Type.type(:file)["/#{name}"], "Could not find '#{name}'") - end - Puppet::Type.allclear + args.each do |name, value| + assert(hash[name], "Did not get default %s" % name) + assert_equal(value, hash[name].value) end end - # Verify that classes are correctly defined in node scopes. - def disabled_test_nodeclasslookup - parent = child1 = nil - children = [] - - # create the parent class - children << classobj("parent") - - # Create child class one - children << classobj("child1", :parentclass => nameobj("parent")) - - # Now call the two classes - assert_nothing_raised("Could not add AST nodes for calling") { - children << AST::ObjectDef.new( - :type => nameobj("child1"), - :name => nameobj("yayness"), - :params => astarray() - ) - } - - # create the node - nodename = "mynodename" - node = nil - assert_nothing_raised("Could not create parent object") { - node = AST::NodeDef.new( - :names => nameobj(nodename), - :code => AST::ASTArray.new( - :children => children - ) - ) - } + def test_hostclass + interp, scope, source = mkclassframing - # Create the wrapper object - top = nil - assert_nothing_raised("Could not create top object") { - top = AST::ASTArray.new( - :children => [node] + # Create the class we're testing, first with no parent + klass = interp.newclass "first", + :code => AST::ASTArray.new( + :children => [resourcedef("file", "/tmp", + "owner" => "nobody", "mode" => "755")] ) - } - - # Evaluate the parse tree - scope = nil - assert_nothing_raised("Could not evaluate node") { - scope = Puppet::Parser::Scope.new() - top.evaluate(:scope => 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") - # verify we can find our node - assert(scope.node(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(:name => [nodename], :facts => {}) - } - - 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") - - trans = nil - # Verify that we can evaluate the node twice - assert_nothing_raised("Could not retrieve node definition") { - trans = scope.evalnode(:name => [nodename], :facts => {}) - } - - objects = nil - assert_nothing_raised("Could not convert to objects") { - objects = trans.to_type - } - - Puppet.type(:file).each { |obj| - assert(obj.path !~ /#{nodename}\[#{nodename}\]/, - "Node name appears twice") - } - - assert(Puppet::Type.type(:file)["/child1"], "Could not find child") - assert(Puppet::Type.type(:file)["/parent"], "Could not find parent") - end + assert_nothing_raised do + klass.evaluate(:scope => scope) + end - # Test that you can look a host up using multiple names, e.g., an FQDN and - # a short name - def test_multiplenodenames - children = [] + # Then try it again + assert_nothing_raised do + klass.evaluate(:scope => scope) + end - # create a short-name node - shortname = "mynodename" - children << nodedef(shortname) + assert(scope.setclass?(klass), "Class was not considered evaluated") - # And a long-name node - longname = "node.domain.com" - children << nodedef(longname) + tmp = scope.findresource("file[/tmp]") + assert(tmp, "Could not find file /tmp") + assert_equal("nobody", tmp[:owner]) + assert_equal("755", tmp[:mode]) - # 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 = Puppet::Parser::Scope.new() - - # Verify we can find the node via a search list - objects = nil - assert_nothing_raised("Could not retrieve short node definition") { - objects = scope.evaluate( - :names => ["%s.domain.com" % shortname, shortname], :facts => {}, - :ast => top + # Now create a couple more classes. + newbase = interp.newclass "newbase", + :code => AST::ASTArray.new( + :children => [resourcedef("file", "/tmp/other", + "owner" => "nobody", "mode" => "644")] ) - } - assert(objects, "Could not retrieve short node definition") - - scope = Puppet::Parser::Scope.new() - # and then look for the long name - assert_nothing_raised("Could not retrieve long node definition") { - objects = scope.evaluate( - :names => [longname.sub(/\..+/, ''), longname], :facts => {}, - :ast => top + newsub = interp.newclass "newsub", + :parent => "newbase", + :code => AST::ASTArray.new( + :children => [resourcedef("file", "/tmp/yay", + "owner" => "nobody", "mode" => "755"), + resourceoverride("file", "/tmp/other", + "owner" => "daemon") + ] ) - } - assert(objects, "Could not retrieve long node definition") - end - - # 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 << nodedef(name) - - # Create the wrapper object - top = nil - assert_nothing_raised("Could not create top object") { - top = AST::ASTArray.new( - :children => children + # Override a different variable in the top scope. + moresub = interp.newclass "moresub", + :parent => "newbase", + :code => AST::ASTArray.new( + :children => [resourceoverride("file", "/tmp/other", + "mode" => "755")] ) - } - scope = Puppet::Parser::Scope.new() - - # Verify we can find the node via a search list - objects = nil - assert_nothing_raised("Could not retrieve short node definition") { - objects = scope.evaluate(:names => [name], :facts => {}, :ast => top) - } - assert(objects, "Could not retrieve short node definition") - assert_instance_of(Puppet::TransBucket, objects) - - # 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 %s" % - "/%s" % name - ) { - assert_equal("/%s" % name, objects[1][0].name) - } - end - - # Test that we can 'include' variables, not just normal strings. - def test_includevars - children = [] - classes = [] - - # Create our class for testin - klassname = "include" - children << classobj(klassname) - classes << klassname - - # Then add our variable assignment - children << varobj("klassvar", klassname) - - # And finally add our calling of the variable - children << AST::ObjectDef.new( - :type => AST::Variable.new(:value => "klassvar"), - :params => astarray - ) - - # And then create our top object - top = AST::ASTArray.new( - :children => children - ) + assert_nothing_raised do + newsub.evaluate(:scope => scope) + end - # Evaluate the parse tree - scope = nil - objects = nil - assert_nothing_raised("Could not evaluate node") { - scope = Puppet::Parser::Scope.new() - objects = scope.evaluate(:ast => top) - } + assert_nothing_raised do + moresub.evaluate(:scope => scope) + end - # Verify we get the right classlist back - assert_equal(classes.sort, scope.classlist.sort) + assert(scope.setclass?(newbase), "Did not eval newbase") + assert(scope.setclass?(newsub), "Did not eval newsub") - # Verify we can find the node via a search list - #assert_nothing_raised("Could not retrieve objects") { - # objects = scope.to_trans - #} - assert(objects, "Could not retrieve objects") + yay = scope.findresource("file[/tmp/yay]") + assert(yay, "Did not find file /tmp/yay") + assert_equal("nobody", yay[:owner]) + assert_equal("755", yay[:mode]) - assert_nothing_raised("Could not find top-declared object") { - assert_equal("/%s" % klassname, objects[0][0].name) - } + other = scope.findresource("file[/tmp/other]") + assert(other, "Did not find file /tmp/other") + assert_equal("daemon", other[:owner]) + assert_equal("755", other[:mode]) end - # Test that node inheritance works correctly - def test_nodeinheritance - children = [] + def test_component + interp, scope, source = mkclassframing - # create the base node - name = "basenode" - children << nodedef(name) - - # and the sub node - name = "subnode" - children << AST::NodeDef.new( - :names => nameobj(name), - :parentclass => nameobj("basenode"), + # Create a new definition + klass = interp.newdefine "yayness", + :arguments => [["owner", stringobj("nobody")], %w{mode}], :code => AST::ASTArray.new( - :children => [ - varobj("%svar" % name, "%svalue" % name), - fileobj("/%s" % name) - ] + :children => [resourcedef("file", "/tmp/$name", + "owner" => varref("owner"), "mode" => varref("mode"))] ) - ) - #subnode = nodedef(name) - #subnode.parentclass = "basenode" - #children << subnode + # Test validattr? a couple different ways + [:owner, "owner", :schedule, "schedule"].each do |var| + assert(klass.validattr?(var), "%s was not considered valid" % var.inspect) + end - # and the top object - top = nil - assert_nothing_raised("Could not create top object") { - top = AST::ASTArray.new( - :children => children + [:random, "random"].each do |var| + assert(! klass.validattr?(var), "%s was considered valid" % var.inspect) + end + # Now call it a couple of times + # First try it without a required param + assert_raise(Puppet::ParseError) do + klass.evaluate(:scope => scope, + :name => "bad", + :arguments => {"owner" => "nobody"} ) - } + end - # Evaluate the parse tree - scope = Puppet::Parser::Scope.new() + # And make sure it didn't create the file + assert_nil(scope.findresource("file[/tmp/bad]"), + "Made file with invalid params") - # Verify we can find the node via a search list - objects = nil - assert_nothing_raised("Could not evaluate node") { - objects = scope.evaluate(:names => [name], :facts => {}, :ast => top) - } - assert(objects, "Could not retrieve node definition") - - assert_nothing_raised { - inner = objects[0] - - # And now verify that we got the subnode file - assert_nothing_raised("Could not find basenode file") { - base = inner[0] - assert_equal("/basenode", base.name) - } - - # and the parent node file - assert_nothing_raised("Could not find subnode file") { - sub = inner[1] - assert_equal("/subnode", sub.name) - } - - inner.each { |obj| - %w{basenode subnode}.each { |tag| - assert(obj.tags.include?(tag), - "%s did not include %s tag" % [obj.name, tag] - ) - } - } - } - end - - def test_typechecking - object = nil - children = [] - type = "deftype" - assert_nothing_raised("Could not add AST nodes for calling") { - object = AST::ObjectDef.new( - :type => nameobj(type), - :name => nameobj("yayness"), - :params => astarray() + assert_nothing_raised do + klass.evaluate(:scope => scope, + :name => "first", + :arguments => {"mode" => "755"} ) - } - - assert_nothing_raised("Typecheck failed") { - object.typecheck(type) - } - - # Add a scope, which makes it think it's evaluating - assert_nothing_raised { - scope = Puppet::Parser::Scope.new() - object.scope = scope - } + end - # Verify an error is thrown when it can't find the type - assert_raise(Puppet::ParseError) { - object.typecheck(type) - } + firstobj = scope.findresource("file[/tmp/first]") + assert(firstobj, "Did not create /tmp/first obj") - # Create child class one - children << classobj(type) - children << object + assert_equal("file", firstobj.type) + assert_equal("/tmp/first", firstobj.title) + assert_equal("nobody", firstobj[:owner]) + assert_equal("755", firstobj[:mode]) - top = nil - assert_nothing_raised("Could not create top object") { - top = AST::ASTArray.new( - :children => children + # Make sure we can't evaluate it with the same args + assert_raise(Puppet::ParseError) do + klass.evaluate(:scope => scope, + :name => "first", + :arguments => {"mode" => "755"} ) - } - - scope = nil - assert_nothing_raised("Could not evaluate") { - scope = Puppet::Parser::Scope.new() - objects = top.evaluate(:scope => scope) - } - end + end - def disabled_test_paramcheck - object = nil - children = [] - type = "deftype" - params = %w{param1 param2} - - comp = compobj(type, { - :args => astarray( - argobj("param1", "yay"), - argobj("param2", "rah") - ), - :code => AST::ASTArray.new( - :children => [ - varobj("%svar" % name, "%svalue" % name), - fileobj("/%s" % name) - ] + # Now create another with different args + assert_nothing_raised do + klass.evaluate(:scope => scope, + :name => "second", + :arguments => {"mode" => "755", "owner" => "daemon"} ) - }) - assert_nothing_raised("Could not add AST nodes for calling") { - object = AST::ObjectDef.new( - :type => nameobj(type), - :name => nameobj("yayness"), - :params => astarray( - astarray(stringobj("param1"), stringobj("value1")), - astarray(stringobj("param2"), stringobj("value2")) - ) - ) - } - - # Add a scope, which makes it think it's evaluating - assert_nothing_raised { - scope = Puppet::Parser::Scope.new() - object.scope = scope - } - - # Verify an error is thrown when it can't find the type - assert_raise(Puppet::ParseError) { - object.paramcheck(false, comp) - } + end - # Create child class one - children << classobj(type) - children << object + secondobj = scope.findresource("file[/tmp/second]") + assert(secondobj, "Did not create /tmp/second obj") - top = nil - assert_nothing_raised("Could not create top object") { - top = AST::ASTArray.new( - :children => children - ) - } - - scope = nil - assert_nothing_raised("Could not evaluate") { - scope = Puppet::Parser::Scope.new() - objects = top.evaluate(:scope => scope) - } + assert_equal("file", secondobj.type) + assert_equal("/tmp/second", secondobj.title) + assert_equal("daemon", secondobj[:owner]) + assert_equal("755", secondobj[:mode]) end - def test_setclass - type = "yay" - classes = [type] - children = [] - # Create child class one - children << varobj("variable", "aclass") - children << tagobj(type, varref("variable")) - children << tagobj(type) + def test_node + interp = mkinterp + scope = mkscope(:interp => interp) - classes << "aclass" + # Define a base node + basenode = interp.newnode "basenode", :code => AST::ASTArray.new(:children => [ + resourcedef("file", "/tmp/base", "owner" => "root") + ]) - top = nil - assert_nothing_raised("Could not create top object") { - top = AST::ASTArray.new( - :children => children - ) - } + # Now define a subnode + nodes = interp.newnode ["mynode", "othernode"], + :code => AST::ASTArray.new(:children => [ + resourcedef("file", "/tmp/mynode", "owner" => "root"), + resourcedef("file", "/tmp/basenode", "owner" => "daemon") + ]) - scope = nil - assert_nothing_raised("Could not evaluate") { - scope = Puppet::Parser::Scope.new() - objects = top.evaluate(:scope => scope) - } + assert_instance_of(Array, nodes) + # Make sure we can find them all. + %w{mynode othernode}.each do |node| + assert(interp.nodesearch_code(node), "Could not find %s" % node) + end + mynode = interp.nodesearch_code("mynode") - classes.each do |tag| - assert(scope.classlist.include?(tag), "Did not set class %s" % tag) + # Now try evaluating the node + assert_nothing_raised do + mynode.evaluate :scope => scope end - end + # Make sure that we can find each of the files + myfile = scope.findresource "file[/tmp/mynode]" + assert(myfile, "Could not find file from node") + assert_equal("root", myfile[:owner]) - # Test that we strip the domain off of host names before they are set as classes - def test_nodenamestrip - children = [] + basefile = scope.findresource "file[/tmp/basenode]" + assert(basefile, "Could not find file from base node") + assert_equal("daemon", basefile[:owner]) - longname = "node.domain.com" - children << nodedef(longname) + # Now make sure we can evaluate nodes with parents + child = interp.newnode(%w{child}, :parent => "basenode").shift - # Create the wrapper object - top = nil - assert_nothing_raised("Could not create top object") { - top = AST::ASTArray.new( - :children => children - ) - } - - scope = Puppet::Parser::Scope.new() - - assert_nothing_raised("Could not evaluate node") { - objects = scope.evaluate(:names => [longname], :facts => {}, :ast => top) - } + newscope = mkscope :interp => interp + assert_nothing_raised do + child.evaluate :scope => newscope + end - assert(!scope.classlist.include?("node.domain.com"), - "Node's long name got set") - assert(scope.classlist.include?("node"), "Node's name did not get set") + assert(newscope.findresource("file[/tmp/base]"), + "Could not find base resource") end - # Make sure that deep class parentage works - def test_classparentage - children = [] - files = [] - base = classobj("base") - files << "/base" + def test_collection + interp = mkinterp + scope = mkscope(:interp => interp) - children << base + coll = nil + assert_nothing_raised do + coll = AST::Collection.new(:type => "file", :form => :virtual) + end - parent = "base" - 5.times { |i| - name = "child%s" % i - files << "/%s" % name - children << classobj(name, :parentclass => nameobj(parent)) + assert_instance_of(AST::Collection, coll) - parent = name - } + ret = nil + assert_nothing_raised do + ret = coll.evaluate :scope => scope + end - children << functionobj("include", parent) + assert_instance_of(Puppet::Parser::Collector, ret) - top = nil - assert_nothing_raised("Could not create top object") { - top = AST::ASTArray.new( - :children => children - ) - } + # Now make sure we get it back from the scope + assert_equal([ret], scope.collections) + end - objects = nil - assert_nothing_raised("Could not evaluate") { - scope = Puppet::Parser::Scope.new() - objects = scope.evaluate(:ast => top) - } + def test_virtual_collexp + @interp, @scope, @source = mkclassframing - objects = objects.flatten + # make a resource + resource = mkresource(:type => "file", :title => "/tmp/testing", + :params => {:owner => "root", :group => "bin", :mode => "644"}) - files.each do |file| - assert(objects.find { |o| o.name == file }, - "Could not find file %s" % file) + run_collection_queries(:virtual) do |string, result, query| + code = nil + assert_nothing_raised do + str, code = query.evaluate :scope => @scope + end + + assert_instance_of(Proc, code) + assert_nothing_raised do + assert_equal(result, code.call(resource), + "'#{string}' failed") + end end end - # Make sure we catch names that are specified like parameters. - def test_name_or_param - obj = nil - assert_nothing_raised { - obj = AST::ObjectDef.new( - :type => nameobj("file"), - :params => astarray(AST::ObjectParam.new( - :param => stringobj("name"), - :value => stringobj("yayness") - )) - ) - } - - scope = Puppet::Parser::Scope.new + if defined? ActiveRecord::Base + def test_exported_collexp + railsinit + Puppet[:storeconfigs] = true + @interp, @scope, @source = mkclassframing + + # make a rails resource + railsresource "file", "/tmp/testing", :owner => "root", :group => "bin", + :mode => "644" + + run_collection_queries(:exported) do |string, result, query| + code = nil + str = nil + + # We don't support anything but the title in rails right now + retval = nil + bad = false + # Figure out if the search is for anything rails will ignore + string.scan(/(\w+) [!=]= \w+/) do |s| + unless s[0] == "title" + bad = true + break + end + end - trans = nil - assert_nothing_raised { - trans = scope.evaluate(:ast => obj, :facts => {}) - } + # And if it is, make sure we throw an error. + if bad + assert_raise(Puppet::ParseError, "Evaluated '#{string}'") do + str, code = query.evaluate :scope => @scope + end + next + else + assert_nothing_raised("Could not evaluate '#{string}'") do + str, code = query.evaluate :scope => @scope + end + end + assert_nothing_raised("Could not find resource") do + retval = Puppet::Rails::RailsResource.find(:all, + :include => :rails_parameters, + :conditions => str) + end - transobj = trans.shift - assert(transobj.name, "Name did not convert from param to name") - end + if result + assert_equal(1, retval.length, "Did not find resource with '#{string}'") + res = retval.shift - if defined? ActiveRecord - # Verify that our collection stuff works. - def test_collection - collectable = [] - non = [] - # First put some objects into the database. - bucket = mk_transtree do |object, depth, width| - # and mark some of them collectable - if width % 2 == 1 - object.collectable = true - collectable << object + assert_equal("file", res.restype) + assert_equal("/tmp/testing", res.title) else - non << object + assert_equal(0, retval.length, "found a resource with '#{string}'") end end - - # Now collect our facts - facts = {} - Facter.each do |fact, value| facts[fact] = value end - - assert_nothing_raised { - Puppet::Rails.init - } - - # Now try storing our crap - assert_nothing_raised { - host = Puppet::Rails::Host.store( - :objects => bucket, - :facts => facts, - :host => facts["hostname"] - ) - } - - # Now create an ast tree that collects that. They should all be files. - coll = nil - assert_nothing_raised { - coll = AST::Collection.new( - :type => nameobj("file") - ) - } - - top = nil - assert_nothing_raised("Could not create top object") { - top = AST::ASTArray.new( - :children => [coll] - ) - } - - objects = nil - assert_nothing_raised("Could not evaluate") { - scope = Puppet::Parser::Scope.new() - objects = scope.evaluate(:ast => top).flatten - } - - assert(objects.length > 0, "Did not receive any collected objects") end - else - $stderr.puts "No ActiveRecord -- skipping collection tests" end - # To fix #140. Currently non-functional. - def disabled_test_classreuse - children = [] - - # Create the parent class, with a definition in it. - children << classobj("parent", :code => AST::ASTArray.new( - :file => __FILE__, - :line => __LINE__, - :children => [ - compobj("foo", :args => AST::ASTArray.new( - :children => [nameobj("arg")] - ), - :code => AST::ASTArray.new( - :file => __FILE__, - :line => __LINE__, - :children => [fileobj("/$arg")] - ) - ), - objectdef("foo", "ptest", {"arg" => "parentfoo"}) - ] - )) - - # Create child class, also trying to use that definition - children << classobj("child1", :parentclass => nameobj("parent"), - :code => AST::ASTArray.new( - :file => __FILE__, - :line => __LINE__, - :children => [ - objectdef("foo", "ctest", {"arg" => "childfoo"}) - ] - ) - ) - - # Call the parent first - children << functionobj("include", "parent") - - # Then call the child, and make sure it can look up the definition - children << functionobj("include", "child1") - - top = nil - assert_nothing_raised("Could not create top object") { - top = AST::ASTArray.new( - :children => children - ) - } - - objects = nil - assert_nothing_raised("Could not evaluate") { - scope = Puppet::Parser::Scope.new() - objects = scope.evaluate(:ast => top) - } - end - - def test_if - astif = nil - astelse = nil - fakeelse = FakeAST.new(:else) - faketest = FakeAST.new(true) - fakeif = FakeAST.new(:if) - - assert_nothing_raised { - astelse = AST::Else.new(:statements => fakeelse) - } - assert_nothing_raised { - astif = AST::IfStatement.new( - :test => faketest, - :statements => fakeif, - :else => astelse - ) - } + def run_collection_queries(form) + {true => [%{title == "/tmp/testing"}, %{(title == "/tmp/testing")}, + %{title == "/tmp/testing" and group == bin}, %{title == bin or group == bin}, + %{title == "/tmp/testing" or title == bin}, %{title == "/tmp/testing"}, + %{(title == "/tmp/testing" or title == bin) and group == bin}], + false => [%{title == bin}, %{title == bin or (title == bin and group == bin)}, + %{title != "/tmp/testing"}, %{title != "/tmp/testing" and group != bin}] + }.each do |res, ary| + ary.each do |str| + if form == :virtual + code = "File <| #{str} |>" + else + code = "File <<| #{str} |>>" + end + parser = mkparser + query = nil - # We initialized it to true, so we should get that first - ret = nil - assert_nothing_raised { - ret = astif.evaluate(:scope => "yay") - } - assert_equal(:if, ret) + assert_nothing_raised("Could not parse '#{str}'") do + query = parser.parse(code)[0].query + end - # Now set it to false and check that - faketest.evaluate = false - assert_nothing_raised { - ret = astif.evaluate(:scope => "yay") - } - assert_equal(:else, ret) + yield str, res, query + end + end end end diff --git a/test/language/collector.rb b/test/language/collector.rb new file mode 100755 index 000000000..d7ac059fa --- /dev/null +++ b/test/language/collector.rb @@ -0,0 +1,206 @@ +#!/usr/bin/ruby + +require 'puppet/rails' +require 'puppettest' +require 'puppettest/parsertesting' +require 'puppettest/resourcetesting' +require 'puppettest/railstesting' + +class TestCollector < Test::Unit::TestCase + include PuppetTest + include PuppetTest::ParserTesting + include PuppetTest::ResourceTesting + include PuppetTest::RailsTesting + Parser = Puppet::Parser + AST = Parser::AST + + def setup + super + Puppet[:trace] = false + @interp, @scope, @source = mkclassframing + end + + def test_virtual + # Make a virtual resource + virtual = mkresource(:type => "file", :title => "/tmp/virtual", + :virtual => true, :params => {:owner => "root"}) + @scope.setresource virtual + + # And a non-virtual + real = mkresource(:type => "file", :title => "/tmp/real", + :params => {:owner => "root"}) + @scope.setresource real + + # Now make a collector + coll = nil + assert_nothing_raised do + coll = Puppet::Parser::Collector.new(@scope, "file", nil, :virtual) + end + + # Set it in our scope + @scope.newcollection(coll) + + # Make sure it's in the collections + assert_equal([coll], @scope.collections) + + # And try to collect the virtual resources. + ret = nil + assert_nothing_raised do + ret = coll.collect_virtual + end + + assert_equal([virtual], ret) + + # Now make sure evaluate does the right thing. + assert_nothing_raised do + ret = coll.evaluate + end + + # Make sure it got deleted from the collection list + assert_equal([], @scope.collections) + + # And make sure our virtual object is no longer virtual + assert(! virtual.virtual?, "Virtual object did not get realized") + + # Now make a new collector of a different type and make sure it + # finds nothing. + assert_nothing_raised do + coll = Puppet::Parser::Collector.new(@scope, "exec", nil, :virtual) + end + + # Remark this as virtual + virtual.virtual = true + + assert_nothing_raised do + ret = coll.evaluate + end + + assert_equal([], ret) + end + + if defined? ActiveRecord::Base + def test_collect_exported + railsinit + # make an exported resource + exported = mkresource(:type => "file", :title => "/tmp/exported", + :exported => true, :params => {:owner => "root"}) + @scope.setresource exported + + assert(exported.exported?, "Object was not marked exported") + assert(exported.virtual?, "Object was not marked virtual") + + # And a non-exported + real = mkresource(:type => "file", :title => "/tmp/real", + :params => {:owner => "root"}) + @scope.setresource real + + # Now make a collector + coll = nil + assert_nothing_raised do + coll = Puppet::Parser::Collector.new(@scope, "file", nil, :exported) + end + + # Set it in our scope + @scope.newcollection(coll) + + # Make sure it's in the collections + assert_equal([coll], @scope.collections) + + # And try to collect the virtual resources. + ret = nil + assert_nothing_raised do + ret = coll.collect_exported + end + + assert_equal([exported], ret) + + # Now make sure evaluate does the right thing. + assert_nothing_raised do + ret = coll.evaluate + end + + # Make sure it got deleted from the collection list + assert_equal([], @scope.collections) + + # And make sure our exported object is no longer exported + assert(! exported.virtual?, "Virtual object did not get realized") + + # But it should still be marked exported. + assert(exported.exported?, "Resource got un-exported") + + # Now make a new collector of a different type and make sure it + # finds nothing. + assert_nothing_raised do + coll = Puppet::Parser::Collector.new(@scope, "exec", nil, :exported) + end + + # Remark this as virtual + exported.virtual = true + + assert_nothing_raised do + ret = coll.evaluate + end + + assert_equal([], ret) + end + + def test_collection_conflicts + railsinit + + # First make a railshost we can conflict with + host = Puppet::Rails::Host.new(:name => "myhost") + + host.rails_resources.build(:title => "/tmp/conflicttest", :restype => "file", + :exported => true) + + host.save + + # Now make a normal resource + normal = mkresource(:type => "file", :title => "/tmp/conflicttest", + :params => {:owner => "root"}) + @scope.setresource normal + + # Now make a collector + coll = nil + assert_nothing_raised do + coll = Puppet::Parser::Collector.new(@scope, "file", nil, :exported) + end + + # And try to collect the virtual resources. + assert_raise(Puppet::ParseError) do + ret = coll.collect_exported + end + end + + # Make sure we do not collect resources from the host we're on + def test_no_resources_from_me + railsinit + + # Make our configuration + host = Puppet::Rails::Host.new(:name => "myhost") + + host.rails_resources.build(:title => "/tmp/hosttest", :restype => "file", + :exported => true) + + host.save + + @scope.host = "myhost" + + # Now make a collector + coll = nil + assert_nothing_raised do + coll = Puppet::Parser::Collector.new(@scope, "file", nil, :exported) + end + + # And make sure we get nada back + ret = nil + assert_nothing_raised do + ret = coll.collect_exported + end + + assert(ret.empty?, "Found exports from our own host") + end + end +end + +# $Id$ diff --git a/test/language/functions.rb b/test/language/functions.rb index 2c3246e6a..062971bf1 100755 --- a/test/language/functions.rb +++ b/test/language/functions.rb @@ -35,7 +35,7 @@ class TestLangFunctions < Test::Unit::TestCase ) end - scope = Puppet::Parser::Scope.new() + scope = mkscope val = nil assert_nothing_raised do val = func.evaluate(:scope => scope) @@ -45,10 +45,10 @@ class TestLangFunctions < Test::Unit::TestCase end def test_taggedfunction - scope = Puppet::Parser::Scope.new() + scope = mkscope tag = "yayness" - scope.setclass(tag.object_id, tag) + scope.tag(tag) {"yayness" => true, "booness" => false}.each do |tag, retval| func = taggedobj(tag, :rvalue) @@ -75,7 +75,7 @@ class TestLangFunctions < Test::Unit::TestCase ) end - scope = Puppet::Parser::Scope.new() + scope = mkscope val = nil assert_raise(Puppet::ParseError) do val = func.evaluate(:scope => scope) @@ -107,7 +107,7 @@ class TestLangFunctions < Test::Unit::TestCase end ast = varobj("output", func) - scope = Puppet::Parser::Scope.new() + scope = mkscope assert_raise(Puppet::ParseError) do ast.evaluate(:scope => scope) end @@ -145,7 +145,7 @@ class TestLangFunctions < Test::Unit::TestCase end ast = varobj("output", func) - scope = Puppet::Parser::Scope.new() + scope = mkscope assert_raise(Puppet::ParseError) do ast.evaluate(:scope => scope) end @@ -180,7 +180,7 @@ class TestLangFunctions < Test::Unit::TestCase end ast = varobj("output", func) - scope = Puppet::Parser::Scope.new() + scope = mkscope scope.setvar("myvar", "this is yayness") assert_raise(Puppet::ParseError) do ast.evaluate(:scope => scope) @@ -257,7 +257,7 @@ class TestLangFunctions < Test::Unit::TestCase "" => "", false => "false", }.each do |string, value| - scope = Puppet::Parser::Scope.new() + scope = mkscope assert_raise(Puppet::ParseError) do ast.evaluate(:scope => scope) end diff --git a/test/language/interpreter.rb b/test/language/interpreter.rb index c127ab517..e195e28b3 100755 --- a/test/language/interpreter.rb +++ b/test/language/interpreter.rb @@ -8,10 +8,18 @@ require 'puppet/parser/parser' require 'puppet/client' require 'puppet/rails' require 'puppettest' +require 'puppettest/resourcetesting' +require 'puppettest/parsertesting' +require 'puppettest/servertest' +require 'puppettest/railstesting' +require 'timeout' class TestInterpreter < Test::Unit::TestCase include PuppetTest include PuppetTest::ServerTest + include PuppetTest::ParserTesting + include PuppetTest::ResourceTesting + include PuppetTest::RailsTesting AST = Puppet::Parser::AST # create a simple manifest that uses nodes to create a file @@ -135,81 +143,48 @@ class TestInterpreter < Test::Unit::TestCase return parent, classes end - def test_ldapnodes + def test_ldapsearch Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" Puppet[:ldapnodes] = true ldapconnect() - file = tempfile() - files = [] - parentfile = tempfile() + "-parent" - files << parentfile - hostname = Facter["hostname"].value - lparent, lclasses = ldaphost(Facter["hostname"].value) - assert(lclasses, "Did not retrieve info from ldap") - File.open(file, "w") { |f| - f.puts "node #{lparent} { - file { \"#{parentfile}\": ensure => file } -}" - - lclasses.each { |klass| - kfile = tempfile() + "-klass" - files << kfile - f.puts "class #{klass} { file { \"#{kfile}\": ensure => file } }" - } - } - interp = nil - assert_nothing_raised { - interp = Puppet::Parser::Interpreter.new( - :Manifest => file - ) - } - parent = nil - classes = nil - # First make sure we get the default node for unknown hosts - dparent, dclasses = ldaphost("default") + interp = mkinterp :NodeSources => [:ldap, :code] - assert_nothing_raised { - parent, classes = interp.nodesearch("nosuchhostokay") - } + # Make sure we can find 'culain' in ldap + parent, classes = nil + assert_nothing_raised do + parent, classes = interp.ldapsearch("culain") + end - assert_equal(dparent, parent, "Default parent node did not match") - assert_equal(dclasses, classes, "Default parent class list did not match") + realparent, realclasses = ldaphost("culain") + assert_equal(realparent, parent) + assert_equal(realclasses, classes) + end - # Look for a host we know doesn't have a parent - npparent, npclasses = ldaphost("noparent") - assert_nothing_raised { - #parent, classes = interp.nodesearch_ldap("noparent") - parent, classes = interp.nodesearch("noparent") - } + def test_ldapnodes + Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" + Puppet[:ldapnodes] = true - assert_equal(npparent, parent, "Parent node did not match") - assert_equal(npclasses, classes, "Class list did not match") + ldapconnect() - # Now look for our normal host - assert_nothing_raised { - parent, classes = interp.nodesearch_ldap(hostname) - } + interp = mkinterp :NodeSources => [:ldap, :code] - assert_equal(lparent, parent, "Parent node did not match") - assert_equal(lclasses, classes, "Class list did not match") + # culain uses basenode, so create that + basenode = interp.newnode([:basenode])[0] - objects = nil - assert_nothing_raised { - objects = interp.run(hostname, Puppet::Client::MasterClient.facts) - } + # Make sure we can find 'culain' in ldap + culain = nil + assert_nothing_raised do + culain = interp.nodesearch_ldap("culain") + end - comp = nil - assert_nothing_raised { - comp = objects.to_type - } + assert(culain, "Did not find culain in ldap") - assert_apply(comp) - files.each { |cfile| - @@tmpfiles << cfile - assert(FileTest.exists?(cfile), "Did not make %s" % cfile) - } + assert_nothing_raised do + assert_equal(basenode.fqname.to_s, culain.parentclass.fqname.to_s, + "Did not get parent class") + end end if Puppet::SUIDManager.uid == 0 and Facter["hostname"].value == "culain" @@ -260,87 +235,32 @@ class TestInterpreter < Test::Unit::TestCase # Make sure searchnode behaves as we expect. def test_nodesearch - # First create a fake nodesearch algorithm - i = 0 - bucket = [] - Puppet::Parser::Interpreter.send(:define_method, "nodesearch_fake") do |node| - return nil, nil if node == "default" - - return bucket[0], bucket[1] - end - text = %{ -node nodeparent {} -node othernodeparent {} -class nodeclass {} -class nothernode {} -} - manifest = tempfile() - File.open(manifest, "w") do |f| f.puts text end - interp = nil - assert_nothing_raised { - interp = Puppet::Parser::Interpreter.new( - :Manifest => manifest, - :NodeSources => [:fake] - ) - } - # Make sure it behaves correctly for all forms - [[nil, nil], - ["nodeparent", nil], - [nil, ["nodeclass"]], - [nil, ["nodeclass", "nothernode"]], - ["othernodeparent", ["nodeclass", "nothernode"]],].each do |ary| - # Set the return values - bucket = ary - - # Look them back up - parent, classes = interp.nodesearch("mynode") - - # Basically, just make sure that if we have either or both, - # we get a result back. - assert_equal(ary[0], parent, - "Parent is not %s" % parent) - assert_equal(ary[1], classes, - "Parent is not %s" % parent) - - next if ary == [nil, nil] - # Now make sure we actually get the configuration. This will throw - # an exception if we don't. - assert_nothing_raised do - interp.run("mynode", {}) - end - end - end - - # Make sure nodesearch uses all names, not just one. - def test_nodesearch_multiple_names - bucket = {} - Puppet::Parser::Interpreter.send(:define_method, "nodesearch_multifake") do |node| - if bucket[node] - return *bucket[node] - else - return nil, nil - end + interp = mkinterp + + # Make some nodes + names = %w{node1 node2 node2.domain.com} + interp.newnode names + + nodes = {} + # Make sure we can find them all, using the direct method + names.each do |name| + nodes[name] = interp.nodesearch_code(name) + assert(nodes[name], "Could not find %s" % name) + nodes[name].file = __FILE__ end - manifest = tempfile() - File.open(manifest, "w") do |f| f.puts "" end - interp = nil - assert_nothing_raised { - interp = Puppet::Parser::Interpreter.new( - :Manifest => manifest, - :NodeSources => [:multifake] - ) - } - bucket["name.domain.com"] = [:parent, [:classes]] - - ret = nil - - assert_nothing_raised do - assert_equal bucket["name.domain.com"], - interp.nodesearch("name", "name.domain.com") + # Now let's try it with the nodesearch method + names.each do |name| + node = interp.nodesearch(name) + assert(node, "Could not find #{name} via nodesearch") end + # Now make sure the longest match always wins + node = interp.nodesearch(*%w{node2 node2.domain.com}) + assert(node, "Did not find node2") + assert_equal("node2.domain.com", node.fqname, + "Did not get longest match") end def test_parsedate @@ -389,4 +309,519 @@ class nothernode {} newdate = interp.parsedate assert(date != newdate, "Parsedate was not updated") end + + # Make sure our node gets added to the node table. + def test_newnode + interp = mkinterp + + # First just try calling it directly + assert_nothing_raised { + interp.newnode("mynode", :code => :yay) + } + + assert_equal(:yay, interp.nodesearch_code("mynode").code) + + # Now make sure that trying to redefine it throws an error. + assert_raise(Puppet::ParseError) { + interp.newnode("mynode", {}) + } + + # Now try one with no code + assert_nothing_raised { + interp.newnode("simplenode", :parent => :foo) + } + + # Make sure trying to get the parentclass throws an error + assert_raise(Puppet::ParseError) do + interp.nodesearch_code("simplenode").parentclass + end + + # Now define the parent node + interp.newnode(:foo) + + # And make sure we get things back correctly + assert_equal("foo", interp.nodesearch_code("simplenode").parentclass.fqname) + assert_nil(interp.nodesearch_code("simplenode").code) + + # Now make sure that trying to redefine it throws an error. + assert_raise(Puppet::ParseError) { + interp.newnode("mynode", {}) + } + + # Test multiple names + names = ["one", "two", "three"] + assert_nothing_raised { + interp.newnode(names, {:code => :yay, :parent => :foo}) + } + + names.each do |name| + assert_equal(:yay, interp.nodesearch_code(name).code) + assert_equal("foo", interp.nodesearch_code(name).parentclass.name) + # Now make sure that trying to redefine it throws an error. + assert_raise(Puppet::ParseError) { + interp.newnode(name, {}) + } + end + end + + # Make sure we're correctly generating a node definition. + def test_gennode + interp = mkinterp + + node = nil + assert_nothing_raised { + node = interp.gennode("nodeA", :classes => %w{classA classB}) + } + + assert_instance_of(Puppet::Parser::AST::Node, node) + + assert_equal("nodeA", node.name) + end + + def test_fqfind + interp = mkinterp + + table = {} + # Define a bunch of things. + %w{a c a::b a::b::c a::c a::b::c::d a::b::c::d::e::f c::d}.each do |string| + table[string] = string + end + + check = proc do |namespace, hash| + hash.each do |thing, result| + assert_equal(result, interp.fqfind(namespace, thing, table), + "Could not find %s in %s" % [thing, namespace]) + end + end + + # Now let's do some test lookups. + + # First do something really simple + check.call "a", "b" => "a::b", "b::c" => "a::b::c", "d" => nil, "::c" => "c" + + check.call "a::b", "c" => "a::b::c", "b" => "a::b", "a" => "a" + + check.call "a::b::c::d::e", "c" => "a::b::c", "::c" => "c", + "c::d" => "a::b::c::d", "::c::d" => "c::d" + + check.call "", "a" => "a", "a::c" => "a::c" + end + + def test_newdefine + interp = mkinterp + + assert_nothing_raised { + interp.newdefine("mydefine", :code => :yay, + :arguments => ["a", stringobj("b")]) + } + + mydefine = interp.finddefine("", "mydefine") + assert(mydefine, "Could not find definition") + assert_equal("mydefine", interp.finddefine("", "mydefine").type) + assert_equal("", mydefine.namespace) + assert_equal("mydefine", mydefine.type) + + assert_raise(Puppet::ParseError) do + interp.newdefine("mydefine", :code => :yay, + :arguments => ["a", stringobj("b")]) + end + + # Now define the same thing in a different scope + assert_nothing_raised { + interp.newdefine("other::mydefine", :code => :other, + :arguments => ["a", stringobj("b")]) + } + other = interp.finddefine("other", "mydefine") + assert(other, "Could not find definition") + assert(interp.finddefine("", "other::mydefine"), + "Could not find other::mydefine") + assert_equal(:other, other.code) + assert_equal("other", other.namespace) + assert_equal("mydefine", other.type) + assert_equal("other::mydefine", other.fqname) + end + + def test_newclass + interp = mkinterp + + mkcode = proc do |ary| + classes = ary.collect do |string| + AST::FlatString.new(:value => string) + end + AST::ASTArray.new(:children => classes) + end + scope = Puppet::Parser::Scope.new(:interp => interp) + + # First make sure that code is being appended + code = mkcode.call(%w{original code}) + + klass = nil + assert_nothing_raised { + klass = interp.newclass("myclass", :code => code) + } + + assert(klass, "Did not return class") + + assert(interp.findclass("", "myclass"), "Could not find definition") + assert_equal("myclass", interp.findclass("", "myclass").type) + assert_equal(%w{original code}, + interp.findclass("", "myclass").code.evaluate(:scope => scope)) + + # Now create the same class name in a different scope + assert_nothing_raised { + klass = interp.newclass("other::myclass", + :code => mkcode.call(%w{something diff})) + } + assert(klass, "Did not return class") + other = interp.findclass("other", "myclass") + assert(other, "Could not find class") + assert(interp.findclass("", "other::myclass"), "Could not find class") + assert_equal("other::myclass", other.fqname) + assert_equal("other", other.namespace) + assert_equal("myclass", other.type) + assert_equal(%w{something diff}, + interp.findclass("other", "myclass").code.evaluate(:scope => scope)) + + # Newclass behaves differently than the others -- it just appends + # the code to the existing class. + code = mkcode.call(%w{something new}) + assert_nothing_raised do + klass = interp.newclass("myclass", :code => code) + end + assert(klass, "Did not return class when appending") + assert_equal(%w{original code something new}, + interp.findclass("", "myclass").code.evaluate(:scope => scope)) + + # Make sure newclass deals correctly with nodes with no code + klass = interp.newclass("nocode") + assert(klass, "Did not return class") + + assert_nothing_raised do + klass = interp.newclass("nocode", :code => mkcode.call(%w{yay test})) + end + assert(klass, "Did not return class with no code") + assert_equal(%w{yay test}, + interp.findclass("", "nocode").code.evaluate(:scope => scope)) + + # Then try merging something into nothing + interp.newclass("nocode2", :code => mkcode.call(%w{foo test})) + assert(klass, "Did not return class with no code") + + assert_nothing_raised do + klass = interp.newclass("nocode2") + end + assert(klass, "Did not return class with no code") + assert_equal(%w{foo test}, + interp.findclass("", "nocode2").code.evaluate(:scope => scope)) + + # And lastly, nothing and nothing + klass = interp.newclass("nocode3") + assert(klass, "Did not return class with no code") + + assert_nothing_raised do + klass = interp.newclass("nocode3") + end + assert(klass, "Did not return class with no code") + assert_nil(interp.findclass("", "nocode3").code) + + end + + # Now make sure we get appropriate behaviour with parent class conflicts. + def test_newclass_parentage + interp = mkinterp + interp.newclass("base1") + interp.newclass("one::two::three") + + # First create it with no parentclass. + assert_nothing_raised { + interp.newclass("sub") + } + assert(interp.findclass("", "sub"), "Could not find definition") + assert_nil(interp.findclass("", "sub").parentclass) + + # Make sure we can't set the parent class to ourself. + assert_raise(Puppet::ParseError) { + interp.newclass("sub", :parent => "sub") + } + + # Now create another one, with a parentclass. + assert_nothing_raised { + interp.newclass("sub", :parent => "base1") + } + + # Make sure we get the right parent class, and make sure it's an object. + assert_equal(interp.findclass("", "base1"), + interp.findclass("", "sub").parentclass) + + # Now make sure we get a failure if we try to conflict. + assert_raise(Puppet::ParseError) { + interp.newclass("sub", :parent => "one::two::three") + } + + # Make sure that failure didn't screw us up in any way. + assert_equal(interp.findclass("", "base1"), + interp.findclass("", "sub").parentclass) + # But make sure we can create a class with a fq parent + assert_nothing_raised { + interp.newclass("another", :parent => "one::two::three") + } + assert_equal(interp.findclass("", "one::two::three"), + interp.findclass("", "another").parentclass) + + end + + def test_namesplit + interp = mkinterp + + assert_nothing_raised do + {"base::sub" => %w{base sub}, + "main" => ["", "main"], + "one::two::three::four" => ["one::two::three", "four"], + }.each do |name, ary| + result = interp.namesplit(name) + assert_equal(ary, result, "%s split to %s" % [name, result]) + end + end + end + + # Make sure you can't have classes and defines with the same name in the + # same scope. + def test_classes_beat_defines + interp = mkinterp + + assert_nothing_raised { + interp.newclass("yay::funtest") + } + + assert_raise(Puppet::ParseError) do + interp.newdefine("yay::funtest") + end + + assert_nothing_raised { + interp.newdefine("yay::yaytest") + } + + assert_raise(Puppet::ParseError) do + interp.newclass("yay::yaytest") + end + end + + # Make sure our whole chain works. + def test_evaluate + interp, scope, source = mkclassframing + + # Create a define that we'll be using + interp.newdefine("wrapper", :code => AST::ASTArray.new(:children => [ + resourcedef("file", varref("name"), "owner" => "root") + ])) + + # Now create a resource that uses that define + define = mkresource(:type => "wrapper", :title => "/tmp/testing", + :scope => scope, :source => source, :params => :none) + + scope.setresource define + + # And a normal resource + scope.setresource mkresource(:type => "file", :title => "/tmp/rahness", + :scope => scope, :source => source, + :params => {:owner => "root"}) + + # Now evaluate everything + objects = nil + interp.usenodes = false + assert_nothing_raised do + objects = interp.evaluate(nil, {}) + end + + assert_instance_of(Puppet::TransBucket, objects) + end + + def test_evaliterate + interp, scope, source = mkclassframing + + # Create a top-level definition that creates a builtin object + interp.newdefine("one", :arguments => [%w{owner}], + :code => AST::ASTArray.new(:children => [ + resourcedef("file", varref("name"), + "owner" => varref("owner") + ) + ]) + ) + + # Create another definition to call that one + interp.newdefine("two", :arguments => [%w{owner}], + :code => AST::ASTArray.new(:children => [ + resourcedef("one", varref("name"), + "owner" => varref("owner") + ) + ]) + ) + + # And then a third + interp.newdefine("three", :arguments => [%w{owner}], + :code => AST::ASTArray.new(:children => [ + resourcedef("two", varref("name"), + "owner" => varref("owner") + ) + ]) + ) + + three = Puppet::Parser::Resource.new( + :type => "three", :title => "/tmp/yayness", + :scope => scope, :source => source, + :params => paramify(source, :owner => "root") + ) + + scope.setresource(three) + + ret = nil + assert_nothing_raised do + ret = scope.unevaluated + end + + + assert_instance_of(Array, ret) + assert(1, ret.length) + assert_equal([three], ret) + + assert(ret.detect { |r| r.ref == "three[/tmp/yayness]"}, + "Did not get three back as unevaluated") + + # Now translate the whole tree + assert_nothing_raised do + interp.evaliterate(scope) + end + + # Now make sure we've got our file + file = scope.findresource "file[/tmp/yayness]" + assert(file, "Could not find file") + + assert_equal("root", file[:owner]) + end + + # Make sure we fail if there are any leftover overrides to perform. + # This would normally mean that someone is trying to override an object + # that does not exist. + def test_failonleftovers + interp, scope, source = mkclassframing + + # Make sure we don't fail, since there are no overrides + assert_nothing_raised do + interp.failonleftovers(scope) + end + + # Add an override, and make sure it causes a failure + over1 = mkresource :scope => scope, :source => source, + :params => {:one => "yay"} + + scope.setoverride(over1) + + assert_raise(Puppet::ParseError) do + interp.failonleftovers(scope) + end + + end + + def test_evalnode + interp = mkinterp + interp.usenodes = false + scope = Parser::Scope.new(:interp => interp) + facts = Facter.to_hash + + # First make sure we get no failures when client is nil + assert_nothing_raised do + interp.evalnode(nil, scope, facts) + end + + # Now define a node + interp.newnode "mynode", :code => AST::ASTArray.new(:children => [ + resourcedef("file", "/tmp/testing", "owner" => "root") + ]) + + # Eval again, and make sure it does nothing + assert_nothing_raised do + interp.evalnode("mynode", scope, facts) + end + + assert_nil(scope.findresource("file[/tmp/testing]"), + "Eval'ed node with nodes off") + + # Now enable usenodes and make sure it works. + interp.usenodes = true + assert_nothing_raised do + interp.evalnode("mynode", scope, facts) + end + file = scope.findresource("file[/tmp/testing]") + + assert_instance_of(Puppet::Parser::Resource, file, + "Could not find file") + end + + # This is mostly used for the cfengine module + def test_specificclasses + interp = mkinterp :Classes => %w{klass1 klass2}, :UseNodes => false + + # Make sure it's not a failure to be missing classes, since + # we're using the cfengine class list, which is huge. + assert_nothing_raised do + interp.evaluate(nil, {}) + end + + interp.newclass("klass1", :code => AST::ASTArray.new(:children => [ + resourcedef("file", "/tmp/klass1", "owner" => "root") + ])) + interp.newclass("klass2", :code => AST::ASTArray.new(:children => [ + resourcedef("file", "/tmp/klass2", "owner" => "root") + ])) + + ret = nil + assert_nothing_raised do + ret = interp.evaluate(nil, {}) + end + + found = ret.flatten.collect do |res| res.name end + + assert(found.include?("/tmp/klass1"), "Did not evaluate klass1") + assert(found.include?("/tmp/klass2"), "Did not evaluate klass2") + end + + if defined? ActiveRecord::Base + # We need to make sure finished objects are stored in the db. + def test_finish_before_store + railsinit + interp = mkinterp + + node = interp.newnode ["myhost"], :code => AST::ASTArray.new(:children => [ + resourcedef("file", "/tmp/yay", :group => "root"), + defaultobj("file", :owner => "root") + ]) + + interp.newclass "myclass", :code => AST::ASTArray.new(:children => [ + ]) + + interp.newclass "sub", :parent => "myclass", + :code => AST::ASTArray.new(:children => [ + resourceoverride("file", "/tmp/yay", :owner => "root") + ] + ) + + # Now do the rails crap + Puppet[:storeconfigs] = true + + interp.evaluate("myhost", {}) + + # And then retrieve the object from rails + res = Puppet::Rails::RailsResource.find_by_restype_and_title("file", "/tmp/yay") + + assert(res, "Did not get resource from rails") + + param = res.rails_parameters.find_by_name("owner") + + assert(param, "Did not find owner param") + + assert_equal("root", param[:value]) + end + end end + +# $Id$ diff --git a/test/language/lexer.rb b/test/language/lexer.rb index 496087ba2..26b85b3b7 100644 --- a/test/language/lexer.rb +++ b/test/language/lexer.rb @@ -13,6 +13,10 @@ class TestLexer < Test::Unit::TestCase include PuppetTest def setup super + mklexer + end + + def mklexer @lexer = Puppet::Parser::Lexer.new() end @@ -79,9 +83,10 @@ class TestLexer < Test::Unit::TestCase def test_files textfiles() { |file| - @lexer.file = file + lexer = Puppet::Parser::Lexer.new() + lexer.file = file assert_nothing_raised() { - @lexer.fullscan() + lexer.fullscan() } Puppet::Type.allclear } @@ -149,6 +154,75 @@ class TestLexer < Test::Unit::TestCase assert_equal([[:AT, "@"], [:NAME, "type"], [:LBRACE, "{"], [false,false]],ret) end + + def test_namespace + @lexer.string = %{class myclass} + + assert_nothing_raised { + @lexer.fullscan + } + + assert_equal("myclass", @lexer.namespace) + + assert_nothing_raised do + @lexer.namepop + end + + assert_equal("", @lexer.namespace) + + @lexer.string = "class base { class sub { class more" + + assert_nothing_raised { + @lexer.fullscan + } + + assert_equal("base::sub::more", @lexer.namespace) + + assert_nothing_raised do + @lexer.namepop + end + + assert_equal("base::sub", @lexer.namespace) + + # Now try it with some fq names + mklexer + + @lexer.string = "class base { class sub::more {" + + assert_nothing_raised { + @lexer.fullscan + } + + assert_equal("base::sub::more", @lexer.namespace) + + assert_nothing_raised do + @lexer.namepop + end + + assert_equal("base", @lexer.namespace) + end + + def test_indefine + @lexer.string = %{define me} + + assert_nothing_raised { + @lexer.fullscan + } + + assert(@lexer.indefine?, "Lexer not considered in define") + + # Now make sure we throw an error when trying to nest defines. + assert_raise(Puppet::ParseError) do + @lexer.string = %{define another} + @lexer.fullscan + end + + assert_nothing_raised do + @lexer.indefine = false + end + + assert(! @lexer.indefine?, "Lexer still considered in define") + end end # $Id$ diff --git a/test/language/node.rb b/test/language/node.rb index 791c44874..251e4c4aa 100644 --- a/test/language/node.rb +++ b/test/language/node.rb @@ -8,7 +8,6 @@ class TestParser < Test::Unit::TestCase def setup super Puppet[:parseonly] = true - @parser = Puppet::Parser::Parser.new() end def test_simple_hostname @@ -41,48 +40,40 @@ class TestParser < Test::Unit::TestCase unless hostnames.is_a?(Array) hostnames = [ hostnames ] end + interp = nil assert_nothing_raised { - @parser.string = "node #{hostnames.join(", ")} { }" + interp = mkinterp :Code => "node #{hostnames.join(", ")} { }" } # Strip quotes hostnames.map! { |s| s.sub(/^'(.*)'$/, "\\1") } - ast = nil + + # parse assert_nothing_raised { - ast = @parser.parse + interp.send(:parsefiles) } - # Verify that the AST has the expected structure - # and that the leaves have the right hostnames in them - assert_kind_of(AST::ASTArray, ast) - assert_equal(1, ast.children.size) - nodedef = ast.children[0] - assert_kind_of(AST::NodeDef, nodedef) - assert_kind_of(AST::ASTArray, nodedef.names) - assert_equal(hostnames.size, nodedef.names.children.size) - hostnames.size.times do |i| - hostnode = nodedef.names.children[i] - assert_kind_of(AST::HostName, hostnode) - assert_equal(hostnames[i], hostnode.value) + + # Now make sure we can look up each of the names + hostnames.each do |name| + assert(interp.nodesearch_code(name), + "Could not find node %s" % name) end end def check_nonparseable(hostname) - assert_nothing_raised { - @parser.string = "node #{hostname} { }" - } - - assert_raise(Puppet::DevError, Puppet::ParseError) { - @parser.parse + interp = nil + assert_raise(Puppet::DevError, Puppet::ParseError, "#{hostname} passed") { + interp = mkinterp :Code => "node #{hostname} { }" + interp.send(:parsefiles) } end # Make sure we can find default nodes if there's no other entry def test_default_node Puppet[:parseonly] = false - @parser = Puppet::Parser::Parser.new() fileA = tempfile() fileB = tempfile() - @parser.string = %{ + code = %{ node mynode { file { "#{fileA}": ensure => file } } @@ -91,34 +82,25 @@ node default { file { "#{fileB}": ensure => file } } } - - # First make sure it parses - ast = nil + interp = nil assert_nothing_raised { - ast = @parser.parse - } - - args = { - :ast => ast, - :facts => {}, - :names => ["mynode"] + interp = mkinterp :Code => code } - # Make sure we get a config for "mynode" - trans = nil + # First make sure it parses assert_nothing_raised { - trans = Puppet::Parser::Scope.new.evaluate(args) + interp.send(:parsefiles) } - assert(trans, "Did not get config for mynode") + # Make sure we find our normal node + assert(interp.nodesearch("mynode"), + "Did not find normal node") - args[:names] = ["othernode"] - # Now make sure the default node is used - trans = nil - assert_nothing_raised { - trans = Puppet::Parser::Scope.new.evaluate(args) - } + # Now look for the default node + default = interp.nodesearch("someother") + assert(default, + "Did not find default node") - assert(trans, "Did not get config for default node") + assert_equal("default", default.fqname) end end diff --git a/test/language/parser.rb b/test/language/parser.rb index d6e176870..dbe48616a 100644 --- a/test/language/parser.rb +++ b/test/language/parser.rb @@ -8,15 +8,15 @@ class TestParser < Test::Unit::TestCase super Puppet[:parseonly] = true #@lexer = Puppet::Parser::Lexer.new() - @parser = Puppet::Parser::Parser.new() end def test_each_file textfiles { |file| + parser = mkparser Puppet.debug("parsing %s" % file) if __FILE__ == $0 assert_nothing_raised() { - @parser.file = file - @parser.parse + parser.file = file + parser.parse } Puppet::Type.eachtype { |type| @@ -32,18 +32,20 @@ class TestParser < Test::Unit::TestCase def test_failers failers { |file| + parser = mkparser Puppet.debug("parsing failer %s" % file) if __FILE__ == $0 assert_raise(Puppet::ParseError) { - @parser.file = file - ast = @parser.parse - Puppet::Parser::Scope.new.evaluate(:ast => ast) + parser.file = file + ast = parser.parse + scope = mkscope :interp => parser.interp + ast.evaluate :scope => scope } Puppet::Type.allclear } end def test_arrayrvalues - parser = Puppet::Parser::Parser.new() + parser = mkparser ret = nil file = tempfile() assert_nothing_raised { @@ -83,7 +85,7 @@ class TestParser < Test::Unit::TestCase } assert_nothing_raised("Could not parse multiple files") { - parser = Puppet::Parser::Parser.new() + parser = mkparser parser.file = manifest parser.parse } @@ -98,103 +100,18 @@ class TestParser < Test::Unit::TestCase f.puts "import \" no such file \"" end assert_raise(Puppet::ParseError) { - parser = Puppet::Parser::Parser.new() + parser = mkparser parser.file = manifest parser.parse } end - def test_defaults - basedir = File.join(tmpdir(), "defaulttesting") - @@tmpfiles << basedir - Dir.mkdir(basedir) - - defs1 = { - "testing" => "value" - } - - defs2 = { - "one" => "two", - "three" => "four", - "five" => false, - "seven" => "eight", - "nine" => true, - "eleven" => "twelve" - } - - mkdef = proc { |hash| - hash.collect { |arg, value| - "$%s = %s" % [arg, value] - }.join(", ") - } - manifest = File.join(basedir, "manifest") - File.open(manifest, "w") { |f| - f.puts " - define method(#{mkdef.call(defs1)}, $other) { - $variable = $testing - } - - define othermethod(#{mkdef.call(defs2)}, $goodness) { - $more = less - } - - method { - other => yayness - } - - othermethod { - goodness => rahness - } -" - - } - - ast = nil - assert_nothing_raised("Could not parse multiple files") { - parser = Puppet::Parser::Parser.new() - parser.file = manifest - ast = parser.parse - } - - assert(ast, "Did not receive AST while parsing defaults") - - scope = nil - assert_nothing_raised("Could not evaluate defaults parse tree") { - scope = Puppet::Parser::Scope.new() - scope.name = "parsetest" - scope.type = "parsetest" - objects = scope.evaluate(:ast => ast) - } - - method = nil - othermethod = nil - assert_nothing_raised { - method = scope.find { |child| - child.is_a?(Puppet::Parser::Scope) and child.type == "method" - } - defs1.each { |var, value| - curval = method.lookupvar(var) - assert_equal(value, curval, "Did not get default") - } - } - - assert_nothing_raised { - method = scope.find { |child| - child.is_a?(Puppet::Parser::Scope) and child.type == "othermethod" - } - defs2.each { |var, value| - curval = method.lookupvar(var) - assert_equal(value, curval, "Did not get default") - } - } - end - def test_trailingcomma path = tempfile() str = %{file { "#{path}": ensure => file, } } - parser = Puppet::Parser::Parser.new + parser = mkparser parser.string = str assert_nothing_raised("Could not parse trailing comma") { @@ -216,7 +133,7 @@ class TestParser < Test::Unit::TestCase f.puts %{import "#{imported}"\ninclude foo} end - parser = Puppet::Parser::Parser.new + parser = mkparser parser.file = importer # Make sure it parses fine @@ -256,7 +173,7 @@ class TestParser < Test::Unit::TestCase f.puts %{class local { file { "#{localmaker}": ensure => file }}} end - parser = Puppet::Parser::Parser.new + parser = mkparser parser.file = importer # Make sure it parses @@ -288,7 +205,7 @@ class TestParser < Test::Unit::TestCase f.puts %{class local { file { "#{file}": ensure => file }}} end - parser = Puppet::Parser::Parser.new + parser = mkparser parser.file = importer assert_nothing_raised { @@ -325,7 +242,7 @@ class TestParser < Test::Unit::TestCase f.puts %{import "subdir/subtwo"} end - parser = Puppet::Parser::Parser.new + parser = mkparser parser.file = top assert_nothing_raised { @@ -333,46 +250,21 @@ class TestParser < Test::Unit::TestCase } end - # Verify that collectable objects are marked that way. - def test_collectable - Puppet[:storeconfigs] = true - ["@port { ssh: protocols => tcp, number => 22 }", - "@port { ssh: protocols => tcp, number => 22; - smtp: protocols => tcp, number => 25 }"].each do |text| - parser = Puppet::Parser::Parser.new - parser.string = text - - ret = nil - assert_nothing_raised { - ret = parser.parse - } - - assert_instance_of(AST::ASTArray, ret) - - ret.each do |obj| - assert_instance_of(AST::ObjectDef, obj) - assert(obj.collectable, "Object was not marked collectable") - end - end - end - # Defaults are purely syntactical, so it doesn't make sense to be able to # collect them. def test_uncollectabledefaults string = "@Port { protocols => tcp }" - parser = Puppet::Parser::Parser.new - parser.string = string assert_raise(Puppet::ParseError) { - parser.parse + mkparser.parse(string) } end # Verify that we can parse collections def test_collecting Puppet[:storeconfigs] = true - text = "port <| |>" - parser = Puppet::Parser::Parser.new + text = "Port <| |>" + parser = mkparser parser.string = text ret = nil @@ -392,7 +284,7 @@ class TestParser < Test::Unit::TestCase File.open(file, "w") do |f| f.puts %{} end - parser = Puppet::Parser::Parser.new + parser = mkparser parser.file = file assert_nothing_raised { parser.parse @@ -412,7 +304,7 @@ node nodeA, nodeB { } end - parser = Puppet::Parser::Parser.new + parser = mkparser parser.file = file ast = nil assert_nothing_raised { @@ -423,7 +315,7 @@ node nodeA, nodeB { def test_emptyarrays str = %{$var = []\n} - parser = Puppet::Parser::Parser.new + parser = mkparser parser.string = str # Make sure it parses fine @@ -442,7 +334,7 @@ file { "/tmp/yayness": ensure => exists } } - parser = Puppet::Parser::Parser.new + parser = mkparser parser.string = str # Make sure it parses fine @@ -452,32 +344,21 @@ file { "/tmp/yayness": end def test_metaparams_in_definition_prototypes - parser = Puppet::Parser::Parser.new - - str1 = %{define mydef($schedule) {}} - parser.string = str1 + parser = mkparser - assert_raise(Puppet::ParseError) { - parser.parse - } - - str2 = %{define mydef($schedule = false) {}} - parser.string = str2 assert_raise(Puppet::ParseError) { - parser.parse + parser.parse %{define mydef($schedule) {}} } - str3 = %{define mydef($schedule = daily) {}} - parser.string = str3 - assert_nothing_raised { - parser.parse + parser.parse %{define adef($schedule = false) {}} + parser.parse %{define mydef($schedule = daily) {}} } end def test_parsingif - parser = Puppet::Parser::Parser.new() + parser = mkparser exec = proc do |val| %{exec { "/bin/echo #{val}": logoutput => true }} end @@ -494,6 +375,208 @@ file { "/tmp/yayness": assert_instance_of(Puppet::Parser::AST::IfStatement, ret) assert_instance_of(Puppet::Parser::AST::Else, ret.else) end + + def test_hostclass + parser = mkparser + interp = parser.interp + + assert_nothing_raised { + parser.parse %{class myclass { class other {} }} + } + assert(interp.findclass("", "myclass"), "Could not find myclass") + assert(interp.findclass("", "myclass::other"), "Could not find myclass::other") + + assert_nothing_raised { + parser.parse "class base {} + class container { + class deep::sub inherits base {} + }" + } + sub = interp.findclass("", "container::deep::sub") + assert(sub, "Could not find sub") + assert_equal("base", sub.parentclass.type) + end + + def test_topnamespace + parser = mkparser + parser.interp.clear + + # Make sure we put the top-level code into a class called "" in + # the "" namespace + assert_nothing_raised do + out = parser.parse "" + + assert_nil(out) + assert_nil(parser.interp.findclass("", "")) + end + + # Now try something a touch more complicated + parser.interp.clear + assert_nothing_raised do + out = parser.parse "Exec { path => '/usr/bin:/usr/sbin' }" + assert_instance_of(AST::ASTArray, out) + assert_equal("", parser.interp.findclass("", "").type) + assert_equal("", parser.interp.findclass("", "").namespace) + assert_equal(out.object_id, parser.interp.findclass("", "").code.object_id) + end + end + + # Make sure virtual and exported resources work appropriately. + def test_virtualresources + Puppet[:storeconfigs] = true + [:virtual, :exported].each do |form| + parser = mkparser + + if form == :virtual + at = "@" + else + at = "@@" + end + + check = proc do |res| + # Real resources get marked virtual when exported + if form == :virtual or res.is_a?(Puppet::Parser::Resource) + assert(res.virtual, "Resource #{res.class} is not virtual") + end + if form == :virtual + assert(! res.exported, "Resource #{res.type} is exported") + else + assert(res.exported, "Resource #{res.type} is not exported") + end + end + + ret = nil + assert_nothing_raised do + ret = parser.parse("#{at}file { '/tmp/testing': owner => root }") + end + + assert_equal("/tmp/testing", ret[0].title.value) + # We always get an astarray back, so... + assert_instance_of(AST::ResourceDef, ret[0]) + check.call(ret[0]) + + # Now let's try it with multiple resources in the same spec + assert_nothing_raised do + ret = parser.parse("#{at}file { ['/tmp/1', '/tmp/2']: owner => root }") + end + + assert_instance_of(AST::ASTArray, ret) + ret.each do |res| + assert_instance_of(AST::ResourceDef, res) + check.call(res) + end + + # Now evaluate these + scope = mkscope + + klass = scope.interp.newclass "" + scope.source = klass + + assert_nothing_raised do + ret.evaluate :scope => scope + end + + # Make sure we can find both of them + %w{/tmp/1 /tmp/2}.each do |title| + res = scope.findresource("file[#{title}]") + assert(res, "Could not find %s" % title) + check.call(res) + end + end + end + + def test_collections + Puppet[:storeconfigs] = true + [:virtual, :exported].each do |form| + parser = mkparser + + if form == :virtual + arrow = "<||>" + else + arrow = "<<||>>" + end + + check = proc do |coll| + assert_instance_of(AST::Collection, coll) + assert_equal(form, coll.form) + end + + ret = nil + assert_nothing_raised do + ret = parser.parse("File #{arrow}") + end + check.call(ret[0]) + end + end + + def test_collectionexpressions + %w{== !=}.each do |oper| + str = "File <| title #{oper} '/tmp/testing' |>" + + parser = mkparser + + res = nil + assert_nothing_raised do + res = parser.parse(str)[0] + end + + assert_instance_of(AST::Collection, res) + + query = res.query + assert_instance_of(AST::CollExpr, query) + + assert_equal(:virtual, query.form) + assert_equal("title", query.test1.value) + assert_equal("/tmp/testing", query.test2.value) + assert_equal(oper, query.oper) + end + end + + def test_collectionstatements + %w{and or}.each do |joiner| + str = "File <| title == '/tmp/testing' #{joiner} owner == root |>" + + parser = mkparser + + res = nil + assert_nothing_raised do + res = parser.parse(str)[0] + end + + assert_instance_of(AST::Collection, res) + + query = res.query + assert_instance_of(AST::CollExpr, query) + + assert_equal(joiner, query.oper) + assert_instance_of(AST::CollExpr, query.test1) + assert_instance_of(AST::CollExpr, query.test2) + end + end + + def test_collectionstatements_with_parens + [ + "(title == '/tmp/testing' and owner == root) or owner == wheel", + "(title == '/tmp/testing')" + ].each do |test| + str = "File <| #{test} |>" + parser = mkparser + + res = nil + assert_nothing_raised("Could not parse '#{test}'") do + res = parser.parse(str)[0] + end + + assert_instance_of(AST::Collection, res) + + query = res.query + assert_instance_of(AST::CollExpr, query) + + #assert_equal(joiner, query.oper) + #assert_instance_of(AST::CollExpr, query.test1) + #assert_instance_of(AST::CollExpr, query.test2) + end + end end # $Id$ diff --git a/test/language/rails.rb b/test/language/rails.rb deleted file mode 100755 index ada4e0915..000000000 --- a/test/language/rails.rb +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/ruby - -require 'puppet' -require 'puppet/rails' -require 'puppet/parser/interpreter' -require 'puppet/parser/parser' -require 'puppet/client' -require 'puppettest' - -class TestRails < Test::Unit::TestCase - include PuppetTest::ParserTesting - - def test_includerails - assert_nothing_raised { - require 'puppet/rails' - } - end - - # Don't do any tests w/out this class - if defined? ActiveRecord::Base - def test_hostcache - # First make some objects - bucket = mk_transtree do |object, depth, width| - # and mark some of them collectable - if width % 2 == 1 - object.collectable = true - end - end - - # Now collect our facts - facts = {} - Facter.each do |fact, value| facts[fact] = value end - - assert_nothing_raised { - Puppet::Rails.init - } - - # Now try storing our crap - host = nil - assert_nothing_raised { - host = Puppet::Rails::Host.store( - :objects => bucket, - :facts => facts, - :host => facts["hostname"] - ) - } - - assert(host, "Did not create host") - - host = nil - assert_nothing_raised { - host = Puppet::Rails::Host.find_by_name(facts["hostname"]) - } - assert(host, "Could not find host object") - - assert(host.rails_objects, "No objects on host") - - assert_equal(facts["hostname"], host.facts["hostname"], - "Did not retrieve facts") - - inline_test_objectcollection(host) - end - - # This is called from another test, it just makes sense to split it out some - def inline_test_objectcollection(host) - # XXX For some reason, find_all doesn't work here at all. - collectable = [] - host.rails_objects.each do |obj| - if obj.collectable? - collectable << obj - end - end - - assert(collectable.length > 0, "Found no collectable objects") - - collectable.each do |obj| - trans = nil - assert_nothing_raised { - trans = obj.to_trans - } - # Make sure that the objects do not retain their collectable - # nature. - assert(!trans.collectable, "Object from db was collectable") - end - - # Now find all collectable objects directly through database APIs - - list = Puppet::Rails::RailsObject.find_all_by_collectable(true) - - assert_equal(collectable.length, list.length, - "Did not get the right number of objects") - end - else - $stderr.puts "Install Rails for Rails and Caching tests" - end -end - -# $Id$ diff --git a/test/language/resource.rb b/test/language/resource.rb new file mode 100755 index 000000000..12b1e7d2a --- /dev/null +++ b/test/language/resource.rb @@ -0,0 +1,391 @@ +#!/usr/bin/ruby + +require 'puppettest' +require 'puppettest/resourcetesting' +require 'puppettest/railstesting' + +class TestResource < Test::Unit::TestCase + include PuppetTest + include PuppetTest::ParserTesting + include PuppetTest::ResourceTesting + include PuppetTest::RailsTesting + Parser = Puppet::Parser + AST = Parser::AST + + def setup + super + Puppet[:trace] = false + @interp, @scope, @source = mkclassframing + end + + def test_initialize + args = {:type => "resource", :title => "testing", + :source => @source, :scope => @scope} + # Check our arg requirements + args.each do |name, value| + try = args.dup.delete(name) + assert_raise(Puppet::DevError) do + Parser::Resource.new(try) + end + end + + args[:params] = paramify @source, :one => "yay", :three => "rah" + + res = nil + assert_nothing_raised do + res = Parser::Resource.new(args) + end + + # Make sure it got the parameters correctly. + assert_equal("yay", res[:one]) + assert_equal("rah", res[:three]) + + assert_equal({:one => "yay", :three => "rah"}, res.to_hash) + end + + def test_override + res = mkresource + + # Now verify we can't override with any random class + assert_raise(Puppet::ParseError) do + res.set paramify(@scope.findclass("other"), "one" => "boo").shift + end + + # And that we can with a subclass + assert_nothing_raised do + res.set paramify(@scope.findclass("sub1"), "one" => "boo").shift + end + + # And that a different subclass can override a different parameter + assert_nothing_raised do + res.set paramify(@scope.findclass("sub2"), "three" => "boo").shift + end + + # But not the same one + assert_raise(Puppet::ParseError) do + res.set paramify(@scope.findclass("sub2"), "one" => "something").shift + end + end + + def test_merge + # Start with the normal one + res = mkresource + + # Now create a resource from a different scope + other = mkresource :source => other, :params => {"one" => "boo"} + + # Make sure we can't merge it + assert_raise(Puppet::ParseError) do + res.merge(other) + end + + # Make one from a subscope + other = mkresource :source => "sub1", :params => {"one" => "boo"} + + # Make sure it merges + assert_nothing_raised do + res.merge(other) + end + + assert_equal("boo", res["one"]) + end + + def test_paramcheck + # First make a builtin resource + res = nil + assert_nothing_raised do + res = Parser::Resource.new :type => "file", :title => tempfile(), + :source => @source, :scope => @scope + end + + %w{path group source schedule subscribe}.each do |param| + assert_nothing_raised("Param %s was considered invalid" % param) do + res.paramcheck(param) + end + end + + %w{this bad noness}.each do |param| + assert_raise(Puppet::ParseError, "%s was considered valid" % param) do + res.paramcheck(param) + end + end + + # Now create a defined resource + assert_nothing_raised do + res = Parser::Resource.new :type => "resource", :title => "yay", + :source => @source, :scope => @scope + end + + %w{one two three schedule subscribe}.each do |param| + assert_nothing_raised("Param %s was considered invalid" % param) do + res.paramcheck(param) + end + end + + %w{this bad noness}.each do |param| + assert_raise(Puppet::ParseError, "%s was considered valid" % param) do + res.paramcheck(param) + end + end + end + + def test_to_trans + # First try translating a builtin resource + res = Parser::Resource.new :type => "file", :title => "/tmp", + :source => @source, :scope => @scope, + :params => paramify(@source, :owner => "nobody", :mode => "644") + + obj = nil + assert_nothing_raised do + obj = res.to_trans + end + + assert_instance_of(Puppet::TransObject, obj) + + assert_equal(obj.type, res.type) + assert_equal(obj.name, res.title) + + # TransObjects use strings, resources use symbols + hash = obj.to_hash.inject({}) { |h,a| h[a[0].intern] = a[1]; h } + assert_equal(hash, res.to_hash) + end + + def test_adddefaults + # Set some defaults at the top level + top = {:one => "fun", :two => "shoe"} + + @scope.setdefaults("resource", paramify(@source, top)) + + # Make a resource at that level + res = Parser::Resource.new :type => "resource", :title => "yay", + :source => @source, :scope => @scope + + # Add the defaults + assert_nothing_raised do + res.adddefaults + end + + # And make sure we got them + top.each do |p, v| + assert_equal(v, res[p]) + end + + # Now got a bit lower + other = @scope.newscope + + # And create a resource + lowerres = Parser::Resource.new :type => "resource", :title => "funtest", + :source => @source, :scope => other + + assert_nothing_raised do + lowerres.adddefaults + end + + # And check + top.each do |p, v| + assert_equal(v, lowerres[p]) + end + + # Now add some of our own defaults + lower = {:one => "shun", :three => "free"} + other.setdefaults("resource", paramify(@source, lower)) + otherres = Parser::Resource.new :type => "resource", :title => "yaytest", + :source => @source, :scope => other + + should = top.dup + # Make sure the lower defaults beat the higher ones. + lower.each do |p, v| should[p] = v end + + otherres.adddefaults + + should.each do |p,v| + assert_equal(v, otherres[p]) + end + end + + def test_evaluate + # Make a definition that we know will, um, do something + @interp.newdefine "evaltest", + :arguments => [%w{one}, ["two", stringobj("755")]], + :code => resourcedef("file", "/tmp", + "owner" => varref("one"), "mode" => varref("two")) + + res = Parser::Resource.new :type => "evaltest", :title => "yay", + :source => @source, :scope => @scope, + :params => paramify(@source, :one => "nobody") + + # Now try evaluating + ret = nil + assert_nothing_raised do + ret = res.evaluate + end + + # Make sure we can find our object now + result = @scope.findresource("file[/tmp]") + + # Now make sure we got the code we expected. + assert_instance_of(Puppet::Parser::Resource, result) + + assert_equal("file", result.type) + assert_equal("/tmp", result.title) + assert_equal("nobody", result["owner"]) + assert_equal("755", result["mode"]) + + # And that we cannot find the old resource + assert_nil(@scope.findresource("evaltest[yay]"), + "Evaluated resource was not deleted") + end + + def test_addoverrides + # First create an override for an object that doesn't yet exist + over1 = mkresource :source => "sub1", :params => {:one => "yay"} + + assert_nothing_raised do + @scope.setoverride(over1) + end + + assert(over1.override, "Override was not marked so") + + # Now make the resource + res = mkresource :source => "base", :params => {:one => "rah", + :three => "foo"} + + # And add it to our scope + @scope.setresource(res) + + # And make sure over1 has not yet taken affect + assert_equal("foo", res[:three], "Lost value") + + # Now add an immediately binding override + over2 = mkresource :source => "sub1", :params => {:three => "yay"} + + assert_nothing_raised do + @scope.setoverride(over2) + end + + # And make sure it worked + assert_equal("yay", res[:three], "Override 2 was ignored") + + # Now add our late-binding override + assert_nothing_raised do + res.addoverrides + end + + # And make sure they're still around + assert_equal("yay", res[:one], "Override 1 lost") + assert_equal("yay", res[:three], "Override 2 lost") + + # And finally, make sure that there are no remaining overrides + assert_nothing_raised do + res.addoverrides + end + end + + def test_proxymethods + res = Parser::Resource.new :type => "evaltest", :title => "yay", + :source => @source, :scope => @scope + + assert_equal("evaltest", res.type) + assert_equal("yay", res.title) + assert_equal(false, res.builtin?) + end + + def test_addmetaparams + res = Parser::Resource.new :type => "evaltest", :title => "yay", + :source => @source, :scope => @scope + + assert_nil(res[:schedule], "Got schedule already") + @scope.setvar("schedule", "daily") + + assert_nothing_raised do + res.addmetaparams + end + + assert_equal("daily", res[:schedule], "Did not get metaparam") + assert_nil(res[:noop], "Got invalid metaparam") + end + + def test_reference_conversion + # First try it as a normal string + ref = Parser::Resource::Reference.new(:type => "file", :title => "/tmp/ref1") + + # Now create an obj that uses it + res = mkresource :type => "file", :title => "/tmp/resource", + :params => {:require => ref} + + trans = nil + assert_nothing_raised do + trans = res.to_trans + end + + assert_instance_of(Array, trans["require"]) + assert_equal(["file", "/tmp/ref1"], trans["require"]) + + # Now try it when using an array of references. + two = Parser::Resource::Reference.new(:type => "file", :title => "/tmp/ref2") + res = mkresource :type => "file", :title => "/tmp/resource2", + :params => {:require => [ref, two]} + + trans = nil + assert_nothing_raised do + trans = res.to_trans + end + + assert_instance_of(Array, trans["require"][0]) + trans["require"].each do |val| + assert_instance_of(Array, val) + assert_equal("file", val[0]) + assert(val[1] =~ /\/tmp\/ref[0-9]/, + "Was %s instead of the file name" % val[1]) + end + end + + # This is a bit of a weird one -- the user should not actually know + # that components exist, so we want references to act like they're not + # builtin + def test_components_are_not_builtin + ref = Parser::Resource::Reference.new(:type => "component", :title => "yay") + + assert_nil(ref.builtintype, "Component was considered builtin") + end + if defined? ActiveRecord::Base + def test_store + railsinit + res = mkresource :type => "file", :title => "/tmp/testing", + :source => @source, :scope => @scope, + :params => {:owner => "root", :mode => "755"} + + # We also need a Rails Host to store under + host = Puppet::Rails::Host.new(:name => Facter.hostname) + + obj = nil + assert_nothing_raised do + obj = res.store(host) + end + + assert_instance_of(Puppet::Rails::RailsResource, obj) + + assert_nothing_raised do + Puppet::Util.benchmark(:info, "Saved host") do + host.save + end + end + + # Now make sure we can find it again + assert_nothing_raised do + obj = Puppet::Rails::RailsResource.find_by_host_id_and_restype_and_title( + host.id, res.type, res.title + ) + end + assert_instance_of(Puppet::Rails::RailsResource, obj) + + # Make sure we get the parameters back + obj.rails_parameters.each do |param| + assert_equal(res[param[:name]], param[:value], + "%s was different" % param[:name]) + end + end + end +end + +# $Id$ diff --git a/test/language/scope.rb b/test/language/scope.rb index 85189627d..b9401e38b 100755 --- a/test/language/scope.rb +++ b/test/language/scope.rb @@ -5,6 +5,8 @@ require 'puppet/parser/interpreter' require 'puppet/parser/parser' require 'puppet/client' require 'puppettest' +require 'puppettest/parsertesting' +require 'puppettest/resourcetesting' # so, what kind of things do we want to test? @@ -17,6 +19,7 @@ require 'puppettest' class TestScope < Test::Unit::TestCase include PuppetTest::ParserTesting + include PuppetTest::ResourceTesting def to_ary(hash) hash.collect { |key,value| @@ -35,7 +38,7 @@ class TestScope < Test::Unit::TestCase 10.times { |index| # slap some recursion in there - scope = Puppet::Parser::Scope.new(:parent => scope) + scope = mkscope(:parent => scope) scopes.push scope var = "var%s" % index @@ -97,8 +100,8 @@ class TestScope < Test::Unit::TestCase def test_declarative # set to declarative - top = Puppet::Parser::Scope.new(:declarative => true) - sub = Puppet::Parser::Scope.new(:parent => top) + top = mkscope(:declarative => true) + sub = mkscope(:parent => top) assert_nothing_raised { top.setvar("test","value") @@ -116,8 +119,8 @@ class TestScope < Test::Unit::TestCase def test_notdeclarative # set to not declarative - top = Puppet::Parser::Scope.new(:declarative => false) - sub = Puppet::Parser::Scope.new(:parent => top) + top = mkscope(:declarative => false) + sub = mkscope(:parent => top) assert_nothing_raised { top.setvar("test","value") @@ -133,106 +136,65 @@ class TestScope < Test::Unit::TestCase } end - def test_defaults - scope = nil - over = "over" - - scopes = [] - vars = [] - values = {} - ovalues = [] + def test_setdefaults + interp, scope, source = mkclassframing - defs = Hash.new { |hash,key| - hash[key] = Hash.new(nil) - } - - prevdefs = Hash.new { |hash,key| - hash[key] = Hash.new(nil) - } - - params = %w{a list of parameters that could be used for defaults} + # The setdefaults method doesn't really check what we're doing, + # so we're just going to use fake defaults here. - types = %w{a set of types that could be used to set defaults} + # First do a simple local lookup + params = paramify(source, :one => "fun", :two => "shoe") + origshould = {} + params.each do |p| origshould[p.name] = p end + assert_nothing_raised do + scope.setdefaults(:file, params) + end - 10.times { |index| - scope = Puppet::Parser::Scope.new(:parent => scope) - scopes.push scope + ret = nil + assert_nothing_raised do + ret = scope.lookupdefaults(:file) + end - tmptypes = [] - - # randomly create defaults for a random set of types - tnum = rand(5) - tnum.times { |t| - # pick a type - #Puppet.debug "Type length is %s" % types.length - #s = rand(types.length) - #Puppet.debug "Type num is %s" % s - #type = types[s] - #Puppet.debug "Type is %s" % s - type = types[rand(types.length)] - if tmptypes.include?(type) - Puppet.debug "Duplicate type %s" % type - redo - else - tmptypes.push type - end + assert_equal(origshould, ret) - Puppet.debug "type is %s" % type + # Now create a subscope and add some more params. + newscope = scope.newscope - d = {} + newparams = paramify(source, :one => "shun", :three => "free") + assert_nothing_raised { + newscope.setdefaults(:file, newparams) + } - # randomly assign some parameters - num = rand(4) - num.times { |n| - param = params[rand(params.length)] - if d.include?(param) - Puppet.debug "Duplicate param %s" % param - redo - else - d[param] = rand(1000) - end - } + # And make sure we get the appropriate ones back + should = {} + params.each do |p| should[p.name] = p end + newparams.each do |p| should[p.name] = p end - # and then add a consistent type - d["always"] = rand(1000) + assert_nothing_raised do + ret = newscope.lookupdefaults(:file) + end - d.each { |var,val| - defs[type][var] = val - } + assert_equal(should, ret) - assert_nothing_raised { - scope.setdefaults(type,to_ary(d)) - } - fdefs = nil - assert_nothing_raised { - fdefs = scope.lookupdefaults(type) - } + # Make sure we still only get the originals from the top scope + assert_nothing_raised do + ret = scope.lookupdefaults(:file) + end - # now, make sure that reassignment fails if we're - # in declarative mode - assert_raise(Puppet::ParseError) { - scope.setdefaults(type,[%w{always funtest}]) - } + assert_equal(origshould, ret) - # assert that we have collected the same values - assert_equal(defs[type],fdefs) + # Now create another scope and make sure we only get the top defaults + otherscope = scope.newscope + assert_equal(origshould, otherscope.lookupdefaults(:file)) - # now assert that our parent still finds the same defaults - # it got last time - if parent = scope.parent - unless prevdefs[type].nil? - assert_equal(prevdefs[type],parent.lookupdefaults(type)) - end - end - d.each { |var,val| - prevdefs[type][var] = val - } - } - } + # And make sure none of the scopes has defaults for other types + [scope, newscope, otherscope].each do |sc| + assert_equal({}, sc.lookupdefaults(:exec)) + end end def test_strinterp - scope = Puppet::Parser::Scope.new() + scope = mkscope() assert_nothing_raised { scope.setvar("test","value") @@ -264,313 +226,61 @@ class TestScope < Test::Unit::TestCase assert_equal("$test string", val) end - # Test some of the host manipulations - def test_hostlookup - top = Puppet::Parser::Scope.new() - - # Create a deep scope tree, so that we know we're doing a deeply recursive - # search. - mid1 = Puppet::Parser::Scope.new(:parent => top) - mid2 = Puppet::Parser::Scope.new(:parent => mid1) - mid3 = Puppet::Parser::Scope.new(:parent => mid2) - child1 = Puppet::Parser::Scope.new(:parent => mid3) - mida = Puppet::Parser::Scope.new(:parent => top) - midb = Puppet::Parser::Scope.new(:parent => mida) - midc = Puppet::Parser::Scope.new(:parent => midb) - child2 = Puppet::Parser::Scope.new(:parent => midc) - - # verify we can set a host - assert_nothing_raised("Could not create host") { - child1.setnode("testing", AST::Node.new( - :type => "testing", - :code => :notused - ) - ) - } - - # Verify we cannot redefine it - assert_raise(Puppet::ParseError, "Duplicate host creation succeeded") { - child2.setnode("testing", AST::Node.new( - :type => "testing", - :code => :notused - ) - ) - } - - # Now verify we can find the host again - host = nil - assert_nothing_raised("Host lookup failed") { - hash = top.node("testing") - host = hash[:node] - } - - assert(host, "Could not find host") - assert(host.code == :notused, "Host is not what we stored") - end - - # Verify that two statements about a file within the same scope tree - # will cause a conflict. - def test_noconflicts - filename = tempfile() - children = [] - - # create the parent class - children << classobj("one", :code => AST::ASTArray.new( - :children => [ - fileobj(filename, "owner" => "root") - ] - )) - - # now create a child class with differ values - children << classobj("two", - :code => AST::ASTArray.new( - :children => [ - fileobj(filename, "owner" => "bin") - ] - )) - - # Now call the child class - assert_nothing_raised("Could not add AST nodes for calling") { - children << AST::ObjectDef.new( - :type => nameobj("two"), - :name => nameobj("yayness"), - :params => astarray() - ) << AST::ObjectDef.new( - :type => nameobj("one"), - :name => nameobj("yayness"), - :params => astarray() - ) - } - - top = nil - assert_nothing_raised("Could not create top object") { - top = AST::ASTArray.new( - :children => children - ) - } - - objects = nil - scope = nil - - # Here's where we should encounter the failure. It should find that - # it has already created an object with that name, and this should result - # in some pukey-pukeyness. - assert_raise(Puppet::ParseError) { - scope = Puppet::Parser::Scope.new() - objects = scope.evaluate(:ast => top) - } - end - - # Verify that statements about the same element within the same scope - # cause a conflict. - def test_failonconflictinsamescope - filename = tempfile() - children = [] - - # Now call the child class - assert_nothing_raised("Could not add AST nodes for calling") { - children << fileobj(filename, "owner" => "root") - children << fileobj(filename, "owner" => "bin") - } - - top = nil - assert_nothing_raised("Could not create top object") { - top = AST::ASTArray.new( - :children => children - ) - } - - objects = nil - scope = nil - - # Here's where we should encounter the failure. It should find that - # it has already created an object with that name, and this should result - # in some pukey-pukeyness. - assert_raise(Puppet::ParseError) { - scope = Puppet::Parser::Scope.new() - scope.top = true - objects = scope.evaluate(:ast => top) - } - end - - # Verify that we override statements that we find within our scope - def test_suboverrides - filename = tempfile() - children = [] - - # create the parent class - children << classobj("parent", :code => AST::ASTArray.new( - :children => [ - fileobj(filename, "owner" => "root") - ] - )) - - # now create a child class with differ values - children << classobj("child", :parentclass => nameobj("parent"), - :code => AST::ASTArray.new( - :children => [ - fileobj(filename, "owner" => "bin") - ] - )) - - # Now call the child class - assert_nothing_raised("Could not add AST nodes for calling") { - children << AST::ObjectDef.new( - :type => nameobj("child"), - :name => nameobj("yayness"), - :params => astarray() - ) - } - - top = nil - assert_nothing_raised("Could not create top object") { - top = AST::ASTArray.new( - :children => children - ) - } - - objects = nil - scope = nil - assert_nothing_raised("Could not evaluate") { - scope = Puppet::Parser::Scope.new() - objects = scope.evaluate(:ast => top) - } - - assert_equal(1, objects.length, "Returned too many objects: %s" % - objects.inspect) - - assert_equal(1, objects[0].length, "Returned too many objects: %s" % - objects[0].inspect) - - assert_nothing_raised { - file = objects[0][0] - assert_equal("bin", file["owner"], "Value did not override correctly") - } - end - - def test_multipletypes - scope = Puppet::Parser::Scope.new() - children = [] - - # create the parent class - children << classobj("aclass") - children << classobj("aclass") - top = nil - assert_nothing_raised("Could not create top object") { - top = AST::ASTArray.new( - :children => children - ) - } - - scope = nil - assert_raise(Puppet::ParseError) { - scope = Puppet::Parser::Scope.new() - objects = top.evaluate(:scope => scope) - } - end - - # Verify that definitions have a different context than classes. - def test_newsubcontext - filename = tempfile() - children = [] - - # Create a component - children << compobj("comp", :code => AST::ASTArray.new( - :children => [ - fileobj(filename, "owner" => "root" ) - ] - )) - - # Now create a class that modifies the same file and also - # calls the component - children << classobj("klass", :code => AST::ASTArray.new( - :children => [ - fileobj(filename, "owner" => "bin" ), - AST::ObjectDef.new( - :type => nameobj("comp"), - :params => astarray() - ) - ] - )) - - # Now call the class - children << AST::ObjectDef.new( - :type => nameobj("klass"), - :params => astarray() - ) - - top = nil - assert_nothing_raised("Could not create top object") { - top = AST::ASTArray.new( - :children => children - ) - } + def test_setclass + interp, scope, source = mkclassframing - trans = nil - scope = nil - #assert_nothing_raised { - assert_raise(Puppet::ParseError, "A conflict was allowed") { - scope = Puppet::Parser::Scope.new() - trans = scope.evaluate(:ast => top) - } - # scope = Puppet::Parser::Scope.new() - # trans = scope.evaluate(:ast => top) - #} - end + base = scope.findclass("base") + assert(base, "Could not find base class") + assert(! scope.setclass?(base), "Class incorrectly set") + assert(! scope.classlist.include?("base"), "Class incorrectly in classlist") + assert_nothing_raised do + scope.setclass base + end - def test_defaultswithmultiplestatements - path = tempfile() + assert(scope.setclass?(base), "Class incorrectly unset") + assert(scope.classlist.include?("base"), "Class not in classlist") - stats = [] - stats << defaultobj("file", "group" => "root") - stats << fileobj(path, "owner" => "root") - stats << fileobj(path, "mode" => "755") + # Now try it with a normal string + assert_raise(Puppet::DevError) do + scope.setclass "string" + end - top = AST::ASTArray.new( - :file => __FILE__, - :line => __LINE__, - :children => stats - ) - scope = Puppet::Parser::Scope.new() - trans = nil - assert_nothing_raised { - trans = scope.evaluate(:ast => top) - } + assert(! scope.setclass?("string"), "string incorrectly set") - obj = trans.find do |obj| obj.is_a? Puppet::TransObject end + # Set "" in the class list, and make sure it doesn't show up in the return + top = scope.findclass("") + assert(top, "Could not find top class") + scope.setclass top - assert(obj, "Could not retrieve file obj") - assert_equal("root", obj["group"], "Default did not take") - assert_equal("root", obj["owner"], "Owner did not take") - assert_equal("755", obj["mode"], "Mode did not take") + assert(! scope.classlist.include?(""), "Class list included empty") end - def test_validclassnames - scope = Puppet::Parser::Scope.new() + def test_validtags + scope = mkscope() - ["a class", "Class", "a.class"].each do |bad| + ["a class", "a.class"].each do |bad| assert_raise(Puppet::ParseError, "Incorrectly allowed %s" % bad.inspect) do - scope.setclass(object_id, bad) + scope.tag(bad) end end - ["a-class", "a_class", "class", "yayNess"].each do |good| + ["a-class", "a_class", "Class", "class", "yayNess"].each do |good| assert_nothing_raised("Incorrectly banned %s" % good.inspect) do - scope.setclass(object_id, good) + scope.tag(good) end end end def test_tagfunction - scope = Puppet::Parser::Scope.new() + scope = mkscope() assert_nothing_raised { scope.function_tag(["yayness", "booness"]) } - assert(scope.classlist.include?("yayness"), "tag 'yayness' did not get set") - assert(scope.classlist.include?("booness"), "tag 'booness' did not get set") + assert(scope.tags.include?("yayness"), "tag 'yayness' did not get set") + assert(scope.tags.include?("booness"), "tag 'booness' did not get set") # Now verify that the 'tagged' function works correctly assert(scope.function_tagged("yayness"), @@ -583,77 +293,37 @@ class TestScope < Test::Unit::TestCase end def test_includefunction - scope = Puppet::Parser::Scope.new() - - one = tempfile() - two = tempfile() - - children = [] - - children << classobj("one", :code => AST::ASTArray.new( - :children => [ - fileobj(one, "owner" => "root") - ] - )) + interp = mkinterp + scope = mkscope :interp => interp - children << classobj("two", :code => AST::ASTArray.new( - :children => [ - fileobj(two, "owner" => "root") - ] - )) + myclass = interp.newclass "myclass" + otherclass = interp.newclass "otherclass" - children << Puppet::Parser::AST::Function.new( + function = Puppet::Parser::AST::Function.new( :name => "include", :ftype => :statement, :arguments => AST::ASTArray.new( - :children => [nameobj("one"), nameobj("two")] + :children => [nameobj("myclass"), nameobj("otherclass")] ) ) - top = AST::ASTArray.new(:children => children) - - #assert_nothing_raised { - # scope.function_include(["one", "two"]) - #} - - assert_nothing_raised { - scope.evaluate(:ast => top) - } - - - assert(scope.classlist.include?("one"), "tag 'one' did not get set") - assert(scope.classlist.include?("two"), "tag 'two' did not get set") + assert_nothing_raised do + function.evaluate :scope => scope + end - # Now verify that the 'tagged' function works correctly - assert(scope.function_tagged("one"), - "tagged function incorrectly returned false") - assert(scope.function_tagged("two"), - "tagged function incorrectly returned false") + [myclass, otherclass].each do |klass| + assert(scope.setclass?(klass), + "%s was not set" % klass.fqname) + end end def test_definedfunction - scope = Puppet::Parser::Scope.new() - - one = tempfile() - two = tempfile() - - children = [] - - children << classobj("one", :code => AST::ASTArray.new( - :children => [ - fileobj(one, "owner" => "root") - ] - )) - - children << classobj("two", :code => AST::ASTArray.new( - :children => [ - fileobj(two, "owner" => "root") - ] - )) - - top = AST::ASTArray.new(:children => children) + interp = mkinterp + %w{one two}.each do |name| + interp.newdefine name + end - top.evaluate(:scope => scope) + scope = mkscope :interp => interp assert_nothing_raised { %w{one two file user}.each do |type| @@ -664,40 +334,6 @@ class TestScope < Test::Unit::TestCase assert(!scope.function_defined(["nopeness"]), "Class 'nopeness' was incorrectly considered defined") } - - - end - - # Make sure components acquire defaults. - def test_defaultswithcomponents - children = [] - - # Create a component - filename = tempfile() - args = AST::ASTArray.new( - :file => tempfile(), - :line => rand(100), - :children => [nameobj("argument")] - ) - children << compobj("comp", :args => args, :code => AST::ASTArray.new( - :children => [ - fileobj(filename, "owner" => varref("argument") ) - ] - )) - - # Create a default - children << defaultobj("comp", "argument" => "yayness") - - # lastly, create an object that calls our third component - children << objectdef("comp", "boo", {"argument" => "parentfoo"}) - - trans = assert_evaluate(children) - - flat = trans.flatten - - assert(!flat.empty?, "Got no objects back") - - assert_equal("parentfoo", flat[0]["owner"], "default did not take") end # Make sure we know what we consider to be truth. @@ -714,7 +350,7 @@ class TestScope < Test::Unit::TestCase # Verify scope context is handled correctly. def test_scopeinside - scope = Puppet::Parser::Scope.new() + scope = mkscope() one = :one two = :two @@ -763,9 +399,10 @@ class TestScope < Test::Unit::TestCase end if defined? ActiveRecord - # Verify that we recursively mark as collectable the results of collectable + # Verify that we recursively mark as exported the results of collectable # components. - def test_collectablecomponents + def test_exportedcomponents + interp, scope, source = mkclassframing children = [] args = AST::ASTArray.new( @@ -773,47 +410,44 @@ class TestScope < Test::Unit::TestCase :line => rand(100), :children => [nameobj("arg")] ) + # Create a top-level component - children << compobj("one", :args => args) + interp.newdefine "one", :arguments => [%w{arg}], + :code => AST::ASTArray.new( + :children => [ + resourcedef("file", "/tmp", {"owner" => varref("arg")}) + ] + ) # And a component that calls it - children << compobj("two", :args => args, :code => AST::ASTArray.new( - :children => [ - objectdef("one", "ptest", {"arg" => "parentfoo"}) - ] - )) + interp.newdefine "two", :arguments => [%w{arg}], + :code => AST::ASTArray.new( + :children => [ + resourcedef("one", "ptest", {"arg" => varref("arg")}) + ] + ) # And then a third component that calls the second - children << compobj("three", :args => args, :code => AST::ASTArray.new( - :children => [ - objectdef("two", "yay", {"arg" => "parentfoo"}) - ] - )) + interp.newdefine "three", :arguments => [%w{arg}], + :code => AST::ASTArray.new( + :children => [ + resourcedef("two", "yay", {"arg" => varref("arg")}) + ] + ) # lastly, create an object that calls our third component - obj = objectdef("three", "boo", {"arg" => "parentfoo"}) - - # And mark it as collectable - obj.collectable = true + obj = resourcedef("three", "boo", {"arg" => "parentfoo"}) - children << obj + # And mark it as exported + obj.exported = true - top = nil - assert_nothing_raised("Could not create top object") { - top = AST::ASTArray.new( - :children => children - ) - } + obj.evaluate :scope => scope - trans = nil - scope = nil - assert_nothing_raised { - scope = Puppet::Parser::Scope.new() - trans = scope.evaluate(:ast => top) - } + # And then evaluate it + interp.evaliterate(scope) %w{file}.each do |type| - objects = scope.exported(type) + objects = scope.lookupexported(type) assert(!objects.empty?, "Did not get an exported %s" % type) end @@ -832,11 +466,11 @@ class TestScope < Test::Unit::TestCase File.open(file, "w") { |f| f.puts " class yay { - @host { myhost: ip => \"192.168.0.2\" } + @@host { myhost: ip => \"192.168.0.2\" } } include yay -@host { puppet: ip => \"192.168.0.3\" } -host <||>" +@@host { puppet: ip => \"192.168.0.3\" } +Host <<||>>" } interp = nil @@ -853,7 +487,7 @@ host <||>" # if we pull it up from the database. 2.times { |i| assert_nothing_raised { - objects = interp.run("localhost", {}) + objects = interp.run("localhost", {"hostname" => "localhost"}) } flat = objects.flatten @@ -863,53 +497,95 @@ host <||>" end } end + else + $stderr.puts "No ActiveRecord -- skipping collection tests" + end - # Verify that we cannot override differently exported objects - def test_exportedoverrides - filename = tempfile() - children = [] + # Make sure tags behave appropriately. + def test_tags + interp, scope, source = mkclassframing - obj = fileobj(filename, "owner" => "root") - obj.collectable = true - # create the parent class - children << classobj("parent", :code => AST::ASTArray.new( - :children => [ - obj - ] - )) - - # now create a child class with differ values - children << classobj("child", :parentclass => nameobj("parent"), - :code => AST::ASTArray.new( - :children => [ - fileobj(filename, "owner" => "bin") - ] - )) - - # Now call the child class - assert_nothing_raised("Could not add AST nodes for calling") { - children << AST::ObjectDef.new( - :type => nameobj("child"), - :name => nameobj("yayness"), - :params => astarray() - ) - } + # First make sure we can only set legal tags + ["an invalid tag", "-anotherinvalid", "bad*tag"].each do |tag| + assert_raise(Puppet::ParseError, "Tag #{tag} was considered valid") do + scope.tag tag + end + end - top = nil - assert_nothing_raised("Could not create top object") { - top = AST::ASTArray.new( - :children => children - ) - } + # Now make sure good tags make it through. + tags = %w{good-tag yaytag GoodTag another_tag} + tags.each do |tag| + assert_nothing_raised("Tag #{tag} was considered invalid") do + scope.tag tag + end + end - objects = nil - scope = nil - assert_raise(Puppet::ParseError, "Incorrectly allowed override") { - scope = Puppet::Parser::Scope.new() - objects = scope.evaluate(:ast => top) - } + # And make sure we get each of them. + ptags = scope.tags + tags.each do |tag| + assert(ptags.include?(tag), "missing #{tag}") + end + + + # Now create a subscope and set some tags there + newscope = scope.newscope(:type => 'subscope') + + # set some tags + newscope.tag "onemore", "yaytag" + + # And make sure we get them plus our parent tags + assert_equal((ptags + %w{onemore subscope}).sort, newscope.tags.sort) end - else - $stderr.puts "No ActiveRecord -- skipping collection tests" + + # Make sure we successfully translate objects + def test_translate + interp, scope, source = mkclassframing + + # Create a define that we'll be using + interp.newdefine("wrapper", :code => AST::ASTArray.new(:children => [ + resourcedef("file", varref("name"), "owner" => "root") + ])) + + # Now create a resource that uses that define + define = mkresource(:type => "wrapper", :title => "/tmp/testing", + :scope => scope, :source => source, :params => :none) + + scope.setresource define + + # And a normal resource + scope.setresource mkresource(:type => "file", :title => "/tmp/rahness", + :scope => scope, :source => source, + :params => {:owner => "root"}) + + # Evaluate the the define thing. + define.evaluate + + # Now the scope should have a resource and a subscope. Translate the + # whole thing. + ret = nil + assert_nothing_raised do + ret = scope.translate + end + + assert_instance_of(Puppet::TransBucket, ret) + + ret.each do |obj| + assert(obj.is_a?(Puppet::TransBucket) || obj.is_a?(Puppet::TransObject), + "Got a non-transportable object %s" % obj.class) + end + + rahness = ret.find { |c| c.type == "file" and c.name == "/tmp/rahness" } + assert(rahness, "Could not find top-level file") + assert_equal("root", rahness["owner"]) + + bucket = ret.find { |c| c.class == Puppet::TransBucket and c.name == "/tmp/testing" } + assert(bucket, "Could not find define bucket") + + testing = bucket.find { |c| c.type == "file" and c.name == "/tmp/testing" } + assert(testing, "Could not find define file") + assert_equal("root", testing["owner"]) + end end + +# $Id$ diff --git a/test/language/snippets.rb b/test/language/snippets.rb index 05ac066eb..f2597c8d0 100755 --- a/test/language/snippets.rb +++ b/test/language/snippets.rb @@ -12,7 +12,7 @@ class TestSnippets < Test::Unit::TestCase include ObjectSpace def self.snippetdir - PuppetTest.exampledir "code", "snippets" + PuppetTest.datadir "snippets" end def snippet(name) @@ -464,6 +464,13 @@ class TestSnippets < Test::Unit::TestCase @@tmpfiles << "/tmp/emptyexextest" end + def snippet_multisubs(trans) + path = "/tmp/multisubtest" + assert(FileTest.exists?(path), "Did not create file") + assert_equal("sub2", File.read(path), "sub2 did not override content") + assert_equal(0755, filemode(path), "sub1 did not override mode") + end + def disabled_snippet_dirchmod(trans) dirs = %w{a b}.collect { |letter| "/tmp/dirchmodtest%s" % letter diff --git a/test/language/transportable.rb b/test/language/transportable.rb index 217bb3370..a25500ee8 100755 --- a/test/language/transportable.rb +++ b/test/language/transportable.rb @@ -3,6 +3,7 @@ require 'puppet' require 'puppet/transportable' require 'puppettest' +require 'puppettest/parsertesting' require 'yaml' class TestTransportable < Test::Unit::TestCase diff --git a/test/lib/puppettest.rb b/test/lib/puppettest.rb index e7ee99c35..c6ce54b93 100644 --- a/test/lib/puppettest.rb +++ b/test/lib/puppettest.rb @@ -4,14 +4,14 @@ require 'test/unit' module PuppetTest # Find the root of the Puppet tree; this is not the test directory, but # the parent of that dir. - def basedir + def basedir(*list) unless defined? @@basedir case $0 when /rake_test_loader/ @@basedir = File.dirname(Dir.getwd) else dir = nil - if /^#{File::SEPARATOR}.+\.rb/ + if $0 =~ /^#{File::SEPARATOR}.+\.rb/ dir = $0 else dir = File.join(Dir.getwd, $0) @@ -20,15 +20,19 @@ module PuppetTest @@basedir = dir end end - @@basedir + if list.empty? + @@basedir + else + File.join(@@basedir, *list) + end end def cleanup(&block) @@cleaners << block end - def datadir - File.join(basedir, "test", "data") + def datadir(*list) + File.join(basedir, "test", "data", *list) end def exampledir(*args) @@ -45,6 +49,18 @@ module PuppetTest module_function :basedir, :datadir, :exampledir + # Rails clobbers RUBYLIB, thanks + def libsetup + curlibs = ENV["RUBYLIB"].split(":") + $:.reject do |dir| dir =~ /^\/usr/ end.each do |dir| + unless curlibs.include?(dir) + curlibs << dir + end + end + + ENV["RUBYLIB"] = curlibs.join(":") + end + def rake? $0 =~ /rake_test_loader/ end @@ -90,6 +106,7 @@ module PuppetTest Puppet::Log.level = :debug #$VERBOSE = 1 Puppet.info @method_name + Puppet[:trace] = true end #if $0 =~ /.+\.rb/ or Puppet[:debug] # Puppet::Log.newdestination :console @@ -169,7 +186,9 @@ module PuppetTest @@tmppids.clear Puppet::Type.allclear Puppet::Storage.clear - Puppet::Rails.clear + if defined? Puppet::Rails + Puppet::Rails.clear + end Puppet.clear @memoryatend = Puppet::Util.memory diff --git a/test/lib/puppettest/parsertesting.rb b/test/lib/puppettest/parsertesting.rb index e0606c501..1a6b5b12a 100644 --- a/test/lib/puppettest/parsertesting.rb +++ b/test/lib/puppettest/parsertesting.rb @@ -1,4 +1,5 @@ require 'puppettest' +require 'puppet/rails' module PuppetTest::ParserTesting include PuppetTest @@ -6,23 +7,36 @@ module PuppetTest::ParserTesting def astarray(*args) AST::ASTArray.new( - :children => args - ) + :children => args + ) end - def classobj(name, args = {}) - args[:type] ||= nameobj(name) - args[:code] ||= AST::ASTArray.new( - :file => __FILE__, - :line => __LINE__, - :children => [ - varobj("%svar" % name, "%svalue" % name), - fileobj("/%s" % name) - ] - ) - assert_nothing_raised("Could not create class %s" % name) { - return AST::ClassDef.new(args) - } + def mkinterp(args = {}) + args[:Code] ||= "" + args[:Local] ||= true + Puppet::Parser::Interpreter.new(args) + end + + def mkparser + Puppet::Parser::Parser.new(mkinterp) + end + + def mkscope(hash = {}) + hash[:interp] ||= mkinterp + hash[:source] ||= (hash[:interp].findclass("", "") || + hash[:interp].newclass("")) + + unless hash[:source] + raise "Could not find source for scope" + end + Puppet::Parser::Scope.new(hash) + end + + def classobj(name, hash = {}) + hash[:file] ||= __FILE__ + hash[:line] ||= __LINE__ + hash[:type] ||= name + AST::HostClass.new(hash) end def tagobj(*names) @@ -40,50 +54,47 @@ module PuppetTest::ParserTesting } end - def compobj(name, args = {}) - args[:file] ||= tempfile() - args[:line] ||= rand(100) - args[:type] ||= nameobj(name) - args[:args] ||= AST::ASTArray.new( - :file => tempfile(), - :line => rand(100), - :children => [] - ) - args[:code] ||= AST::ASTArray.new( - :file => tempfile(), - :line => rand(100), - :children => [ - varobj("%svar" % name, "%svalue" % name), - fileobj("/%s" % name) - ] - ) - assert_nothing_raised("Could not create compdef %s" % name) { - return AST::CompDef.new(args) + def resourcedef(type, title, params) + unless title.is_a?(AST) + title = stringobj(title) + end + assert_nothing_raised("Could not create %s %s" % [type, title]) { + return AST::ResourceDef.new( + :file => __FILE__, + :line => __LINE__, + :title => title, + :type => type, + :params => resourceinst(params) + ) } end - def objectdef(type, name, params) + def resourceoverride(type, title, params) assert_nothing_raised("Could not create %s %s" % [type, name]) { - return AST::ObjectDef.new( - :file => __FILE__, - :line => __LINE__, - :name => stringobj(name), - :type => nameobj(type), - :params => objectinst(params) - ) + return AST::ResourceOverride.new( + :file => __FILE__, + :line => __LINE__, + :object => resourceref(type, title), + :type => type, + :params => resourceinst(params) + ) + } + end + + def resourceref(type, title) + assert_nothing_raised("Could not create %s %s" % [type, title]) { + return AST::ResourceRef.new( + :file => __FILE__, + :line => __LINE__, + :type => type, + :title => stringobj(title) + ) } end def fileobj(path, hash = {"owner" => "root"}) assert_nothing_raised("Could not create file %s" % path) { - return objectdef("file", path, hash) - # return AST::ObjectDef.new( - # :file => tempfile(), - # :line => rand(100), - # :name => stringobj(path), - # :type => nameobj("file"), - # :params => objectinst(hash) - # ) + return resourcedef("file", path, hash) } end @@ -123,12 +134,12 @@ module PuppetTest::ParserTesting } end - def objectinst(hash) - assert_nothing_raised("Could not create object instance") { + def resourceinst(hash) + assert_nothing_raised("Could not create resource instance") { params = hash.collect { |param, value| - objectparam(param, value) + resourceparam(param, value) } - return AST::ObjectInst.new( + return AST::ResourceInst.new( :file => tempfile(), :line => rand(100), :children => params @@ -136,16 +147,16 @@ module PuppetTest::ParserTesting } end - def objectparam(param, value) + def resourceparam(param, value) # Allow them to pass non-strings in if value.is_a?(String) value = stringobj(value) end assert_nothing_raised("Could not create param %s" % param) { - return AST::ObjectParam.new( + return AST::ResourceParam.new( :file => tempfile(), :line => rand(100), - :param => nameobj(param), + :param => param, :value => value ) } @@ -194,10 +205,10 @@ module PuppetTest::ParserTesting def defaultobj(type, params) pary = [] params.each { |p,v| - pary << AST::ObjectParam.new( + pary << AST::ResourceParam.new( :file => __FILE__, :line => __LINE__, - :param => nameobj(p), + :param => p, :value => stringobj(v) ) } @@ -208,10 +219,10 @@ module PuppetTest::ParserTesting ) assert_nothing_raised("Could not create defaults for %s" % type) { - return AST::TypeDefaults.new( + return AST::ResourceDefaults.new( :file => __FILE__, :line => __LINE__, - :type => typeobj(type), + :type => type, :params => past ) } @@ -274,7 +285,7 @@ module PuppetTest::ParserTesting return obj end - def mk_transbucket(*objects) + def mk_transbucket(*resources) bucket = nil assert_nothing_raised { bucket = Puppet::TransBucket.new @@ -282,12 +293,12 @@ module PuppetTest::ParserTesting bucket.type = "yaytype" } - objects.each { |o| bucket << o } + resources.each { |o| bucket << o } return bucket end - # Make a tree of objects, yielding if desired + # Make a tree of resources, yielding if desired def mk_transtree(depth = 4, width = 2) top = nil assert_nothing_raised { @@ -300,7 +311,7 @@ module PuppetTest::ParserTesting file = tempfile() depth.times do |i| - objects = [] + resources = [] width.times do |j| path = tempfile + i.to_s obj = Puppet::TransObject.new("file", path) @@ -312,10 +323,10 @@ module PuppetTest::ParserTesting yield(obj, i, j) end - objects << obj + resources << obj end - newbucket = mk_transbucket(*objects) + newbucket = mk_transbucket(*resources) bucket.push newbucket bucket = newbucket @@ -324,13 +335,13 @@ module PuppetTest::ParserTesting return top end - # Take a list of AST objects, evaluate them, and return the results + # Take a list of AST resources, evaluate them, and return the results def assert_evaluate(children) top = nil assert_nothing_raised("Could not create top object") { top = AST::ASTArray.new( - :children => children - ) + :children => children + ) } trans = nil diff --git a/test/lib/puppettest/railstesting.rb b/test/lib/puppettest/railstesting.rb new file mode 100644 index 000000000..3a32d0c9e --- /dev/null +++ b/test/lib/puppettest/railstesting.rb @@ -0,0 +1,34 @@ +module PuppetTest::RailsTesting + Parser = Puppet::Parser + AST = Puppet::Parser::AST + include PuppetTest::ParserTesting + + def railsinit + Puppet::Rails.init + end + + def railsresource(type = "file", title = "/tmp/testing", params = {}) + railsinit + + # We need a host for resources + host = Puppet::Rails::Host.new(:name => Facter.value("hostname")) + + # Now build a resource + resource = host.rails_resources.build( + :title => title, :restype => type, + :exported => true + ) + + # Now add some params + params.each do |param, value| + resource.rails_parameters.build( + :name => param, :value => value + ) + end + + # Now save the whole thing + host.save + end +end + +# $Id$ diff --git a/test/lib/puppettest/resourcetesting.rb b/test/lib/puppettest/resourcetesting.rb new file mode 100644 index 000000000..a7d183ca7 --- /dev/null +++ b/test/lib/puppettest/resourcetesting.rb @@ -0,0 +1,64 @@ +module PuppetTest::ResourceTesting + Parser = Puppet::Parser + AST = Puppet::Parser::AST + def mkclassframing(interp = nil) + interp ||= mkinterp + + interp.newdefine("resource", :arguments => [%w{one}, %w{two value}, %w{three}]) + interp.newclass("") + source = interp.newclass("base") + interp.newclass("sub1", :parent => "base") + interp.newclass("sub2", :parent => "base") + interp.newclass("other") + + scope = Parser::Scope.new(:interp => interp) + scope.source = source + + return interp, scope, source + end + + def mkresource(args = {}) + + if args[:scope] and ! args[:source] + args[:source] = args[:scope].source + end + + unless args[:scope] + unless defined? @scope + raise "Must set @scope to mkresource" + end + end + + {:type => "resource", :title => "testing", + :source => @source, :scope => @scope}.each do |param, value| + args[param] ||= value + end + + unless args[:source].is_a?(Puppet::Parser::AST::HostClass) + args[:source] = args[:scope].findclass(args[:source]) + end + + params = args[:params] || {:one => "yay", :three => "rah"} + if args[:params] == :none + args.delete(:params) + else + args[:params] = paramify args[:source], params + end + + Parser::Resource.new(args) + end + + def param(name, value, source) + Parser::Resource::Param.new(:name => name, :value => value, :source => source) + end + + def paramify(source, hash) + hash.collect do |name, value| + Parser::Resource::Param.new( + :name => name, :value => value, :source => source + ) + end + end +end + +# $Id$ diff --git a/test/lib/puppettest/support/utils.rb b/test/lib/puppettest/support/utils.rb index 00ea1a1c9..ea2d5483c 100644 --- a/test/lib/puppettest/support/utils.rb +++ b/test/lib/puppettest/support/utils.rb @@ -102,7 +102,7 @@ module PuppetTest # a list of files that we can parse for testing def textfiles - textdir = File.join(exampledir,"code", "snippets") + textdir = datadir "snippets" Dir.entries(textdir).reject { |f| f =~ /^\./ or f =~ /fail/ }.each { |f| @@ -111,7 +111,7 @@ module PuppetTest end def failers - textdir = File.join(exampledir,"code", "failers") + textdir = datadir "failers" # only parse this one file now files = Dir.entries(textdir).reject { |file| file =~ %r{\.swp} diff --git a/test/other/config.rb b/test/other/config.rb index 0afe8979b..09d6abe3b 100755 --- a/test/other/config.rb +++ b/test/other/config.rb @@ -3,9 +3,11 @@ require 'puppet' require 'puppet/config' require 'puppettest' +require 'puppettest/parsertesting' class TestConfig < Test::Unit::TestCase include PuppetTest + include PuppetTest::ParserTesting def check_for_users count = Puppet::Type.type(:user).inject(0) { |c,o| @@ -39,21 +41,16 @@ class TestConfig < Test::Unit::TestCase } Puppet[:parseonly] = true - parser = Puppet::Parser::Parser.new() - objects = nil - assert_nothing_raised("Could not parse generated manifest") { - parser.string = manifest - objects = parser.parse - } - scope = Puppet::Parser::Scope.new - assert_nothing_raised("Could not compile objects") { - scope.evaluate(:ast => objects) - } + interp = nil + assert_nothing_raised do + interp = mkinterp :Code => manifest, :UseNodes => false + end + trans = nil - assert_nothing_raised("Could not convert objects to transportable") { - trans = scope.to_trans - } + assert_nothing_raised do + trans = interp.evaluate(nil, {}) + end assert_nothing_raised("Could not instantiate objects") { trans.to_type } diff --git a/test/rails/rails.rb b/test/rails/rails.rb new file mode 100755 index 000000000..c780d7698 --- /dev/null +++ b/test/rails/rails.rb @@ -0,0 +1,87 @@ +#!/usr/bin/ruby + +require 'puppet' +require 'puppet/rails' +require 'puppet/parser/interpreter' +require 'puppet/parser/parser' +require 'puppet/client' +require 'puppettest' +require 'puppettest/parsertesting' +require 'puppettest/resourcetesting' +require 'puppettest/railstesting' + +class TestRails < Test::Unit::TestCase + include PuppetTest::ParserTesting + include PuppetTest::ResourceTesting + include PuppetTest::RailsTesting + + def test_includerails + assert_nothing_raised { + require 'puppet/rails' + } + end + + # Don't do any tests w/out this class + if defined? ActiveRecord::Base + def test_hostcache + @interp, @scope, @source = mkclassframing + # First make some objects + resources = [] + 20.times { |i| + resources << mkresource(:type => "file", :title => "/tmp/file#{i.to_s}", + :params => {:owner => "user#{i}"}) + } + + # Now collect our facts + facts = Facter.to_hash + + assert_nothing_raised { + Puppet::Rails.init + } + + # Now try storing our crap + host = nil + assert_nothing_raised { + host = Puppet::Rails::Host.store( + :resources => resources, + :facts => facts, + :name => facts["hostname"], + :classes => ["one", "two::three", "four"] + ) + } + + assert(host, "Did not create host") + + host = nil + assert_nothing_raised { + host = Puppet::Rails::Host.find_by_name(facts["hostname"]) + } + assert(host, "Could not find host object") + + assert(host.rails_resources, "No objects on host") + + assert_equal(facts["hostname"], host.facts["hostname"], + "Did not retrieve facts") + + count = 0 + host.rails_resources.each do |resource| + count += 1 + i = nil + if resource[:title] =~ /file([0-9]+)/ + i = $1 + else + raise "Got weird resource %s" % resource.inspect + end + + assert_equal("user#{i}", + resource.rails_parameters.find_by_name("owner")[:value]) + end + + assert_equal(20, count, "Did not get enough resources") + end + else + $stderr.puts "Install Rails for Rails and Caching tests" + end +end + +# $Id$ diff --git a/test/rails/railsparameter.rb b/test/rails/railsparameter.rb new file mode 100755 index 000000000..7dc7ef5dd --- /dev/null +++ b/test/rails/railsparameter.rb @@ -0,0 +1,50 @@ +#!/usr/bin/ruby + +require 'puppet' +require 'puppet/rails' +require 'puppettest' +require 'puppettest/railstesting' + +class TestRailsParameter < Test::Unit::TestCase + include PuppetTest::RailsTesting + + # Don't do any tests w/out this class + if defined? ActiveRecord::Base + # Create a resource param from a rails parameter + def test_to_resourceparam + railsinit + # First create our parameter + rparam = nil + hash = { :name => :myparam, :value => "myval", + :file => __FILE__, :line => __LINE__} + assert_nothing_raised do + rparam = Puppet::Rails::RailsParameter.new(hash) + end + + assert(rparam, "Did not create rails parameter") + + # The id doesn't get assigned until we save + rparam.save + + # Now create a source + interp = mkinterp + source = interp.newclass "myclass" + + # And try to convert our parameter + pparam = nil + assert_nothing_raised do + pparam = rparam.to_resourceparam(source) + end + + + assert_instance_of(Puppet::Parser::Resource::Param, pparam) + hash.each do |name, value| + assert_equal(value, pparam.send(name), "%s was not equal" % name) + end + end + else + $stderr.puts "Install Rails for Rails and Caching tests" + end +end + +# $Id$ diff --git a/test/rails/railsresource.rb b/test/rails/railsresource.rb new file mode 100755 index 000000000..86c3b4908 --- /dev/null +++ b/test/rails/railsresource.rb @@ -0,0 +1,60 @@ +#!/usr/bin/ruby + +require 'puppet' +require 'puppet/rails' +require 'puppettest' +require 'puppettest/railstesting' +require 'puppettest/resourcetesting' + +class TestRailsResource < Test::Unit::TestCase + include PuppetTest::RailsTesting + include PuppetTest::ResourceTesting + + # Don't do any tests w/out this class + if defined? ActiveRecord::Base + # Create a resource param from a rails parameter + def test_to_resource + railsinit + + # We need a host for resources + host = Puppet::Rails::Host.new(:name => "myhost") + + # Now build a resource + resource = host.rails_resources.build( + :title => "/tmp/to_resource", :restype => "file", + :exported => true + ) + + # Now add some params + {"owner" => "root", "mode" => "644"}.each do |param, value| + resource.rails_parameters.build( + :name => param, :value => value + ) + end + + # Now save the whole thing + host.save + + # Now, try to convert our resource to a real resource + + # We need a scope + interp, scope, source = mkclassframing + + res = nil + assert_nothing_raised do + res = resource.to_resource(scope) + end + + assert_instance_of(Puppet::Parser::Resource, res) + + assert_equal("root", res[:owner]) + assert_equal("644", res[:mode]) + assert_equal("/tmp/to_resource", res.title) + assert_equal(source, res.source) + end + else + $stderr.puts "Install Rails for Rails and Caching tests" + end +end + +# $Id$ diff --git a/test/tagging/tagging.rb b/test/tagging/tagging.rb index 42f04ff4f..b30160bcb 100644 --- a/test/tagging/tagging.rb +++ b/test/tagging/tagging.rb @@ -1,14 +1,18 @@ require 'puppet' require 'puppettest' +require 'puppettest/parsertesting' +require 'puppettest/resourcetesting' class TestTagging < Test::Unit::TestCase include PuppetTest + include PuppetTest::ParserTesting + include PuppetTest::ResourceTesting # Make sure the scopes are getting the right tags def test_scopetags scope = nil assert_nothing_raised { - scope = Puppet::Parser::Scope.new() + scope = mkscope scope.name = "yayness" scope.type = "solaris" } @@ -22,7 +26,7 @@ class TestTagging < Test::Unit::TestCase def test_deepscopetags scope = nil assert_nothing_raised { - scope = Puppet::Parser::Scope.new() + scope = mkscope scope.name = "yayness" scope.type = "solaris" scope = scope.newscope @@ -40,36 +44,21 @@ class TestTagging < Test::Unit::TestCase def test_objecttags scope = nil assert_nothing_raised { - scope = Puppet::Parser::Scope.new() + scope = mkscope scope.name = "yayness" scope.type = "solaris" } - assert_nothing_raised { - scope.setobject( - :type => "file", - :name => "/etc/passwd", - :arguments => {"owner" => "root"}, - :file => "/yay", - :line => 1 - ) - } + resource = mkresource :type => "file", :title => "/tmp/testing", + :params => {:owner => "root"}, :file => "/yay", :line => 1, + :scope => scope - ast = Puppet::Parser::AST::ASTArray.new({}) - - # We have to use 'evaluate', rather than just calling to_trans directly, - # because scopes do some internal checking to make sure the same object - # is not translated multiple times. - objects = nil assert_nothing_raised { - objects = scope.evaluate(:ast => ast) + scope.setresource(resource) } - # There's only one object, so shift it out - object = objects.shift - assert_nothing_raised { - assert_equal(%w{solaris}, object.tags, + assert_equal(%w{solaris}, resource.tags, "Incorrect tags") } end |