diff --git a/lib/puppet/parser/ast/definition.rb b/lib/puppet/parser/ast/definition.rb index 2e39e7332..bf57942d7 100644 --- a/lib/puppet/parser/ast/definition.rb +++ b/lib/puppet/parser/ast/definition.rb @@ -1,209 +1,201 @@ 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 Definition < AST::Branch - include Puppet::Util - include Puppet::Util::Warnings - include Puppet::Util::MethodHelper - class << self - attr_accessor :name - end +require 'puppet/util/warnings' - # The class name - @name = :definition +# The AST class for defined types, which is also the base class +# nodes and classes. +class Puppet::Parser::AST::Definition < Puppet::Parser::AST::Branch + include Puppet::Util::Warnings + class << self + attr_accessor :name + end - attr_accessor :classname, :arguments, :code, :scope, :keyword - attr_accessor :exported, :namespace, :parser, :virtual + # The class name + @name = :definition - # These are retrieved when looking up the superclass - attr_accessor :name + attr_accessor :classname, :arguments, :code, :scope, :keyword + attr_accessor :exported, :namespace, :parser, :virtual, :name - attr_reader :parentclass + attr_reader :parentclass - def child_of?(klass) - false - end + def child_of?(klass) + false + end - def evaluate(origscope, resource) - # Create a new scope. - scope = subscope(origscope, resource) + # Create a resource that knows how to evaluate our actual code. + def evaluate(scope) + resource = Puppet::Parser::Resource.new(:type => "class", :title => klass.classname, :scope => scope, :source => scope.source) - # Additionally, add a tag for whatever kind of class - # we are - if @classname != "" and ! @classname.nil? - @classname.split(/::/).each { |tag| scope.resource.tag(tag) } - end + scope.compile.store_resource(scope, resource) - [resource.name, resource.title].each do |str| - unless str.nil? or str =~ /[^\w]/ or str == "" - scope.resource.tag(str) - end - end + return resource + end - set_resource_parameters(scope, resource) + # Now evaluate the code associated with this class or definition. + def evaluate_code(resource) + # Create a new scope. + scope = subscope(resource.scope, resource) - if self.code - return self.code.safeevaluate(scope) - else - return nil - end + set_resource_parameters(scope, resource) + + if self.code + return self.code.safeevaluate(scope) + else + return nil end + end - def initialize(hash = {}) - @arguments = nil - @parentclass = nil - super + def initialize(hash = {}) + @arguments = nil + @parentclass = nil + super - # Convert the arguments to a hash for ease of later use. - if @arguments - unless @arguments.is_a? Array - @arguments = [@arguments] - end - oldargs = @arguments - @arguments = {} - oldargs.each do |arg, val| - @arguments[arg] = val - end - else - @arguments = {} + # Convert the arguments to a hash for ease of later use. + if @arguments + unless @arguments.is_a? Array + @arguments = [@arguments] end - - # Deal with metaparams in the argument list. - @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 resources" % arg - else - raise Puppet::ParseError, "%s is a metaparameter; please choose another parameter name in the %s definition" % [arg, self.classname] - end + oldargs = @arguments + @arguments = {} + oldargs.each do |arg, val| + @arguments[arg] = val end + else + @arguments = {} end - def find_parentclass - @parser.findclass(namespace, parentclass) + # Deal with metaparams in the argument list. + @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 resources" % arg + else + raise Puppet::ParseError, "%s is a metaparameter; please choose another parameter name in the %s definition" % [arg, self.classname] + end end + end - # Set our parent class, with a little check to avoid some potential - # weirdness. - def parentclass=(name) - if name == self.classname - parsefail "Parent classes must have dissimilar names" - end + def find_parentclass + @parser.findclass(namespace, parentclass) + end - @parentclass = name + # Set our parent class, with a little check to avoid some potential + # weirdness. + def parentclass=(name) + if name == self.classname + parsefail "Parent classes must have dissimilar names" end - # Hunt down our class object. - def parentobj - if @parentclass - # Cache our result, since it should never change. - unless defined?(@parentobj) - unless tmp = find_parentclass - parsefail "Could not find %s %s" % [self.class.name, @parentclass] - end + @parentclass = name + end - if tmp == self - parsefail "Parent classes must have dissimilar names" - end + # Hunt down our class object. + def parentobj + if @parentclass + # Cache our result, since it should never change. + unless defined?(@parentobj) + unless tmp = find_parentclass + parsefail "Could not find %s %s" % [self.class.name, @parentclass] + end - @parentobj = tmp + if tmp == self + parsefail "Parent classes must have dissimilar names" end - @parentobj - else - nil + + @parentobj = tmp end + @parentobj + else + nil end + end - # Create a new subscope in which to evaluate our code. - def subscope(scope, resource) - args = { - :resource => resource, - :keyword => self.keyword, - :namespace => self.namespace, - :source => self - } + # Create a new subscope in which to evaluate our code. + def subscope(scope, resource) + args = { + :resource => resource, + :keyword => self.keyword, + :namespace => self.namespace, + :source => self + } - oldscope = scope - scope = scope.newscope(args) - scope.source = self + oldscope = scope + scope = scope.newscope(args) + scope.source = self - return scope - end + return scope + end - def to_s - classname - end + def to_s + classname + end - # Check whether a given argument is valid. Searches up through - # any parent classes that might exist. - def validattr?(param) - param = param.to_s + # Check whether a given argument is valid. Searches up through + # any parent classes that might exist. + def validattr?(param) + param = param.to_s - if @arguments.include?(param) - # It's a valid arg for us - return true - elsif param == "name" - return true + if @arguments.include?(param) + # It's a valid arg for us + return true + elsif param == "name" + return true # elsif defined? @parentclass and @parentclass # # Else, check any existing parent # if parent = @scope.lookuptype(@parentclass) and parent != [] # return parent.validarg?(param) # elsif builtin = Puppet::Type.type(@parentclass) # return builtin.validattr?(param) # else # raise Puppet::Error, "Could not find parent class %s" % # @parentclass # end - elsif Puppet::Type.metaparam?(param) - return true - else - # Or just return false - return false - end + elsif Puppet::Type.metaparam?(param) + return true + else + # Or just return false + return false end + end - private - - # Set any arguments passed by the resource as variables in the scope. - def set_resource_parameters(scope, resource) - args = symbolize_options(resource.to_hash || {}) - - # Verify that all required arguments are either present or - # have been provided with defaults. - if self.arguments - self.arguments.each { |arg, default| - arg = symbolize(arg) - unless args.include?(arg) - if defined? default and ! default.nil? - default = default.safeevaluate scope - args[arg] = default - #Puppet.debug "Got default %s for %s in %s" % - # [default.inspect, arg.inspect, @name.inspect] - else - parsefail "Must pass %s to %s of type %s" % - [arg, resource.title, @classname] - end + private + + # Set any arguments passed by the resource as variables in the scope. + def set_resource_parameters(scope, resource) + args = symbolize_options(resource.to_hash || {}) + + # Verify that all required arguments are either present or + # have been provided with defaults. + if self.arguments + self.arguments.each { |arg, default| + arg = arg.to_sym + unless args.include?(arg) + if defined? default and ! default.nil? + default = default.safeevaluate scope + args[arg] = default + #Puppet.debug "Got default %s for %s in %s" % + # [default.inspect, arg.inspect, @name.inspect] + else + parsefail "Must pass %s to %s of type %s" % + [arg, resource.title, @classname] end - } - end - - # Set each of the provided arguments as variables in the - # definition's scope. - args.each { |arg,value| - unless validattr?(arg) - parsefail "%s does not accept attribute %s" % [@classname, arg] - end - - exceptwrap do - scope.setvar(arg.to_s, args[arg]) end } - - scope.setvar("title", resource.title) unless args.include? :title - scope.setvar("name", resource.name) unless args.include? :name end + + # Set each of the provided arguments as variables in the + # definition's scope. + args.each { |arg,value| + unless validattr?(arg) + parsefail "%s does not accept attribute %s" % [@classname, arg] + end + + exceptwrap do + scope.setvar(arg.to_s, args[arg]) + end + } + + scope.setvar("title", resource.title) unless args.include? :title + scope.setvar("name", resource.name) unless args.include? :name end end diff --git a/lib/puppet/parser/ast/hostclass.rb b/lib/puppet/parser/ast/hostclass.rb index 42d13f4b2..251d5eba6 100644 --- a/lib/puppet/parser/ast/hostclass.rb +++ b/lib/puppet/parser/ast/hostclass.rb @@ -1,78 +1,73 @@ require 'puppet/parser/ast/definition' -class Puppet::Parser::AST - # The code associated with a class. This is different from definitions - # in that each class is a singleton -- only one will exist for a given - # node. - class HostClass < AST::Definition - @name = :class +# The code associated with a class. This is different from definitions +# in that each class is a singleton -- only one will exist for a given +# node. +class Puppet::Parser::AST::HostClass < Puppet::Parser::AST::Definition + @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 + # 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.parentobj - return true - else - return self.parentobj.child_of?(klass) - end + if klass == self.parentobj + return true + else + return self.parentobj.child_of?(klass) end + end - # Evaluate the code associated with this class. - def evaluate(scope, resource) - # Verify that we haven't already been evaluated. This is - # what provides the singleton aspect. - if existing_scope = scope.compile.class_scope(self) - Puppet.debug "Class '%s' already evaluated; not evaluating again" % (classname == "" ? "main" : classname) - return nil - end - - scope.compile.catalog.tag(self.classname) + # Evaluate the code associated with this class. + def evaluate_code(resource) + scope = resource.scope + # Verify that we haven't already been evaluated. This is + # what provides the singleton aspect. + if existing_scope = scope.compile.class_scope(self) + Puppet.debug "Class '%s' already evaluated; not evaluating again" % (classname == "" ? "main" : classname) + return nil + end - pnames = nil - if pklass = self.parentobj - pklass.safeevaluate(scope, resource) + pnames = nil + if pklass = self.parentobj + pklass.evaluate_code(resource) - scope = parent_scope(scope, pklass) - pnames = scope.namespaces - end + scope = parent_scope(scope, pklass) + pnames = scope.namespaces + end - # Don't create a subscope for the top-level class, since it already - # has its own scope. - unless resource.title == :main - scope = subscope(scope, resource) - end + # Don't create a subscope for the top-level class, since it already + # has its own scope. + scope = subscope(scope, resource) unless resource.title == :main - if pnames - pnames.each do |ns| - scope.add_namespace(ns) - end + if pnames + pnames.each do |ns| + scope.add_namespace(ns) end + end - # Set the class before we do anything else, so that it's set - # during the evaluation and can be inspected. - scope.compile.class_set(self.classname, scope) + # Set the class before we do anything else, so that it's set + # during the evaluation and can be inspected. + scope.compile.class_set(self.classname, scope) - # Now evaluate our code, yo. - if self.code - return self.code.safeevaluate(scope) - else - return nil - end + # Now evaluate our code, yo. + if self.code + return self.code.safeevaluate(scope) + else + return nil end + end - def initialize(options) - @parentclass = nil - super - end + def initialize(options) + @parentclass = nil + super + end - def parent_scope(scope, klass) - if s = scope.compile.class_scope(klass) - return s - else - raise Puppet::DevError, "Could not find scope for %s" % klass.classname - end + def parent_scope(scope, klass) + if s = scope.compile.class_scope(klass) + return s + else + raise Puppet::DevError, "Could not find scope for %s" % klass.classname end end end diff --git a/lib/puppet/parser/ast/node.rb b/lib/puppet/parser/ast/node.rb index 3c5d44d1b..7ff7a18e1 100644 --- a/lib/puppet/parser/ast/node.rb +++ b/lib/puppet/parser/ast/node.rb @@ -1,59 +1,57 @@ 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 :name - - def evaluate(scope, resource) - # 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.parentobj - scope = parent.safeevaluate scope, resource - end - - scope = scope.newscope( - :resource => resource, - :keyword => @keyword, - :source => self, - :namespace => "" # nodes are always in "" - ) - - # 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.compile.class_set(self.classname, scope) - - # And then evaluate our code if we have any - if self.code - @code.safeevaluate(scope) - end - - return scope +# 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 Puppet::Parser::AST::Node < Puppet::Parser::AST::HostClass + @name = :node + + # Evaluate the code associated with our node definition. + def evaluate_code(resource) + scope = resource.scope + + # 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.parentobj + scope = parent.evaluate_code(resource) end - def initialize(options) - @parentclass = nil - super + scope = scope.newscope( + :resource => resource, + :keyword => @keyword, + :source => self, + :namespace => "" # nodes are always in "" + ) - # Do some validation on the node name - if @name =~ /[^-\w.]/ - raise Puppet::ParseError, "Invalid node name %s" % @name - end - end + # 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.compile.class_set(self.classname, scope) - # Make sure node scopes are marked as such. - def subscope(*args) - scope = super - scope.nodescope = true - end + # And then evaluate our code if we have any + @code.safeevaluate(scope) if self.code + + return scope + end + + def initialize(options) + @parentclass = nil + super - private - # Search for the object matching our parent class. - def find_parentclass - @parser.findnode(parentclass) + # Do some validation on the node name + if @name =~ /[^-\w.]/ + raise Puppet::ParseError, "Invalid node name %s" % @name end end + + # Make sure node scopes are marked as such. + def subscope(*args) + scope = super + scope.nodescope = true + end + + private + # Search for the object matching our parent class. + def find_parentclass + @parser.findnode(parentclass) + end end diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb index ea6038299..f8701578c 100644 --- a/lib/puppet/parser/resource.rb +++ b/lib/puppet/parser/resource.rb @@ -1,449 +1,449 @@ # 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' require 'puppet/util/tagging' include Puppet::Util include Puppet::Util::MethodHelper include Puppet::Util::Errors include Puppet::Util::Logging include Puppet::Util::Tagging attr_accessor :source, :line, :file, :scope, :rails_id attr_accessor :virtual, :override, :translated attr_reader :exported, :evaluated, :params # Determine whether the provided parameter name is a relationship parameter. def self.relationship_parameter?(name) unless defined?(@relationship_names) @relationship_names = Puppet::Type.relationship_params.collect { |p| p.name } end @relationship_names.include?(name) end # 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, :virtual, :evaluated].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 def builtin=(bool) @ref.builtin = bool end # Retrieve the associated definition and evaluate it. def evaluate if klass = @ref.definedtype finish() scope.compile.delete_resource(self) - return klass.evaluate(scope, self) + return klass.evaluate_code(self) elsif builtin? devfail "Cannot evaluate a builtin type" else self.fail "Cannot find definition %s" % self.type end ensure @evaluated = true end # Mark this resource as both exported and virtual, # or remove the exported mark. def exported=(value) if value @virtual = true @exported = value else @exported = value end end # Do any finishing work on this object, called before evaluation or # before storage/translation. def finish add_overrides() add_defaults() add_metaparams() add_scope_tags() validate() end def initialize(options) # Set all of the options we can. options.each do |option, value| if respond_to?(option.to_s + "=") send(option.to_s + "=", value) options.delete(option) end end unless self.scope raise ArgumentError, "Resources require a scope" end @source ||= scope.source options = symbolize_options(options) # Set up our reference. if type = options[:type] and title = options[:title] options.delete(:type) options.delete(:title) else raise ArgumentError, "Resources require a type and title" end @ref = Reference.new(:type => type, :title => title, :scope => self.scope) @params = {} # Define all of the parameters if params = options[:params] options.delete(:params) params.each do |param| set_parameter(param) end end # Throw an exception if we've got any arguments left to set. unless options.empty? raise ArgumentError, "Resources do not accept %s" % options.keys.collect { |k| k.to_s }.join(", ") end tag(@ref.type) tag(@ref.title) if valid_tag?(@ref.title.to_s) end # Merge an override resource in. This will throw exceptions if # any overrides aren't allowed. def merge(resource) # Test the resource scope, to make sure the resource is even allowed # to override. unless self.source.object_id == resource.source.object_id || resource.source.child_of?(self.source) raise Puppet::ParseError.new("Only subclasses can override parameters", resource.line, resource.file) end # Some of these might fail, but they'll fail in the way we want. resource.params.each do |name, param| override_parameter(param) end end # Modify this resource in the Rails database. Poor design, yo. def modify_rails(db_resource) args = rails_args args.each do |param, value| db_resource[param] = value unless db_resource[param] == value end # Handle file specially if (self.file and (!db_resource.file or db_resource.file != self.file)) db_resource.file = self.file end updated_params = @params.inject({}) do |hash, ary| hash[ary[0].to_s] = ary[1] hash end db_resource.ar_hash_merge(db_resource.get_params_hash(db_resource.param_values), updated_params, :create => Proc.new { |name, parameter| parameter.to_rails(db_resource) }, :delete => Proc.new { |values| values.each { |value| db_resource.param_values.delete(value) } }, :modify => Proc.new { |db, mem| mem.modify_rails_values(db) }) updated_tags = tags.inject({}) { |hash, tag| hash[tag] = tag hash } db_resource.ar_hash_merge(db_resource.get_tag_hash(), updated_tags, :create => Proc.new { |name, tag| db_resource.add_resource_tag(name) }, :delete => Proc.new { |tag| db_resource.resource_tags.delete(tag) }, :modify => Proc.new { |db, mem| # nothing here }) end # Return the resource name, or the title if no name # was specified. def name unless defined? @name @name = self[:name] || self.title end @name end # This *significantly* reduces the number of calls to Puppet.[]. def paramcheck? unless defined? @@paramcheck @@paramcheck = Puppet[:paramcheck] end @@paramcheck 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 def to_hash @params.inject({}) do |hash, ary| param = ary[1] # Skip "undef" values. if param.value != :undef hash[param.name] = param.value end hash end end # Turn our parser resource into a Rails resource. def to_rails(host) args = rails_args db_resource = host.resources.build(args) # Handle file specially db_resource.file = self.file db_resource.save @params.each { |name, param| param.to_rails(db_resource) } tags.each { |tag| db_resource.add_resource_tag(tag) } return db_resource end def to_s self.ref end # Translate our object to a transportable object. def to_trans return nil if virtual? if builtin? to_transobject else to_transbucket end end def to_transbucket bucket = Puppet::TransBucket.new([]) bucket.type = self.type bucket.name = self.title # TransBuckets don't support parameters, which is why they're being deprecated. return bucket end def to_transobject # 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 # If the value is an array with only one value, then # convert it to a single value. This is largely so that # the database interaction doesn't have to worry about # whether it returns an array or a string. obj[p.to_s] = if v.is_a?(Array) and v.length == 1 v[0] else v end end obj.file = self.file obj.line = self.line obj.tags = self.tags return obj end private # Add default values from our definition. def add_defaults scope.lookupdefaults(self.type).each do |name, param| unless @params.include?(name) self.debug "Adding default for %s" % name @params[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 add_metaparams Puppet::Type.eachmetaparam do |name| # Skip metaparams that we already have defined, unless they're relationship metaparams. # LAK:NOTE Relationship metaparams get treated specially -- we stack them, instead of # overriding. next if @params[name] and not self.class.relationship_parameter?(name) # Skip metaparams for which we get no value. next unless val = scope.lookupvar(name.to_s, false) and val != :undefined # The default case: just set the value return set_parameter(name, val) unless @params[name] # For relationship params, though, join the values (a la #446). @params[name].value = [@params[name].value, val].flatten end end # Add any overrides for this object. def add_overrides if overrides = scope.compile.resource_overrides(self) overrides.each do |over| self.merge(over) end # Remove the overrides, so that the configuration knows there # are none left. overrides.clear end end def add_scope_tags if scope_resource = scope.resource tag(*scope_resource.tags) end end # Accept a parameter from an override. def override_parameter(param) # This can happen if the override is defining a new parameter, rather # than replacing an existing one. (@params[param.name] = param and return) unless current = @params[param.name] # The parameter is already set. Fail if they're not allowed to override it. unless param.source.child_of?(current.source) if Puppet[:trace] puts caller end msg = "Parameter '%s' is already set on %s" % [param.name, self.to_s] if current.source.to_s != "" msg += " by %s" % current.source end if current.file or current.line fields = [] fields << current.file if current.file fields << current.line.to_s if current.line msg += " at %s" % fields.join(":") end msg += "; cannot redefine" raise Puppet::ParseError.new(msg, param.line, param.file) end # If we've gotten this far, we're allowed to override. # Merge with previous value, if the parameter was generated with the +> syntax. # It's important that we use the new param instance here, not the old one, # so that the source is registered correctly for later overrides. param.value = [current.value, param.value].flatten if param.add @params[param.name] = param 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) param = param.to_s # Now make sure it's a valid argument to our class. These checks # are organized in order of commonhood -- most types, it's a valid # argument and paramcheck is enabled. if @ref.typeclass.validattr?(param) true elsif %w{name title}.include?(param) # always allow these true elsif paramcheck? self.fail Puppet::ParseError, "Invalid parameter '%s' for type '%s'" % [param, @ref.type] end end def rails_args return [:type, :title, :line, :exported].inject({}) do |hash, param| # 'type' isn't a valid column name, so we have to use another name. to = (param == :type) ? :restype : param if value = self.send(param) hash[to] = value end hash end end # Define a parameter in our resource. def set_parameter(param, value = nil) if value param = Puppet::Parser::Resource::Param.new( :name => param, :value => value, :source => self.source ) elsif ! param.is_a?(Puppet::Parser::Resource::Param) raise ArgumentError, "Must pass a parameter or all necessary values" end # And store it in our parameter hash. @params[param.name] = param end # Make sure the resource's parameters are all valid for the type. def validate @params.each do |name, param| # Make sure it's a valid parameter. paramcheck(name) end end end diff --git a/lib/puppet/parser/resource/param.rb b/lib/puppet/parser/resource/param.rb index 9352311d6..9dd3f26d2 100644 --- a/lib/puppet/parser/resource/param.rb +++ b/lib/puppet/parser/resource/param.rb @@ -1,90 +1,90 @@ # The parameters we stick in Resources. class Puppet::Parser::Resource::Param attr_accessor :name, :value, :source, :line, :file, :add include Puppet::Util include Puppet::Util::Errors include Puppet::Util::MethodHelper def initialize(hash) set_options(hash) requiredopts(:name, :value, :source) @name = symbolize(@name) end def inspect - "#<#{self.class} @name => #{self.name}, @value => #{self.value}, @source => #{self.source.name}>" + "#<#{self.class} @name => #{name}, @value => #{value}, @source => #{source.name}>" end def line_to_i return line ? Integer(line) : nil end # Make sure an array (or possibly not an array) of values is correctly # set up for Rails. The main thing is that Resource::Reference objects # should stay objects, so they just get serialized. def munge_for_rails(values) values = value.is_a?(Array) ? value : [value] values.map do |v| if v.is_a?(Puppet::Parser::Resource::Reference) v else v.to_s end end end # Store a new parameter in a Rails db. def to_rails(db_resource) values = munge_for_rails(value) param_name = Puppet::Rails::ParamName.find_or_create_by_name(self.name.to_s) line_number = line_to_i() return values.collect do |v| db_resource.param_values.create(:value => v, :line => line_number, :param_name => param_name) end end def modify_rails_values(db_values) #dev_warn if db_values.nil? || db_values.empty? values_to_remove(db_values).each { |remove_me| Puppet::Rails::ParamValue.delete(remove_me.id) } line_number = line_to_i() values_to_add(db_values).each { |add_me| db_resource = db_values[0].resource db_param_name = db_values[0].param_name db_resource.param_values.create(:value => add_me, :line => line_number, :param_name => db_param_name) } end def to_s "%s => %s" % [self.name, self.value] end def values_to_remove(db_values) values = munge_for_rails(value) line_number = line_to_i() db_values.collect do |db| db unless (db.line == line_number && values.find { |v| v == db.value } ) end.compact end def values_to_add(db_values) values = munge_for_rails(value) line_number = line_to_i() values.collect do |v| v unless db_values.find { |db| (v == db.value && line_number == db.line) } end.compact end end diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index 028414cc0..81d4ac71a 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -1,332 +1,337 @@ # The scope class, which handles storing and retrieving variables and types and # such. require 'puppet/parser/parser' require 'puppet/parser/templatewrapper' require 'puppet/transportable' require 'strscan' class Puppet::Parser::Scope require 'puppet/parser/resource' AST = Puppet::Parser::AST Puppet::Util.logmethods(self) include Enumerable include Puppet::Util::Errors attr_accessor :parent, :level, :parser, :source, :resource attr_accessor :base, :keyword, :nodescope attr_accessor :top, :translated, :compile + # A demeterific shortcut to the catalog. + def catalog + compile.catalog + end + # Proxy accessors def host @compile.node.name end def interpreter @compile.interpreter 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 == "" or value == :undef return false else return true end end # Add to our list of namespaces. def add_namespace(ns) return false if @namespaces.include?(ns) if @namespaces == [""] @namespaces = [ns] else @namespaces << ns end end # Are we the top scope? def topscope? @level == 1 end def findclass(name) @namespaces.each do |namespace| if r = parser.findclass(namespace, name) return r end end return nil end def finddefine(name) @namespaces.each do |namespace| if r = parser.finddefine(namespace, name) return r end end return nil end def findresource(string, name = nil) compile.findresource(string, name) end # Initialize our new scope. Defaults to having no parent. def initialize(hash = {}) if hash.include?(:namespace) if n = hash[:namespace] @namespaces = [n] end hash.delete(:namespace) else @namespaces = [""] end 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 = [] # The symbol table for this scope. This is where we store variables. @symtable = {} # 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. @defaults = Hash.new { |dhash,type| dhash[type] = {} } 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 @defaults.include?(type) @defaults[type].each { |var,value| values[var] = value } end #Puppet.debug "Got defaults for %s: %s" % # [type,values.inspect] return values end # Look up a defined type. def lookuptype(name) finddefine(name) || findclass(name) end def lookup_qualified_var(name, usestring) parts = name.split(/::/) shortname = parts.pop klassname = parts.join("::") klass = findclass(klassname) unless klass raise Puppet::ParseError, "Could not find class %s" % klassname end unless kscope = compile.class_scope(klass) raise Puppet::ParseError, "Class %s has not been evaluated so its variables cannot be referenced" % klass.classname end return kscope.lookupvar(shortname, usestring) end private :lookup_qualified_var # 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) # If the variable is qualified, then find the specified scope and look the variable up there instead. if name =~ /::/ return lookup_qualified_var(name, usestring) end # We can't use "if @symtable[name]" here because the value might be false if @symtable.include?(name) if usestring and @symtable[name] == :undef return "" else return @symtable[name] end elsif self.parent return parent.lookupvar(name, usestring) elsif usestring return "" else return :undefined end end def namespaces @namespaces.dup end # Create a new scope and set these options. def newscope(options = {}) compile.newscope(self, options) end # Is this class for a node? This is used to make sure that # nodes and classes with the same name conflict (#620), which # is required because of how often the names are used throughout # the system, including on the client. def nodescope? self.nodescope end # We probably shouldn't cache this value... But it's a lot faster # than doing lots of queries. def parent unless defined?(@parent) @parent = compile.parent(self) end @parent end # Return the list of scopes up to the top scope, ordered with our own first. # This is used for looking up variables and defaults. def scope_path if parent [self, parent.scope_path].flatten.compact else [self] end end # 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 = @defaults[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 table.include?(param.name) raise Puppet::ParseError.new("Default already defined for %s { %s }; cannot redefine" % [type, param.name], param.line, param.file) end table[param.name] = param } 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. def setvar(name,value, file = nil, line = nil) #Puppet.debug "Setting %s to '%s' at level %s" % # [name.inspect,value,self.level] if @symtable.include?(name) error = Puppet::ParseError.new("Cannot reassign variable %s" % name) if file error.file = file end if line error.line = line end raise error end @symtable[name] = value end # Return an interpolated string. def strinterp(string, file = nil, line = nil) # Most strings won't have variables in them. ss = StringScanner.new(string) out = "" while not ss.eos? if ss.scan(/^\$\{((\w*::)*\w+)\}|^\$((\w*::)*\w+)/) # If it matches the backslash, then just retun the dollar sign. if ss.matched == '\\$' out << '$' else # look the variable up out << lookupvar(ss[1] || ss[3]).to_s || "" end elsif ss.scan(/^\\(.)/) # Puppet.debug("Got escape: pos:%d; m:%s" % [ss.pos, ss.matched]) case ss[1] when 'n' out << "\n" when 't' out << "\t" when 's' out << " " when '\\' out << '\\' when '$' out << '$' else str = "Unrecognised escape sequence '#{ss.matched}'" if file str += " in file %s" % file end if line str += " at line %s" % line end Puppet.warning str out << ss.matched end elsif ss.scan(/^\$/) out << '$' elsif ss.scan(/^\\\n/) # an escaped carriage return next else tmp = ss.scan(/[^\\$]+/) # Puppet.debug("Got other: pos:%d; m:%s" % [ss.pos, tmp]) unless tmp error = Puppet::ParseError.new("Could not parse string %s" % string.inspect) {:file= => file, :line= => line}.each do |m,v| error.send(m, v) if v end raise error end out << tmp end end return out end # 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 resource.tags end # Used mainly for logging def to_s "Scope(%s)" % @resource.to_s end # Undefine a variable; only used for testing. def unsetvar(var) if @symtable.include?(var) @symtable.delete(var) end end end diff --git a/spec/unit/parser/ast/definition.rb b/spec/unit/parser/ast/definition.rb index 1c84f5c5f..fae2851e3 100755 --- a/spec/unit/parser/ast/definition.rb +++ b/spec/unit/parser/ast/definition.rb @@ -1,47 +1,47 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' describe Puppet::Parser::AST::Definition, "when initializing" do end describe Puppet::Parser::AST::Definition, "when evaluating" do before do @type = Puppet::Parser::Resource @parser = Puppet::Parser::Parser.new :Code => "" @source = @parser.newclass "" @definition = @parser.newdefine "mydefine" @node = Puppet::Node.new("yaynode") @compile = Puppet::Parser::Compile.new(@node, @parser) @scope = @compile.topscope @resource = Puppet::Parser::Resource.new(:type => "mydefine", :title => "myresource", :scope => @scope, :source => @source) end it "should create a new scope" do scope = nil code = mock 'code' code.expects(:safeevaluate).with do |scope| scope.object_id.should_not == @scope.object_id true end @definition.stubs(:code).returns(code) - @definition.evaluate(@scope, @resource) + @definition.evaluate_code(@resource) end # it "should copy its namespace to the scope" # # it "should mark the scope virtual if the resource is virtual" # # it "should mark the scope exported if the resource is exported" # # it "should set the resource's parameters as variables in the scope" # # it "should set the resource's title as a variable in the scope" # # it "should copy the resource's title in a 'name' variable in the scope" # # it "should not copy the resource's title as the name if 'name' is one of the resource parameters" # # it "should evaluate the associated code with the new scope" end diff --git a/spec/unit/parser/resource.rb b/spec/unit/parser/resource.rb index 36e104fb1..099cfd05e 100755 --- a/spec/unit/parser/resource.rb +++ b/spec/unit/parser/resource.rb @@ -1,149 +1,149 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' # LAK: FIXME This is just new tests for resources; I have # not moved all tests over yet. describe Puppet::Parser::Resource, " when evaluating" do before do @type = Puppet::Parser::Resource @parser = Puppet::Parser::Parser.new :Code => "" @source = @parser.newclass "" @definition = @parser.newdefine "mydefine" @class = @parser.newclass "myclass" @nodedef = @parser.newnode("mynode")[0] @node = Puppet::Node.new("yaynode") @compile = Puppet::Parser::Compile.new(@node, @parser) @scope = @compile.topscope end it "should evaluate the associated AST definition" do res = @type.new(:type => "mydefine", :title => "whatever", :scope => @scope, :source => @source) - @definition.expects(:evaluate).with(@scope, res) + @definition.expects(:evaluate_code).with(res) res.evaluate end it "should evaluate the associated AST class" do res = @type.new(:type => "class", :title => "myclass", :scope => @scope, :source => @source) - @class.expects(:evaluate).with(@scope, res) + @class.expects(:evaluate_code).with(res) res.evaluate end it "should evaluate the associated AST node" do res = @type.new(:type => "node", :title => "mynode", :scope => @scope, :source => @source) - @nodedef.expects(:evaluate).with(@scope, res) + @nodedef.expects(:evaluate_code).with(res) res.evaluate end end describe Puppet::Parser::Resource, " when finishing" do before do @parser = Puppet::Parser::Parser.new :Code => "" @source = @parser.newclass "" @definition = @parser.newdefine "mydefine" @class = @parser.newclass "myclass" @nodedef = @parser.newnode("mynode")[0] @node = Puppet::Node.new("yaynode") @compile = Puppet::Parser::Compile.new(@node, @parser) @scope = @compile.topscope @resource = Puppet::Parser::Resource.new(:type => "mydefine", :title => "whatever", :scope => @scope, :source => @source) end it "should copy metaparams from its scope" do @scope.setvar("noop", "true") @resource.class.publicize_methods(:add_metaparams) { @resource.add_metaparams } @resource["noop"].should == "true" end it "should not copy metaparams that it already has" do @resource.class.publicize_methods(:set_parameter) { @resource.set_parameter("noop", "false") } @scope.setvar("noop", "true") @resource.class.publicize_methods(:add_metaparams) { @resource.add_metaparams } @resource["noop"].should == "false" end it "should stack relationship metaparams from its container if it already has them" do @resource.class.publicize_methods(:set_parameter) { @resource.set_parameter("require", "resource") } @scope.setvar("require", "container") @resource.class.publicize_methods(:add_metaparams) { @resource.add_metaparams } @resource["require"].sort.should == %w{container resource} end it "should flatten the array resulting from stacking relationship metaparams" do @resource.class.publicize_methods(:set_parameter) { @resource.set_parameter("require", ["resource1", "resource2"]) } @scope.setvar("require", %w{container1 container2}) @resource.class.publicize_methods(:add_metaparams) { @resource.add_metaparams } @resource["require"].sort.should == %w{container1 container2 resource1 resource2} end it "should add any tags from the scope resource" do scope_resource = stub 'scope_resource', :tags => %w{one two} @scope.stubs(:resource).returns(scope_resource) @resource.class.publicize_methods(:add_scope_tags) { @resource.add_scope_tags } @resource.tags.should be_include("one") @resource.tags.should be_include("two") end end describe Puppet::Parser::Resource, "when being tagged" do before do @scope_resource = stub 'scope_resource', :tags => %w{srone srtwo} @scope = stub 'scope', :resource => @scope_resource @resource = Puppet::Parser::Resource.new(:type => "file", :title => "yay", :scope => @scope, :source => mock('source')) end it "should get tagged with the resource type" do @resource.tags.should be_include("file") end it "should get tagged with the title" do @resource.tags.should be_include("yay") end it "should get tagged with each name in the title if the title is a qualified class name" do resource = Puppet::Parser::Resource.new(:type => "file", :title => "one::two", :scope => @scope, :source => mock('source')) resource.tags.should be_include("one") resource.tags.should be_include("two") end it "should get tagged with each name in the type if the type is a qualified class name" do resource = Puppet::Parser::Resource.new(:type => "one::two", :title => "whatever", :scope => @scope, :source => mock('source')) resource.tags.should be_include("one") resource.tags.should be_include("two") end it "should not get tagged with non-alphanumeric titles" do resource = Puppet::Parser::Resource.new(:type => "file", :title => "this is a test", :scope => @scope, :source => mock('source')) resource.tags.should_not be_include("this is a test") end it "should fail on tags containing '*' characters" do lambda { @resource.tag("bad*tag") }.should raise_error(Puppet::ParseError) end it "should fail on tags starting with '-' characters" do lambda { @resource.tag("-badtag") }.should raise_error(Puppet::ParseError) end it "should fail on tags containing ' ' characters" do lambda { @resource.tag("bad tag") }.should raise_error(Puppet::ParseError) end it "should allow alpha tags" do lambda { @resource.tag("good_tag") }.should_not raise_error(Puppet::ParseError) end end diff --git a/test/language/ast.rb b/test/language/ast.rb index 72a3ee90c..9b1c1c1dc 100755 --- a/test/language/ast.rb +++ b/test/language/ast.rb @@ -1,188 +1,138 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../lib/puppettest' require 'puppettest' require 'puppet/parser/interpreter' require 'puppet/parser/parser' require 'puppettest/resourcetesting' require 'puppettest/parsertesting' require 'puppettest/support/collection' class TestAST < Test::Unit::TestCase include PuppetTest::ParserTesting include PuppetTest::ResourceTesting include PuppetTest::Support::Collection 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 ) } # We initialized it to true, so we should get that first ret = nil assert_nothing_raised { ret = astif.evaluate("yay") } assert_equal(:if, ret) # Now set it to false and check that faketest.evaluate = false assert_nothing_raised { ret = astif.evaluate("yay") } assert_equal(:else, ret) end # Make sure our override object behaves "correctly" def test_override scope = mkscope ref = nil assert_nothing_raised do ref = resourceoverride("file", "/yayness", "owner" => "blah", "group" => "boo") end Puppet::Parser::Resource.expects(:new).with { |o| o.is_a?(Hash) }.returns(:override) scope.compile.expects(:store_override).with(:override) ret = nil assert_nothing_raised do ret = ref.evaluate scope end assert_equal(:override, ret, "Did not return override") end # make sure our resourcedefaults ast object works correctly. def test_resourcedefaults scope = mkscope # Now make some defaults for files args = {:source => "/yay/ness", :group => "yayness"} assert_nothing_raised do obj = defaultobj "file", args obj.evaluate scope end hash = nil assert_nothing_raised do hash = scope.lookupdefaults("File") end hash.each do |name, value| assert_instance_of(Symbol, name) # params always convert assert_instance_of(Puppet::Parser::Resource::Param, value) end args.each do |name, value| assert(hash[name], "Did not get default %s" % name) assert_equal(value, hash[name].value) end end - def test_node - scope = mkscope - parser = scope.compile.parser - - # Define a base node - basenode = parser.newnode "basenode", :code => AST::ASTArray.new(:children => [ - resourcedef("file", "/tmp/base", "owner" => "root") - ]) - - # Now define a subnode - nodes = parser.newnode ["mynode", "othernode"], - :code => AST::ASTArray.new(:children => [ - resourcedef("file", "/tmp/mynode", "owner" => "root"), - resourcedef("file", "/tmp/basenode", "owner" => "daemon") - ]) - - assert_instance_of(Array, nodes) - - # Make sure we can find them all. - %w{mynode othernode}.each do |node| - assert(parser.nodes[node], "Could not find %s" % node) - end - mynode = parser.nodes["mynode"] - - # Now try evaluating the node - assert_nothing_raised do - mynode.evaluate scope, scope.resource - 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]) - - basefile = scope.findresource "File[/tmp/basenode]" - assert(basefile, "Could not find file from base node") - assert_equal("daemon", basefile[:owner]) - - # Now make sure we can evaluate nodes with parents - child = parser.newnode(%w{child}, :parent => "basenode").shift - - newscope = mkscope :parser => parser - assert_nothing_raised do - child.evaluate newscope, scope.resource - end - - assert(newscope.findresource("File[/tmp/base]"), - "Could not find base resource") - end - def test_collection scope = mkscope coll = nil assert_nothing_raised do coll = AST::Collection.new(:type => "file", :form => :virtual) end assert_instance_of(AST::Collection, coll) ret = nil assert_nothing_raised do ret = coll.evaluate scope end assert_instance_of(Puppet::Parser::Collector, ret) # Now make sure we get it back from the scope colls = scope.compile.instance_variable_get("@collections") assert_equal([ret], colls, "Did not store collector in config's collection list") end def test_virtual_collexp scope = mkscope # make a resource resource = mkresource(:type => "file", :title => "/tmp/testing", :scope => scope, :params => {:owner => "root", :group => "bin", :mode => "644"}) run_collection_queries(:virtual) do |string, result, query| code = nil assert_nothing_raised do str, code = query.evaluate scope end assert_instance_of(Proc, code) assert_nothing_raised do assert_equal(result, code.call(resource), "'#{string}' failed") end end end end diff --git a/test/language/ast/definition.rb b/test/language/ast/definition.rb index 5f415eb41..d4e4bd185 100755 --- a/test/language/ast/definition.rb +++ b/test/language/ast/definition.rb @@ -1,166 +1,166 @@ #!/usr/bin/env ruby # # Created by Luke A. Kanies on 2006-02-20. # Copyright (c) 2006. All rights reserved. require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' require 'mocha' require 'puppettest/parsertesting' require 'puppettest/resourcetesting' class TestASTDefinition < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting include PuppetTest::ResourceTesting AST = Puppet::Parser::AST def test_initialize parser = mkparser # Create a new definition klass = parser.newdefine "yayness", :arguments => [["owner", stringobj("nobody")], %w{mode}], :code => AST::ASTArray.new( :children => [resourcedef("file", "/tmp/$name", "owner" => varref("owner"), "mode" => varref("mode"))] ) # 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 [:random, "random"].each do |var| assert(! klass.validattr?(var), "%s was considered valid" % var.inspect) end end def test_evaluate parser = mkparser config = mkcompile config.send(:evaluate_main) scope = config.topscope klass = parser.newdefine "yayness", :arguments => [["owner", stringobj("nobody")], %w{mode}], :code => AST::ASTArray.new( :children => [resourcedef("file", "/tmp/$name", "owner" => varref("owner"), "mode" => varref("mode"))] ) resource = Puppet::Parser::Resource.new( :title => "first", :type => "yayness", :exported => false, :virtual => false, :scope => scope, :source => scope.source ) resource.send(:set_parameter, "name", "first") resource.send(:set_parameter, "mode", "755") resource.stubs(:title) assert_nothing_raised do - klass.evaluate(scope, resource) + klass.evaluate_code(resource) end firstobj = config.findresource("File[/tmp/first]") assert(firstobj, "Did not create /tmp/first obj") assert_equal("File", firstobj.type) assert_equal("/tmp/first", firstobj.title) assert_equal("nobody", firstobj[:owner]) assert_equal("755", firstobj[:mode]) # Make sure we can't evaluate it with the same args assert_raise(Puppet::ParseError) do - klass.evaluate(scope, resource) + klass.evaluate_code(resource) end # Now create another with different args resource2 = Puppet::Parser::Resource.new( :title => "second", :type => "yayness", :exported => false, :virtual => false, :scope => scope, :source => scope.source ) resource2.send(:set_parameter, "name", "second") resource2.send(:set_parameter, "mode", "755") resource2.send(:set_parameter, "owner", "daemon") assert_nothing_raised do - klass.evaluate(scope, resource2) + klass.evaluate_code(resource2) end secondobj = config.findresource("File[/tmp/second]") assert(secondobj, "Did not create /tmp/second obj") assert_equal("File", secondobj.type) assert_equal("/tmp/second", secondobj.title) assert_equal("daemon", secondobj[:owner]) assert_equal("755", secondobj[:mode]) end # #539 - definitions should support both names and titles def test_names_and_titles parser = mkparser scope = mkscope :parser => parser [ {:name => "one", :title => "two"}, {:title => "mytitle"} ].each_with_index do |hash, i| # Create a definition that uses both name and title. Put this # inside the loop so the subscope expectations work. klass = parser.newdefine "yayness%s" % i resource = Puppet::Parser::Resource.new( :title => hash[:title], :type => "yayness%s" % i, :exported => false, :virtual => false, :scope => scope, :source => scope.source ) subscope = klass.subscope(scope, resource) klass.expects(:subscope).returns(subscope) if hash[:name] resource.stubs(:to_hash).returns({:name => hash[:name]}) end assert_nothing_raised("Could not evaluate definition with %s" % hash.inspect) do - klass.evaluate(scope, resource) + klass.evaluate_code(resource) end name = hash[:name] || hash[:title] title = hash[:title] assert_equal(name, subscope.lookupvar("name"), "Name did not get set correctly") assert_equal(title, subscope.lookupvar("title"), "title did not get set correctly") [:name, :title].each do |param| val = resource.send(param) assert(subscope.tags.include?(val), "Scope was not tagged with %s '%s'" % [param, val]) end end end # Testing the root cause of #615. We should be using the fqname for the type, instead # of just the short name. def test_fully_qualified_types parser = mkparser klass = parser.newclass("one::two") assert_equal("one::two", klass.classname, "Class did not get fully qualified class name") end end diff --git a/test/language/ast/hostclass.rb b/test/language/ast/hostclass.rb index abc5e05be..7697317a6 100755 --- a/test/language/ast/hostclass.rb +++ b/test/language/ast/hostclass.rb @@ -1,184 +1,184 @@ #!/usr/bin/env ruby # # Created by Luke A. Kanies on 2006-02-20. # Copyright (c) 2006. All rights reserved. require File.dirname(__FILE__) + '/../../lib/puppettest' require 'puppettest' require 'puppettest/parsertesting' require 'puppettest/resourcetesting' require 'mocha' class TestASTHostClass < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting include PuppetTest::ResourceTesting AST = Puppet::Parser::AST def test_hostclass scope = mkscope parser = scope.compile.parser # Create the class we're testing, first with no parent klass = parser.newclass "first", :code => AST::ASTArray.new( :children => [resourcedef("file", "/tmp", "owner" => "nobody", "mode" => "755")] ) resource = Puppet::Parser::Resource.new(:type => "class", :title => "first", :scope => scope) assert_nothing_raised do - klass.evaluate(scope, resource) + klass.evaluate_code(resource) end # Then try it again assert_nothing_raised do - klass.evaluate(scope, resource) + klass.evaluate_code(resource) end assert(scope.compile.class_scope(klass), "Class was not considered evaluated") tmp = scope.findresource("File[/tmp]") assert(tmp, "Could not find file /tmp") assert_equal("nobody", tmp[:owner]) assert_equal("755", tmp[:mode]) # Now create a couple more classes. newbase = parser.newclass "newbase", :code => AST::ASTArray.new( :children => [resourcedef("file", "/tmp/other", "owner" => "nobody", "mode" => "644")] ) newsub = parser.newclass "newsub", :parent => "newbase", :code => AST::ASTArray.new( :children => [resourcedef("file", "/tmp/yay", "owner" => "nobody", "mode" => "755"), resourceoverride("file", "/tmp/other", "owner" => "daemon") ] ) # Override a different variable in the top scope. moresub = parser.newclass "moresub", :parent => "newbase", :code => AST::ASTArray.new( :children => [resourceoverride("file", "/tmp/other", "mode" => "755")] ) assert_nothing_raised do - newsub.evaluate(scope, resource) + newsub.evaluate_code(resource) end assert_nothing_raised do - moresub.evaluate(scope, resource) + moresub.evaluate_code(resource) end assert(scope.compile.class_scope(newbase), "Did not eval newbase") assert(scope.compile.class_scope(newsub), "Did not eval newsub") yay = scope.findresource("File[/tmp/yay]") assert(yay, "Did not find file /tmp/yay") assert_equal("nobody", yay[:owner]) assert_equal("755", yay[:mode]) 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 # Make sure that classes set their namespaces to themselves. This # way they start looking for definitions in their own namespace. def test_hostclass_namespace scope = mkscope parser = scope.compile.parser # Create a new class klass = nil assert_nothing_raised do klass = parser.newclass "funtest" end # Now define a definition in that namespace define = nil assert_nothing_raised do define = parser.newdefine "funtest::mydefine" end assert_equal("funtest", klass.namespace, "component namespace was not set in the class") assert_equal("funtest", define.namespace, "component namespace was not set in the definition") newscope = klass.subscope(scope, mock("resource")) assert_equal(["funtest"], newscope.namespaces, "Scope did not inherit namespace") # Now make sure we can find the define assert(newscope.finddefine("mydefine"), "Could not find definition in my enclosing class") end # Make sure that our scope is a subscope of the parentclass's scope. # At the same time, make sure definitions in the parent class can be # found within the subclass (#517). def test_parent_scope_from_parentclass scope = mkscope parser = scope.compile.parser source = parser.newclass "" parser.newclass("base") fun = parser.newdefine("base::fun") parser.newclass("middle", :parent => "base") parser.newclass("sub", :parent => "middle") scope = mkscope :parser => parser ret = nil assert_nothing_raised do ret = scope.compile.evaluate_classes(["sub"], scope) end scope.compile.send(:evaluate_generators) subscope = scope.compile.class_scope(scope.findclass("sub")) assert(subscope, "could not find sub scope") mscope = scope.compile.class_scope(scope.findclass("middle")) assert(mscope, "could not find middle scope") pscope = scope.compile.class_scope(scope.findclass("base")) assert(pscope, "could not find parent scope") assert(pscope == mscope.parent, "parent scope of middle was not set correctly") assert(mscope == subscope.parent, "parent scope of sub was not set correctly") result = mscope.finddefine("fun") assert(result, "could not find parent-defined definition from middle") assert(fun == result, "found incorrect parent-defined definition from middle") result = subscope.finddefine("fun") assert(result, "could not find parent-defined definition from sub") assert(fun == result, "found incorrect parent-defined definition from sub") end # #795 - make sure the subclass's tags get set before we # evaluate the parent class, so we can be sure that the parent # class can switch based on the sub classes. def test_tags_set_before_parent_is_evaluated scope = mkscope parser = scope.compile.parser base = parser.newclass "base" sub = parser.newclass "sub", :parent => "base" - base.expects(:safeevaluate).with do |*args| - assert(scope.compile.catalog.tags.include?("sub"), "Did not tag with sub class name before evaluating base class") - base.evaluate(*args) + base.expects(:evaluate_code).with do |*args| + assert(scope.catalog.tags.include?("sub"), "Did not tag with sub class name before evaluating base class") + base.evaluate_code(*args) true end - sub.evaluate scope, scope.resource + sub.evaluate_code scope.resource end end diff --git a/test/language/ast/node.rb b/test/language/ast/node.rb new file mode 100755 index 000000000..df732480d --- /dev/null +++ b/test/language/ast/node.rb @@ -0,0 +1,68 @@ +#!/usr/bin/env ruby +# +# Created by Luke A. Kanies on 2008-02-09. +# Copyright (c) 2008. All rights reserved. + +require File.dirname(__FILE__) + '/../../lib/puppettest' + +require 'puppettest' +require 'mocha' +require 'puppettest/parsertesting' +require 'puppettest/resourcetesting' + +class TestASTNode < Test::Unit::TestCase + include PuppetTest + include PuppetTest::ParserTesting + include PuppetTest::ResourceTesting + AST = Puppet::Parser::AST + + def test_node + scope = mkscope + parser = scope.compile.parser + + # Define a base node + basenode = parser.newnode "basenode", :code => AST::ASTArray.new(:children => [ + resourcedef("file", "/tmp/base", "owner" => "root") + ]) + + # Now define a subnode + nodes = parser.newnode ["mynode", "othernode"], + :code => AST::ASTArray.new(:children => [ + resourcedef("file", "/tmp/mynode", "owner" => "root"), + resourcedef("file", "/tmp/basenode", "owner" => "daemon") + ]) + + assert_instance_of(Array, nodes) + + # Make sure we can find them all. + %w{mynode othernode}.each do |node| + assert(parser.nodes[node], "Could not find %s" % node) + end + mynode = parser.nodes["mynode"] + + # Now try evaluating the node + assert_nothing_raised do + mynode.evaluate_code scope.resource + 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]) + + basefile = scope.findresource "File[/tmp/basenode]" + assert(basefile, "Could not find file from base node") + assert_equal("daemon", basefile[:owner]) + + # Now make sure we can evaluate nodes with parents + child = parser.newnode(%w{child}, :parent => "basenode").shift + + newscope = mkscope :parser => parser + assert_nothing_raised do + child.evaluate_code newscope.resource + end + + assert(newscope.findresource("File[/tmp/base]"), + "Could not find base resource") + end +end diff --git a/test/language/resource.rb b/test/language/resource.rb index 3c027ed07..3aa9dcf6a 100755 --- a/test/language/resource.rb +++ b/test/language/resource.rb @@ -1,492 +1,492 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../lib/puppettest' require 'puppettest' require 'puppettest/resourcetesting' class TestResource < PuppetTest::TestCase include PuppetTest include PuppetTest::ParserTesting include PuppetTest::ResourceTesting Parser = Puppet::Parser AST = Parser::AST Resource = Puppet::Parser::Resource Reference = Puppet::Parser::Resource::Reference def setup super Puppet[:trace] = false end def teardown mocha_verify end def test_initialize args = {:type => "resource", :title => "testing", :scope => mkscope} # Check our arg requirements args.each do |name, value| try = args.dup try.delete(name) assert_raise(ArgumentError, "Did not fail when %s was missing" % name) do Parser::Resource.new(try) end end res = nil assert_nothing_raised do res = Parser::Resource.new(args) end ref = res.instance_variable_get("@ref") assert_equal("Resource", ref.type, "did not set resource type") assert_equal("testing", ref.title, "did not set resource title") end def test_merge res = mkresource other = mkresource # First try the case where the resource is not allowed to override res.source = "source1" other.source = "source2" other.source.expects(:child_of?).with("source1").returns(false) assert_raise(Puppet::ParseError, "Allowed unrelated resources to override") do res.merge(other) end # Next try it when the sources are equal. res.source = "source3" other.source = res.source other.source.expects(:child_of?).with("source3").never params = {:a => :b, :c => :d} other.expects(:params).returns(params) res.expects(:override_parameter).with(:b) res.expects(:override_parameter).with(:d) res.merge(other) # And then parentage is involved other = mkresource res.source = "source3" other.source = "source4" other.source.expects(:child_of?).with("source3").returns(true) params = {:a => :b, :c => :d} other.expects(:params).returns(params) res.expects(:override_parameter).with(:b) res.expects(:override_parameter).with(:d) res.merge(other) end # the [] method def test_array_accessors res = mkresource params = res.instance_variable_get("@params") assert_nil(res[:missing], "Found a missing parameter somehow") params[:something] = stub(:value => "yay") assert_equal("yay", res[:something], "Did not correctly call value on the parameter") res.expects(:title).returns(:mytitle) assert_equal(:mytitle, res[:title], "Did not call title when asked for it as a param") end # Make sure any defaults stored in the scope get added to our resource. def test_add_defaults res = mkresource params = res.instance_variable_get("@params") params[:a] = :b res.scope.expects(:lookupdefaults).with(res.type).returns(:a => :replaced, :c => :d) res.expects(:debug) res.send(:add_defaults) assert_equal(:d, params[:c], "Did not set default") assert_equal(:b, params[:a], "Replaced parameter with default") end def test_finish res = mkresource res.expects(:add_overrides) res.expects(:add_defaults) res.expects(:add_metaparams) res.expects(:validate) res.finish end # Make sure we paramcheck our params def test_validate res = mkresource params = res.instance_variable_get("@params") params[:one] = :two params[:three] = :four res.expects(:paramcheck).with(:one) res.expects(:paramcheck).with(:three) res.send(:validate) end def test_override_parameter res = mkresource params = res.instance_variable_get("@params") # There are three cases, with the second having two options: # No existing parameter. param = stub(:name => "myparam") res.send(:override_parameter, param) assert_equal(param, params["myparam"], "Override was not added to param list") # An existing parameter that we can override. source = stub(:child_of? => true) # Start out without addition params["param2"] = stub(:source => :whatever) param = stub(:name => "param2", :source => source, :add => false) res.send(:override_parameter, param) assert_equal(param, params["param2"], "Override was not added to param list") # Try with addition. params["param2"] = stub(:value => :a, :source => :whatever) param = stub(:name => "param2", :source => source, :add => true, :value => :b) param.expects(:value=).with([:a, :b]) res.send(:override_parameter, param) assert_equal(param, params["param2"], "Override was not added to param list") # And finally, make sure we throw an exception when the sources aren't related source = stub(:child_of? => false) params["param2"] = stub(:source => :whatever, :file => :f, :line => :l) old = params["param2"] param = stub(:name => "param2", :source => source, :file => :f, :line => :l) assert_raise(Puppet::ParseError, "Did not fail when params conflicted") do res.send(:override_parameter, param) end assert_equal(old, params["param2"], "Param was replaced irrespective of conflict") end def test_set_parameter res = mkresource params = res.instance_variable_get("@params") # First test the simple case: It's already a parameter param = mock('param') param.expects(:is_a?).with(Resource::Param).returns(true) param.expects(:name).returns("pname") res.send(:set_parameter, param) assert_equal(param, params["pname"], "Parameter was not added to hash") # Now the case where there's no value but it's not a param param = mock('param') param.expects(:is_a?).with(Resource::Param).returns(false) assert_raise(ArgumentError, "Did not fail when a non-param was passed") do res.send(:set_parameter, param) end # and the case where a value is passed in param = stub :name => "pname", :value => "whatever" Resource::Param.expects(:new).with(:name => "pname", :value => "myvalue", :source => res.source).returns(param) res.send(:set_parameter, "pname", "myvalue") assert_equal(param, params["pname"], "Did not put param in hash") end def test_paramcheck # There are three cases here: # It's a valid parameter res = mkresource ref = mock('ref') res.instance_variable_set("@ref", ref) klass = mock("class") ref.expects(:typeclass).returns(klass).times(4) klass.expects(:validattr?).with("good").returns(true) assert(res.send(:paramcheck, :good), "Did not allow valid param") # It's name or title klass.expects(:validattr?).with("name").returns(false) assert(res.send(:paramcheck, :name), "Did not allow name") klass.expects(:validattr?).with("title").returns(false) assert(res.send(:paramcheck, :title), "Did not allow title") # It's not actually allowed klass.expects(:validattr?).with("other").returns(false) res.expects(:fail) ref.expects(:type) res.send(:paramcheck, :other) end def test_to_transobject # First try translating a builtin resource. Make sure we use some references # and arrays, to make sure they translate correctly. source = mock("source") scope = mkscope scope.stubs(:tags).returns([]) refs = [] 4.times { |i| refs << Puppet::Parser::Resource::Reference.new(:title => "file%s" % i, :type => "file") } res = Parser::Resource.new :type => "file", :title => "/tmp", :source => source, :scope => scope, :params => paramify(source, :owner => "nobody", :group => %w{you me}, :require => refs[0], :ignore => %w{svn}, :subscribe => [refs[1], refs[2]], :notify => [refs[3]]) obj = nil assert_nothing_raised do obj = res.to_trans end assert_instance_of(Puppet::TransObject, obj) assert_equal(obj.type, res.type.downcase) assert_equal(obj.name, res.title) # TransObjects use strings, resources use symbols assert_equal("nobody", obj["owner"], "Single-value string was not passed correctly") assert_equal(%w{you me}, obj["group"], "Array of strings was not passed correctly") assert_equal("svn", obj["ignore"], "Array with single string was not turned into single value") assert_equal(["file", refs[0].title], obj["require"], "Resource reference was not passed correctly") assert_equal([["file", refs[1].title], ["file", refs[2].title]], obj["subscribe"], "Array of resource references was not passed correctly") assert_equal(["file", refs[3].title], obj["notify"], "Array with single resource reference was not turned into single value") end # FIXME This isn't a great test, but I need to move on. def test_to_transbucket bucket = mock("transbucket") source = mock("source") scope = mkscope res = Parser::Resource.new :type => "mydefine", :title => "yay", :source => source, :scope => scope result = res.to_trans assert_equal("yay", result.name, "did not set bucket name correctly") assert_equal("Mydefine", result.type, "did not set bucket type correctly") end def test_evaluate # First try the most common case, we're not a builtin type. res = mkresource ref = res.instance_variable_get("@ref") type = mock("type") ref.expects(:definedtype).returns(type) res.expects(:finish) res.scope = mock("scope") config = mock("config") res.scope.expects(:compile).returns(config) config.expects(:delete_resource).with(res) - type.expects(:evaluate).with(res.scope, res) + type.expects(:evaluate_code).with(res) res.evaluate end def test_add_overrides # Try it with nil res = mkresource res.scope = mock('scope') config = mock("config") res.scope.expects(:compile).returns(config) config.expects(:resource_overrides).with(res).returns(nil) res.expects(:merge).never res.send(:add_overrides) # And an empty array res = mkresource res.scope = mock('scope') config = mock("config") res.scope.expects(:compile).returns(config) config.expects(:resource_overrides).with(res).returns([]) res.expects(:merge).never res.send(:add_overrides) # And with some overrides res = mkresource res.scope = mock('scope') config = mock("config") res.scope.expects(:compile).returns(config) returns = %w{a b} config.expects(:resource_overrides).with(res).returns(returns) res.expects(:merge).with("a") res.expects(:merge).with("b") res.send(:add_overrides) assert(returns.empty?, "Did not clear overrides") end def test_proxymethods res = Parser::Resource.new :type => "evaltest", :title => "yay", :source => mock("source"), :scope => mkscope assert_equal("Evaltest", res.type) assert_equal("yay", res.title) assert_equal(false, res.builtin?) 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} res.scope = mkscope 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]} res.scope = mkscope 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, "Definition was considered builtin") end # The second part of #539 - make sure resources pass the arguments # correctly. def test_title_with_definitions parser = mkparser define = parser.newdefine "yayness", :code => resourcedef("file", "/tmp", "owner" => varref("name"), "mode" => varref("title")) klass = parser.findclass("", "") should = {:name => :owner, :title => :mode} [ {:name => "one", :title => "two"}, {:title => "three"}, ].each do |hash| config = mkcompile parser args = {:type => "yayness", :title => hash[:title], :source => klass, :scope => config.topscope} if hash[:name] args[:params] = {:name => hash[:name]} else args[:params] = {} # override the defaults end res = nil assert_nothing_raised("Could not create res with %s" % hash.inspect) do res = mkresource(args) end assert_nothing_raised("Could not eval res with %s" % hash.inspect) do res.evaluate end made = config.topscope.findresource("File[/tmp]") assert(made, "Did not create resource with %s" % hash.inspect) should.each do |orig, param| assert_equal(hash[orig] || hash[:title], made[param], "%s was not set correctly with %s" % [param, hash.inspect]) end end end # part of #629 -- the undef keyword. Make sure 'undef' params get skipped. def test_undef_and_to_hash res = mkresource :type => "file", :title => "/tmp/testing", :source => mock("source"), :scope => mkscope, :params => {:owner => :undef, :mode => "755"} hash = nil assert_nothing_raised("Could not convert resource with undef to hash") do hash = res.to_hash end assert_nil(hash[:owner], "got a value for an undef parameter") end # #643 - Make sure virtual defines result in virtual resources def test_virtual_defines parser = mkparser define = parser.newdefine("yayness", :code => resourcedef("file", varref("name"), "mode" => "644")) config = mkcompile(parser) res = mkresource :type => "yayness", :title => "foo", :params => {}, :scope => config.topscope res.virtual = true result = nil assert_nothing_raised("Could not evaluate defined resource") do result = res.evaluate end scope = res.scope newres = scope.findresource("File[foo]") assert(newres, "Could not find resource") assert(newres.virtual?, "Virtual defined resource generated non-virtual resources") # Now try it with exported resources res = mkresource :type => "yayness", :title => "bar", :params => {}, :scope => config.topscope res.exported = true result = nil assert_nothing_raised("Could not evaluate exported resource") do result = res.evaluate end scope = res.scope newres = scope.findresource("File[bar]") assert(newres, "Could not find resource") assert(newres.exported?, "Exported defined resource generated non-exported resources") assert(newres.virtual?, "Exported defined resource generated non-virtual resources") end # Make sure tags behave appropriately. def test_tags scope_resource = stub 'scope_resource', :tags => %w{srone srtwo} scope = stub 'scope', :resource => scope_resource resource = Puppet::Parser::Resource.new(:type => "file", :title => "yay", :scope => scope, :source => mock('source')) # Make sure we get the type and title %w{yay file}.each do |tag| assert(resource.tags.include?(tag), "Did not tag resource with %s" % tag) end # 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 resource.tag tag end end # make sure good tags make it through. tags = %w{good-tag yaytag GoodTag another_tag a ab A} tags.each do |tag| assert_nothing_raised("Tag #{tag} was considered invalid") do resource.tag tag end end # make sure we get each of them. ptags = resource.tags tags.each do |tag| assert(ptags.include?(tag.downcase), "missing #{tag}") end end end