diff --git a/lib/puppet/parser/ast/collection.rb b/lib/puppet/parser/ast/collection.rb index 7daf031cf..a7564f0c9 100644 --- a/lib/puppet/parser/ast/collection.rb +++ b/lib/puppet/parser/ast/collection.rb @@ -1,30 +1,30 @@ 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 Collection < AST::Branch attr_accessor :type, :query, :form # We return an object that does a late-binding evaluation. def evaluate(hash) scope = hash[:scope] if self.query str, code = self.query.safeevaluate :scope => scope else str = code = nil end newcoll = Puppet::Parser::Collector.new(scope, @type, str, code, self.form) - scope.configuration.add_collection(newcoll) + scope.compile.add_collection(newcoll) newcoll end end end # $Id$ diff --git a/lib/puppet/parser/collector.rb b/lib/puppet/parser/collector.rb index 0846c40ab..c9d5ed5f0 100644 --- a/lib/puppet/parser/collector.rb +++ b/lib/puppet/parser/collector.rb @@ -1,178 +1,178 @@ # 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, :vquery, :rquery, :form, :resources # Collect exported objects. def collect_exported # 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).reject { |r| ! r.virtual? } count = 0 unless @scope.host raise Puppet::DevError, "Cannot collect resources for a nil host" end # We're going to collect objects from rails, but we don't want any # objects from this host. unless ActiveRecord::Base.connected? Puppet::Rails.init end host = Puppet::Rails::Host.find_by_name(@scope.host) args = {:include => {:param_values => :param_name}} args[:conditions] = "(exported = 't' AND restype = '%s')" % [@type] if @equery args[:conditions] += " AND (%s)" % [@equery] end if host args[:conditions] = "host_id != %s AND %s" % [host.id, args[:conditions]] 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. time = Puppet::Util.thinmark do Puppet::Rails::Resource.find(:all, @type, true, args ).each do |obj| if resource = export_resource(obj) count += 1 resources << resource end end end scope.debug("Collected %s %s resource%s in %.2f seconds" % [count, @type, count == 1 ? "" : "s", time]) return resources end def collect_resources unless @resources.is_a?(Array) @resources = [@resources] end method = "collect_#{form.to_s}_resources" send(method) end def collect_exported_resources raise Puppet::ParseError, "realize() is not yet implemented for exported resources" end # Collect resources directly; this is the result of using 'realize', # which specifies resources, rather than using a normal collection. def collect_virtual_resources result = @resources.dup.collect do |ref| if res = @scope.findresource(ref.to_s) @resources.delete(ref) res end end.reject { |r| r.nil? }.each do |res| res.virtual = false end # If there are no more resources to find, delete this from the list # of collections. if @resources.empty? - @scope.configuration.delete_collection(self) + @scope.compile.delete_collection(self) end return result end - # Collect just virtual objects, from our local configuration. + # Collect just virtual objects, from our local compile. def collect_virtual(exported = false) if exported method = :exported? else method = :virtual? end - scope.configuration.resources.find_all do |resource| + scope.compile.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 if self.resources if objects = collect_resources and ! objects.empty? return objects else return false end else method = "collect_#{@form.to_s}" objects = send(method).each { |obj| obj.virtual = false } if objects.empty? return false else return objects end end end def initialize(scope, type, equery, vquery, form) @scope = scope @type = type @equery = equery @vquery = vquery @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.vquery return self.vquery.call(resource) else return true end end def export_resource(obj) if existing = @scope.findresource(obj.restype, obj.title) # See if we exported it; if so, just move on if @scope.host == obj.host.name return nil else # Next see if we've already collected this resource if existing.rails_id == obj.id # This is the one we've already collected return nil else raise Puppet::ParseError, "Exported resource %s cannot override local resource" % [obj.ref] end end end begin 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 # über-lame. scope.setresource(resource) rescue => detail if Puppet[:trace] puts detail.backtrace end raise end resource.exported = false return resource end end # $Id$ diff --git a/lib/puppet/parser/configuration.rb b/lib/puppet/parser/compile.rb similarity index 97% rename from lib/puppet/parser/configuration.rb rename to lib/puppet/parser/compile.rb index a56bfa080..6cfe66fce 100644 --- a/lib/puppet/parser/configuration.rb +++ b/lib/puppet/parser/compile.rb @@ -1,558 +1,558 @@ # Created by Luke A. Kanies on 2007-08-13. # Copyright (c) 2007. All rights reserved. require 'puppet/external/gratr/digraph' require 'puppet/external/gratr/import' require 'puppet/external/gratr/dot' require 'puppet/node' require 'puppet/util/errors' # Maintain a graph of scopes, along with a bunch of data # about the individual configuration we're compiling. -class Puppet::Parser::Configuration +class Puppet::Parser::Compile include Puppet::Util include Puppet::Util::Errors attr_reader :topscope, :parser, :node, :facts, :collections attr_accessor :extraction_format attr_writer :ast_nodes # Add a collection to the global list. def add_collection(coll) @collections << coll end # Do we use nodes found in the code, vs. the external node sources? def ast_nodes? defined?(@ast_nodes) and @ast_nodes end # Store the fact that we've evaluated a class, and store a reference to # the scope in which it was evaluated, so that we can look it up later. def class_set(name, scope) if existing = @class_scopes[name] if existing.nodescope? or scope.nodescope? raise Puppet::ParseError, "Cannot have classes, nodes, or definitions with the same name" else raise Puppet::DevError, "Somehow evaluated the same class twice" end end @class_scopes[name] = scope tag(name) end # Return the scope associated with a class. This is just here so # that subclasses can set their parent scopes to be the scope of # their parent class, and it's also used when looking up qualified # variables. def class_scope(klass) # They might pass in either the class or class name if klass.respond_to?(:classname) @class_scopes[klass.classname] else @class_scopes[klass] end end # Return a list of all of the defined classes. def classlist return @class_scopes.keys.reject { |k| k == "" } end # Compile our configuration. This mostly revolves around finding and evaluating classes. # This is the main entry into our configuration. def compile # Set the client's parameters into the top scope. set_node_parameters() evaluate_main() evaluate_ast_node() evaluate_classes() evaluate_generators() fail_on_unevaluated() finish() if Puppet[:storeconfigs] store() end return extract() end # FIXME There are no tests for this. def delete_collection(coll) @collections.delete(coll) if @collections.include?(coll) end # FIXME There are no tests for this. def delete_resource(resource) @resource_table.delete(resource.ref) if @resource_table.include?(resource.ref) @resource_graph.remove_vertex!(resource) if @resource_graph.vertex?(resource) end # Return the node's environment. def environment unless defined? @environment if node.environment and node.environment != "" @environment = node.environment end end @environment end # Evaluate each class in turn. If there are any classes we can't find, # just tag the configuration and move on. def evaluate_classes(classes = nil) classes ||= node.classes found = [] classes.each do |name| if klass = @parser.findclass("", name) # This will result in class_set getting called, which # will in turn result in tags. Yay. klass.safeevaluate(:scope => topscope) found << name else Puppet.info "Could not find class %s for %s" % [name, node.name] tag(name) end end found end # Make sure we support the requested extraction format. def extraction_format=(value) unless respond_to?("extract_to_%s" % value) raise ArgumentError, "Invalid extraction format %s" % value end @extraction_format = value end # Return a resource by either its ref or its type and title. def findresource(string, name = nil) if name string = "%s[%s]" % [string.capitalize, name] end @resource_table[string] end # Set up our configuration. We require a parser # and a node object; the parser is so we can look up classes # and AST nodes, and the node has all of the client's info, # like facts and environment. def initialize(node, parser, options = {}) @node = node @parser = parser options.each do |param, value| begin send(param.to_s + "=", value) rescue NoMethodError - raise ArgumentError, "Configuration objects do not accept %s" % param + raise ArgumentError, "Compile objects do not accept %s" % param end end @extraction_format ||= :transportable initvars() end # Create a new scope, with either a specified parent scope or # using the top scope. Adds an edge between the scope and # its parent to the graph. def newscope(parent, options = {}) parent ||= @topscope - options[:configuration] = self + options[:compile] = self options[:parser] ||= self.parser scope = Puppet::Parser::Scope.new(options) @scope_graph.add_edge!(parent, scope) scope end # Find the parent of a given scope. Assumes scopes only ever have # one in edge, which will always be true. def parent(scope) if ary = @scope_graph.adjacent(scope, :direction => :in) and ary.length > 0 ary[0] else nil end end # Return any overrides for the given resource. def resource_overrides(resource) @resource_overrides[resource.ref] end # Return a list of all resources. def resources @resource_table.values end # Store a resource override. def store_override(override) override.override = true # If possible, merge the override in immediately. if resource = @resource_table[override.ref] resource.merge(override) else # Otherwise, store the override for later; these # get evaluated in Resource#finish. @resource_overrides[override.ref] << override end end # Store a resource in our resource table. def store_resource(scope, resource) # This might throw an exception verify_uniqueness(resource) # Store it in the global table. @resource_table[resource.ref] = resource # And in the resource graph. At some point, this might supercede # the global resource table, but the table is a lot faster # so it makes sense to maintain for now. @resource_graph.add_edge!(scope, resource) end private # If ast nodes are enabled, then see if we can find and evaluate one. def evaluate_ast_node return unless ast_nodes? # Now see if we can find the node. astnode = nil #nodes = @parser.nodes @node.names.each do |name| break if astnode = @parser.nodes[name.to_s.downcase] end unless astnode astnode = @parser.nodes["default"] end unless astnode raise Puppet::ParseError, "Could not find default node or by name with '%s'" % node.names.join(", ") end astnode.safeevaluate :scope => topscope end # Evaluate our collections and return true if anything returned an object. # The 'true' is used to continue a loop, so it's important. def evaluate_collections return false if @collections.empty? found_something = false exceptwrap do @collections.each do |collection| if collection.evaluate found_something = true end end end return found_something end # Make sure all of our resources have been evaluated into native resources. # We return true if any resources have, so that we know to continue the # evaluate_generators loop. def evaluate_definitions exceptwrap do if ary = unevaluated_resources ary.each do |resource| resource.evaluate end # If we evaluated, let the loop know. return true else return false end end end # Iterate over collections and resources until we're sure that the whole - # configuration is evaluated. This is necessary because both collections + # compile is evaluated. This is necessary because both collections # and defined resources can generate new resources, which themselves could # be defined resources. def evaluate_generators count = 0 loop do done = true # Call collections first, then definitions. done = false if evaluate_collections done = false if evaluate_definitions break if done if count > 1000 raise Puppet::ParseError, "Somehow looped more than 1000 times while evaluating host configuration" end end end # Find and evaluate our main object, if possible. def evaluate_main if klass = @parser.findclass("", "") # Set the source, so objects can tell where they were defined. topscope.source = klass klass.safeevaluate :scope => topscope, :nosubscope => true end end # Turn our configuration graph into whatever the client is expecting. def extract send("extract_to_%s" % extraction_format) end # Create the traditional TransBuckets and TransObjects from our configuration # graph. This will hopefully be deprecated soon. def extract_to_transportable top = nil current = nil buckets = {} # I'm *sure* there's a simple way to do this using a breadth-first search # or something, but I couldn't come up with, and this is both fast # and simple, so I'm not going to worry about it too much. @scope_graph.vertices.each do |scope| # For each scope, we need to create a TransBucket, and then # put all of the scope's resources into that bucket, translating # each resource into a TransObject. # Unless the bucket's already been created, make it now and add # it to the cache. unless bucket = buckets[scope] bucket = buckets[scope] = scope.to_trans end # First add any contained scopes @scope_graph.adjacent(scope, :direction => :out).each do |vertex| # If there's not already a bucket, then create and cache it. unless child_bucket = buckets[vertex] child_bucket = buckets[vertex] = vertex.to_trans end bucket.push child_bucket end # Then add the resources. if @resource_graph.vertex?(scope) @resource_graph.adjacent(scope, :direction => :out).each do |vertex| # Some resources don't get translated, e.g., virtual resources. if obj = vertex.to_trans bucket.push obj end end end end # Retrive the bucket for the top-level scope and set the appropriate metadata. result = buckets[topscope] result.copy_type_and_name(topscope) unless classlist.empty? result.classes = classlist end # Clear the cache to encourage the GC buckets.clear return result end # Make sure the entire configuration is evaluated. def fail_on_unevaluated fail_on_unevaluated_overrides fail_on_unevaluated_resource_collections end # If there are any resource overrides remaining, then we could # not find the resource they were supposed to override, so we # want to throw an exception. def fail_on_unevaluated_overrides remaining = [] @resource_overrides.each do |name, overrides| remaining += overrides end unless remaining.empty? fail Puppet::ParseError, "Could not find object(s) %s" % remaining.collect { |o| o.ref }.join(", ") end end # Make sure we don't have any remaining collections that specifically # look for resources, because we want to consider those to be # parse errors. def fail_on_unevaluated_resource_collections remaining = [] @collections.each do |coll| # We're only interested in the 'resource' collections, # which result from direct calls of 'realize'. Anything # else is allowed not to return resources. # Collect all of them, so we have a useful error. if r = coll.resources if r.is_a?(Array) remaining += r else remaining << r end end end unless remaining.empty? raise Puppet::ParseError, "Failed to realize virtual resources %s" % remaining.join(', ') end end # Make sure all of our resources and such have done any last work # necessary. def finish @resource_table.each { |name, resource| resource.finish if resource.respond_to?(:finish) } end # Set up all of our internal variables. def initvars # The table for storing class singletons. This will only actually # be used by top scopes and node scopes. @class_scopes = {} # The table for all defined resources. @resource_table = {} # The list of objects that will available for export. @exported_resources = {} # The list of overrides. This is used to cache overrides on objects # that don't exist yet. We store an array of each override. @resource_overrides = Hash.new do |overs, ref| overs[ref] = [] end # The list of collections that have been created. This is a global list, # but they each refer back to the scope that created them. @collections = [] # A list of tags we've generated; most class names. @tags = [] # Create our initial scope, our scope graph, and add the initial scope to the graph. - @topscope = Puppet::Parser::Scope.new(:configuration => self, :type => "main", :name => "top", :parser => self.parser) + @topscope = Puppet::Parser::Scope.new(:compile => self, :type => "main", :name => "top", :parser => self.parser) # For maintaining scope relationships. @scope_graph = GRATR::Digraph.new @scope_graph.add_vertex!(@topscope) # For maintaining the relationship between scopes and their resources. @resource_graph = GRATR::Digraph.new end # Set the node's parameters into the top-scope as variables. def set_node_parameters node.parameters.each do |param, value| @topscope.setvar(param, value) end end # Store the configuration into the database. def store unless Puppet.features.rails? raise Puppet::Error, "storeconfigs is enabled but rails is unavailable" end unless ActiveRecord::Base.connected? Puppet::Rails.connect end # We used to have hooks here for forking and saving, but I don't # think it's worth retaining at this point. store_to_active_record(@node, @resource_table.values) end # Do the actual storage. def store_to_active_record(node, resources) begin # We store all of the objects, even the collectable ones benchmark(:info, "Stored configuration for #{node.name}") do Puppet::Rails::Host.transaction do Puppet::Rails::Host.store(node, resources) end end rescue => detail if Puppet[:trace] puts detail.backtrace end Puppet.err "Could not store configs: %s" % detail.to_s end end # Add a tag. def tag(*names) names.each do |name| name = name.to_s @tags << name unless @tags.include?(name) end nil end # Return the list of tags. def tags @tags.dup end # Return an array of all of the unevaluated resources. These will be definitions, # which need to get evaluated into native resources. def unevaluated_resources ary = @resource_table.find_all do |name, object| ! object.builtin? and ! object.evaluated? end.collect { |name, object| object } if ary.empty? return nil else return ary end end # Verify that the given resource isn't defined elsewhere. def verify_uniqueness(resource) # Short-curcuit the common case, unless existing_resource = @resource_table[resource.ref] return true end if typeclass = Puppet::Type.type(resource.type) and ! typeclass.isomorphic? Puppet.info "Allowing duplicate %s" % typeclass.name return true end # Either it's a defined type, which are never # isomorphic, or it's a non-isomorphic type, so # we should throw an exception. msg = "Duplicate definition: %s is already defined" % resource.ref if existing_resource.file and existing_resource.line msg << " in file %s at line %s" % [existing_resource.file, existing_resource.line] end if resource.line or resource.file msg << "; cannot redefine" end raise Puppet::ParseError.new(msg) end end diff --git a/lib/puppet/parser/functions.rb b/lib/puppet/parser/functions.rb index ad58c7040..895b4f083 100644 --- a/lib/puppet/parser/functions.rb +++ b/lib/puppet/parser/functions.rb @@ -1,307 +1,307 @@ # Grr require 'puppet/util/autoload' require 'puppet/parser/scope' module Puppet::Parser module Functions # A module for managing parser functions. Each specified function # becomes an instance method on the Scope class. class << self include Puppet::Util end def self.autoloader unless defined? @autoloader @autoloader = Puppet::Util::Autoload.new(self, "puppet/parser/functions", :wrap => false ) end @autoloader end # Create a new function type. def self.newfunction(name, options = {}, &block) @functions ||= {} name = symbolize(name) if @functions.include? name raise Puppet::DevError, "Function %s already defined" % name end # We want to use a separate, hidden module, because we don't want # people to be able to call them directly. unless defined? FCollection eval("module FCollection; end") end ftype = options[:type] || :statement unless ftype == :statement or ftype == :rvalue raise Puppet::DevError, "Invalid statement type %s" % ftype.inspect end fname = "function_" + name.to_s Puppet::Parser::Scope.send(:define_method, fname, &block) # Someday we'll support specifying an arity, but for now, nope #@functions[name] = {:arity => arity, :type => ftype} @functions[name] = {:type => ftype, :name => fname} if options[:doc] @functions[name][:doc] = options[:doc] end end # Determine if a given name is a function def self.function(name) name = symbolize(name) unless @functions.include? name autoloader.load(name) end if @functions.include? name return @functions[name][:name] else return false end end def self.functiondocs autoloader.loadall ret = "" @functions.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, hash| #ret += "%s\n%s\n" % [name, hash[:type]] ret += "%s\n%s\n" % [name, "-" * name.to_s.length] if hash[:doc] ret += hash[:doc].gsub(/\n\s*/, ' ') else ret += "Undocumented.\n" end ret += "\n\n- **Type**: %s\n\n" % hash[:type] end return ret end def self.functions @functions.keys end # Determine if a given function returns a value or not. def self.rvalue?(name) name = symbolize(name) if @functions.include? name case @functions[name][:type] when :statement: return false when :rvalue: return true end else return false end end # Include the specified classes newfunction(:include, :doc => "Evaluate one or more classes.") do |vals| vals = [vals] unless vals.is_a?(Array) - klasses = configuration.evaluate_classes(vals) + klasses = compile.evaluate_classes(vals) missing = vals.find_all do |klass| ! klasses.include?(klass) end unless missing.empty? # Throw an error if we didn't evaluate all of the classes. str = "Could not find class" if missing.length > 1 str += "es" end str += " " + missing.join(", ") if n = namespaces and ! n.empty? and n != [""] str += " in namespaces %s" % @namespaces.join(", ") end self.fail Puppet::ParseError, str end end # Tag the current scope with each passed name newfunction(:tag, :doc => "Add the specified tags to the containing class or definition. All contained objects will then acquire that tag, also. ") do |vals| self.tag(*vals) end # Test whether a given tag is set. This functions as a big OR -- if any of the # specified tags are unset, we return false. newfunction(:tagged, :type => :rvalue, :doc => "A boolean function that tells you whether the current container is tagged with the specified tags. The tags are ANDed, so that all of the specified tags must be included for the function to return true.") do |vals| - classlist = configuration.classlist + classlist = compile.classlist retval = true vals.each do |val| unless classlist.include?(val) or self.tags.include?(val) retval = false break end end return retval end # Test whether a given class or definition is defined newfunction(:defined, :type => :rvalue, :doc => "Determine whether a given type is defined, either as a native type or a defined type, or whether a class is defined. This is useful for checking whether a class is defined and only including it if it is. This function can also test whether a resource has been defined, using resource references (e.g., ``if defined(File['/tmp/myfile'] { ... }``). This function is unfortunately dependent on the parse order of the configuration when testing whether a resource is defined.") do |vals| result = false vals.each do |val| case val when String: # For some reason, it doesn't want me to return from here. if Puppet::Type.type(val) or finddefine(val) or findclass(val) result = true break end when Puppet::Parser::Resource::Reference: if findresource(val.to_s) result = true break end else raise ArgumentError, "Invalid argument of type %s to 'defined'" % val.class end end result end newfunction(:fail, :doc => "Fail with a parse error.") do |vals| vals = vals.collect { |s| s.to_s }.join(" ") if vals.is_a? Array raise Puppet::ParseError, vals.to_s end # Runs a newfunction to create a function for each of the log levels Puppet::Util::Log.levels.each do |level| newfunction(level, :doc => "Log a message on the server at level #{level.to_s}.") do |vals| send(level, vals.join(" ")) end end newfunction(:template, :type => :rvalue, :doc => "Evaluate a template and return its value. See `the templating docs`_ for more information. Note that if multiple templates are specified, their output is all concatenated and returned as the output of the function. .. _the templating docs: /trac/puppet/wiki/PuppetTemplating ") do |vals| require 'erb' vals.collect do |file| # Use a wrapper, so the template can't get access to the full # Scope object. debug "Retrieving template %s" % file wrapper = Puppet::Parser::TemplateWrapper.new(self, file) begin wrapper.result() rescue => detail raise Puppet::ParseError, "Failed to parse template %s: %s" % [file, detail] end end.join("") end # This is just syntactic sugar for a collection, although it will generally # be a good bit faster. newfunction(:realize, :doc => "Make a virtual object real. This is useful when you want to know the name of the virtual object and don't want to bother with a full collection. It is slightly faster than a collection, and, of course, is a bit shorter. You must pass the object using a reference; e.g.: ``realize User[luke]``." ) do |vals| coll = Puppet::Parser::Collector.new(self, :nomatter, nil, nil, :virtual) vals = [vals] unless vals.is_a?(Array) coll.resources = vals - configuration.add_collection(coll) + compile.add_collection(coll) end newfunction(:search, :doc => "Add another namespace for this class to search. This allows you to create classes with sets of definitions and add those classes to another class's search path.") do |vals| vals.each do |val| add_namespace(val) end end newfunction(:file, :type => :rvalue, :doc => "Return the contents of a file. Multiple files can be passed, and the first file that exists will be read in.") do |vals| ret = nil vals.each do |file| unless file =~ /^#{File::SEPARATOR}/ raise Puppet::ParseError, "Files must be fully qualified" end if FileTest.exists?(file) ret = File.read(file) break end end if ret ret else raise Puppet::ParseError, "Could not find any files from %s" % vals.join(", ") end end newfunction(:generate, :type => :rvalue, :doc => "Calls an external command and returns the results of the command. Any arguments are passed to the external command as arguments. If the generator does not exit with return code of 0, the generator is considered to have failed and a parse error is thrown. Generators can only have file separators, alphanumerics, dashes, and periods in them. This function will attempt to protect you from malicious generator calls (e.g., those with '..' in them), but it can never be entirely safe. No subshell is used to execute generators, so all shell metacharacters are passed directly to the generator.") do |args| unless args[0] =~ /^#{File::SEPARATOR}/ raise Puppet::ParseError, "Generators must be fully qualified" end unless args[0] =~ /^[-#{File::SEPARATOR}\w.]+$/ raise Puppet::ParseError, "Generators can only contain alphanumerics, file separators, and dashes" end if args[0] =~ /\.\./ raise Puppet::ParseError, "Can not use generators with '..' in them." end begin output = Puppet::Util.execute(args) rescue Puppet::ExecutionFailure => detail raise Puppet::ParseError, "Failed to execute generator %s: %s" % [args[0], detail] end output end end end # $Id$ diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index 291f122a7..c0f9d32a7 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -1,95 +1,95 @@ require 'puppet' require 'timeout' require 'puppet/rails' require 'puppet/util/methodhelper' require 'puppet/parser/parser' -require 'puppet/parser/configuration' +require 'puppet/parser/compile' require 'puppet/parser/scope' # The interpreter is a very simple entry-point class that # manages the existence of the parser (e.g., replacing it # when files are reparsed). You can feed it a node and # get the node's configuration back. class Puppet::Parser::Interpreter include Puppet::Util attr_accessor :usenodes attr_accessor :code, :file include Puppet::Util::Errors # Determine the configuration version for a given node's environment. def configuration_version(node) parser(node.environment).version end # evaluate our whole tree def compile(node) return Puppet::Parser::Configuration.new(node, parser(node.environment), :ast_nodes => usenodes?).compile end # create our interpreter def initialize(options = {}) if @code = options[:Code] elsif @file = options[:Manifest] end if options.include?(:UseNodes) @usenodes = options[:UseNodes] else @usenodes = true end # The class won't always be defined during testing. if Puppet[:storeconfigs] if Puppet.features.rails? Puppet::Rails.init else raise Puppet::Error, "Rails is missing; cannot store configurations" end end @parsers = {} end # Should we parse ast nodes? def usenodes? defined?(@usenodes) and @usenodes end private # Create a new parser object and pre-parse the configuration. def create_parser(environment) begin parser = Puppet::Parser::Parser.new(environment) if self.code parser.string = self.code elsif self.file parser.file = self.file end parser.parse return parser rescue => detail if Puppet[:trace] puts detail.backtrace end Puppet.err "Could not parse for environment %s: %s" % [environment, detail] return nil end end # Return the parser for a specific environment. def parser(environment) if ! @parsers[environment] or @parsers[environment].reparse? if tmp = create_parser(environment) @parsers[environment].clear if @parsers[environment] @parsers[environment] = tmp end unless @parsers[environment] raise Puppet::Error, "Could not parse any configurations" end end @parsers[environment] end end diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb index f946c1328..3940e5fc2 100644 --- a/lib/puppet/parser/resource.rb +++ b/lib/puppet/parser/resource.rb @@ -1,418 +1,418 @@ # 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, :rails_id attr_accessor :virtual, :override, :translated attr_reader :exported, :evaluated, :params 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, :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.configuration.delete_resource(self) + scope.compile.delete_resource(self) return klass.evaluate_resource(:scope => scope, :type => self.type, :title => self.title, :arguments => self.to_hash, :virtual => self.virtual, :exported => self.exported ) 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() 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 [:scope, :source].each do |attribute| unless self.send(attribute) raise ArgumentError, "Resources require a %s" % attribute end end 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 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 # 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 tags unless defined? @tags @tags = scope.tags @tags << self.type end @tags 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 @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 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 # 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. next if @params[name] if val = scope.lookupvar(name.to_s, false) unless val == :undefined set_parameter(name, val) end end end end # Add any overrides for this object. def add_overrides - if overrides = scope.configuration.resource_overrides(self) + 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 # 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. unless current = @params[param.name] @params[param.name] = param return end # The parameter is already set. See if they're allowed to override it. if param.source.child_of?(current.source) if param.add # Merge with previous value. param.value = [ current.value, param.value ].flatten end # Replace it, keeping all of its info. @params[param.name] = param else 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 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/scope.rb b/lib/puppet/parser/scope.rb index 9e6739c53..396cce888 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -1,457 +1,457 @@ # 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 attr_accessor :name, :type, :base, :keyword - attr_accessor :top, :translated, :exported, :virtual, :configuration + attr_accessor :top, :translated, :exported, :virtual, :compile # Proxy accessors def host - @configuration.node.name + @compile.node.name end def interpreter - @configuration.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 # Is the type a builtin type? def builtintype?(type) if typeklass = Puppet::Type.type(type) return typeklass else return false end end - # Retrieve a given class scope from the configuration. + # Retrieve a given class scope from the compile. def class_scope(klass) - configuration.class_scope(klass) + compile.class_scope(klass) end # Are we the top scope? def topscope? @level == 1 end def exported? self.exported 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) - configuration.findresource(string, name) + 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 = 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 = {}) - configuration.newscope(self, 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? defined?(@nodescope) and @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 = configuration.parent(self) + @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 def resources @definedtable.values 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(klass) if klass.is_a?(AST::HostClass) unless name = klass.classname raise Puppet::DevError, "Got a %s with no fully qualified name" % klass.class end - @configuration.class_set(name, self) + @compile.class_set(name, self) else raise Puppet::DevError, "Invalid class %s" % klass.inspect end if klass.is_a?(AST::Node) @nodescope = true end nil end # Add a new object to our object table and the global list, and do any necessary # checks. def setresource(resource) - @configuration.store_resource(self, resource) + @compile.store_resource(self, resource) # Mark the resource as virtual or exported, as necessary. if self.exported? resource.exported = true elsif self.virtual? resource.virtual = true end return resource end # 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) - @configuration.store_override(resource) + @compile.store_override(resource) 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 # Add a tag to our current list. These tags will be added to all # of the objects contained in this scope. def tag(*ary) ary.each { |tag| if tag.nil? or tag == "" puts caller Puppet.debug "got told to tag with %s" % tag.inspect next end unless tag =~ /^\w[-\w]*$/ fail Puppet::ParseError, "Invalid tag %s" % tag.inspect 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 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 "parent returned tag %s" % tag.inspect next end unless tmp.include?(tag) tmp << tag end } end return tmp.sort.uniq end # Used mainly for logging def to_s if self.name return "%s[%s]" % [@type, @name] else return self.type.to_s end end # Convert our resource to a TransBucket. def to_trans bucket = Puppet::TransBucket.new([]) case self.type when "": bucket.type = "main" when nil: devfail "A Scope with no type" else bucket.type = @type end if self.name bucket.name = self.name end return bucket end # Undefine a variable; only used for testing. def unsetvar(var) if @symtable.include?(var) @symtable.delete(var) end end def virtual? self.virtual || self.exported? end end # $Id$ diff --git a/lib/puppet/parser/templatewrapper.rb b/lib/puppet/parser/templatewrapper.rb index ecef60e43..f863e31aa 100644 --- a/lib/puppet/parser/templatewrapper.rb +++ b/lib/puppet/parser/templatewrapper.rb @@ -1,54 +1,54 @@ # 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 - @file = Puppet::Module::find_template(file, @scope.configuration.environment) + @file = Puppet::Module::find_template(file, @scope.compile.environment) unless FileTest.exists?(@file) raise Puppet::ParseError, "Could not find template %s" % file end # We'll only ever not have a parser in testing, but, eh. if @scope.parser @scope.parser.watch_file(@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/test/language/configuration.rb b/spec/unit/parser/compile.rb similarity index 100% copy from test/language/configuration.rb copy to spec/unit/parser/compile.rb diff --git a/test/language/configuration.rb b/test/language/compile.rb similarity index 97% rename from test/language/configuration.rb rename to test/language/compile.rb index 409d4ca1d..90cbc292e 100755 --- a/test/language/configuration.rb +++ b/test/language/compile.rb @@ -1,755 +1,755 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'mocha' require 'puppettest' require 'puppettest/parsertesting' -require 'puppet/parser/configuration' +require 'puppet/parser/compile' -# Test our configuration object. -class TestConfiguration < Test::Unit::TestCase +# Test our compile object. +class TestCompile < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting - Config = Puppet::Parser::Configuration + Compile = Puppet::Parser::Compile Scope = Puppet::Parser::Scope Node = Puppet::Network::Handler.handler(:node) SimpleNode = Puppet::Node def mknode(name = "foo") @node = SimpleNode.new(name) end def mkparser # This should mock an interpreter @parser = mock 'parser' end def mkconfig(options = {}) if node = options[:node] options.delete(:node) else node = mknode end - @config = Config.new(node, mkparser, options) + @config = Compile.new(node, mkparser, options) end def test_initialize config = nil assert_nothing_raised("Could not init config with all required options") do - config = Config.new("foo", "parser") + config = Compile.new("foo", "parser") end assert_equal("foo", config.node, "Did not set node correctly") assert_equal("parser", config.parser, "Did not set parser correctly") # We're not testing here whether we call initvars, because it's too difficult to # mock. # Now try it with some options assert_nothing_raised("Could not init config with extra options") do - config = Config.new("foo", "parser", :ast_nodes => false) + config = Compile.new("foo", "parser", :ast_nodes => false) end assert_equal(false, config.ast_nodes?, "Did not set ast_nodes? correctly") end def test_initvars config = mkconfig [:class_scopes, :resource_table, :exported_resources, :resource_overrides].each do |table| assert_instance_of(Hash, config.send(:instance_variable_get, "@#{table}"), "Did not set %s table correctly" % table) end assert_instance_of(Scope, config.topscope, "Did not create a topscope") graph = config.instance_variable_get("@scope_graph") assert_instance_of(GRATR::Digraph, graph, "Did not create scope graph") assert(graph.vertex?(config.topscope), "Did not add top scope as a vertex in the graph") end # Make sure we store and can retrieve references to classes and their scopes. def test_class_set_and_class_scope klass = mock 'ast_class' klass.expects(:classname).returns("myname") config = mkconfig config.expects(:tag).with("myname") assert_nothing_raised("Could not set class") do config.class_set "myname", "myscope" end # First try to retrieve it by name. assert_equal("myscope", config.class_scope("myname"), "Could not retrieve class scope by name") # Then by object assert_equal("myscope", config.class_scope(klass), "Could not retrieve class scope by object") end def test_classlist config = mkconfig config.class_set "", "empty" config.class_set "one", "yep" config.class_set "two", "nope" # Make sure our class list is correct assert_equal(%w{one two}.sort, config.classlist.sort, "Did not get correct class list") end # Make sure collections get added to our internal array def test_add_collection config = mkconfig assert_nothing_raised("Could not add collection") do config.add_collection "nope" end assert_equal(%w{nope}, config.instance_variable_get("@collections"), "Did not add collection") end # Make sure we create a graph of scopes. def test_newscope config = mkconfig graph = config.instance_variable_get("@scope_graph") assert_instance_of(Scope, config.topscope, "Did not create top scope") assert_instance_of(GRATR::Digraph, graph, "Did not create graph") assert(graph.vertex?(config.topscope), "The top scope is not a vertex in the graph") # Now that we've got the top scope, create a new, subscope subscope = nil assert_nothing_raised("Could not create subscope") do subscope = config.newscope(config.topscope) end assert_instance_of(Scope, subscope, "Did not create subscope") assert(graph.edge?(config.topscope, subscope), "An edge between top scope and subscope was not added") # Make sure a scope can find its parent. - assert(config.parent(subscope), "Could not look up parent scope on configuration") - assert_equal(config.topscope.object_id, config.parent(subscope).object_id, "Did not get correct parent scope from configuration") + assert(config.parent(subscope), "Could not look up parent scope on compile") + assert_equal(config.topscope.object_id, config.parent(subscope).object_id, "Did not get correct parent scope from compile") assert_equal(config.topscope.object_id, subscope.parent.object_id, "Scope did not correctly retrieve its parent scope") # Now create another, this time specifying options another = nil assert_nothing_raised("Could not create subscope") do another = config.newscope(subscope, :name => "testing") end assert_equal("testing", another.name, "did not set scope option correctly") assert_instance_of(Scope, another, "Did not create second subscope") assert(graph.edge?(subscope, another), "An edge between parent scope and second subscope was not added") # Make sure it can find its parent. - assert(config.parent(another), "Could not look up parent scope of second subscope on configuration") - assert_equal(subscope.object_id, config.parent(another).object_id, "Did not get correct parent scope of second subscope from configuration") + assert(config.parent(another), "Could not look up parent scope of second subscope on compile") + assert_equal(subscope.object_id, config.parent(another).object_id, "Did not get correct parent scope of second subscope from compile") assert_equal(subscope.object_id, another.parent.object_id, "Second subscope did not correctly retrieve its parent scope") # And make sure both scopes show up in the right order in the search path assert_equal([another.object_id, subscope.object_id, config.topscope.object_id], another.scope_path.collect { |p| p.object_id }, "Did not get correct scope path") end # The heart of the action. def test_compile config = mkconfig [:set_node_parameters, :evaluate_main, :evaluate_ast_node, :evaluate_classes, :evaluate_generators, :fail_on_unevaluated, :finish].each do |method| config.expects(method) end config.expects(:extract).returns(:config) assert_equal(:config, config.compile, "Did not return the results of the extraction") end # Test setting the node's parameters into the top scope. def test_set_node_parameters config = mkconfig @node.parameters = {"a" => "b", "c" => "d"} scope = config.topscope @node.parameters.each do |param, value| scope.expects(:setvar).with(param, value) end assert_nothing_raised("Could not call 'set_node_parameters'") do config.send(:set_node_parameters) end end # Test that we can evaluate the main class, which is the one named "" in namespace # "". def test_evaluate_main config = mkconfig main = mock 'main_class' config.topscope.expects(:source=).with(main) main.expects(:safeevaluate).with(:scope => config.topscope, :nosubscope => true) @parser.expects(:findclass).with("", "").returns(main) assert_nothing_raised("Could not call evaluate_main") do config.send(:evaluate_main) end end # Make sure we either don't look for nodes, or that we find and evaluate the right object. def test_evaluate_ast_node # First try it with ast_nodes disabled config = mkconfig :ast_nodes => false config.expects(:ast_nodes?).returns(false) config.parser.expects(:nodes).never assert_nothing_raised("Could not call evaluate_ast_node when ast nodes are disabled") do config.send(:evaluate_ast_node) end # Now try it with them enabled, but no node found. nodes = mock 'node_hash' config = mkconfig :ast_nodes => true config.expects(:ast_nodes?).returns(true) config.parser.expects(:nodes).returns(nodes).times(4) # Set some names for our test @node.names = %w{a b c} nodes.expects(:[]).with("a").returns(nil) nodes.expects(:[]).with("b").returns(nil) nodes.expects(:[]).with("c").returns(nil) # It should check this last, of course. nodes.expects(:[]).with("default").returns(nil) # And make sure the lack of a node throws an exception assert_raise(Puppet::ParseError, "Did not fail when we couldn't find an ast node") do config.send(:evaluate_ast_node) end # Finally, make sure it works dandily when we have a node nodes = mock 'hash' config = mkconfig :ast_nodes => true config.expects(:ast_nodes?).returns(true) config.parser.expects(:nodes).returns(nodes).times(3) node = mock 'node' node.expects(:safeevaluate).with(:scope => config.topscope) # Set some names for our test @node.names = %w{a b c} nodes.expects(:[]).with("a").returns(nil) nodes.expects(:[]).with("b").returns(nil) nodes.expects(:[]).with("c").returns(node) nodes.expects(:[]).with("default").never # And make sure the lack of a node throws an exception assert_nothing_raised("Failed when a node was found") do config.send(:evaluate_ast_node) end # Lastly, check when we actually find the default. nodes = mock 'hash' config = mkconfig :ast_nodes => true config.expects(:ast_nodes?).returns(true) config.parser.expects(:nodes).returns(nodes).times(4) node = mock 'node' node.expects(:safeevaluate).with(:scope => config.topscope) # Set some names for our test @node.names = %w{a b c} nodes.expects(:[]).with("a").returns(nil) nodes.expects(:[]).with("b").returns(nil) nodes.expects(:[]).with("c").returns(nil) nodes.expects(:[]).with("default").returns(node) # And make sure the lack of a node throws an exception assert_nothing_raised("Failed when a node was found") do config.send(:evaluate_ast_node) end end # Make sure our config object handles tags appropriately. def test_tags config = mkconfig config.send(:tag, "one") assert_equal(%w{one}, config.send(:tags), "Did not add tag") config.send(:tag, "two", "three") assert_equal(%w{one two three}, config.send(:tags), "Did not add new tags") config.send(:tag, "two") assert_equal(%w{one two three}, config.send(:tags), "Allowed duplicate tag") end def test_evaluate_classes config = mkconfig classes = { "one" => mock('class one'), "three" => mock('class three') } classes.each do |name, obj| config.parser.expects(:findclass).with("", name).returns(obj) obj.expects(:safeevaluate).with(:scope => config.topscope) end %w{two four}.each do |name| config.parser.expects(:findclass).with("", name).returns(nil) end config.expects(:tag).with("two") config.expects(:tag).with("four") @node.classes = %w{one two three four} result = nil assert_nothing_raised("could not call evaluate_classes") do result = config.send(:evaluate_classes) end assert_equal(%w{one three}, result, "Did not return the list of evaluated classes") end def test_evaluate_collections config = mkconfig colls = [] # Make sure we return false when there's nothing there. assert(! config.send(:evaluate_collections), "Returned true when there were no collections") # And when the collections fail to evaluate. colls << mock("coll1-false") colls << mock("coll2-false") colls.each { |c| c.expects(:evaluate).returns(false) } config.instance_variable_set("@collections", colls) assert(! config.send(:evaluate_collections), "Returned true when collections both evaluated nothing") # Now have one of the colls evaluate colls.clear colls << mock("coll1-one-true") colls << mock("coll2-one-true") colls[0].expects(:evaluate).returns(true) colls[1].expects(:evaluate).returns(false) assert(config.send(:evaluate_collections), "Did not return true when one collection evaluated true") # And have them both eval true colls.clear colls << mock("coll1-both-true") colls << mock("coll2-both-true") colls[0].expects(:evaluate).returns(true) colls[1].expects(:evaluate).returns(true) assert(config.send(:evaluate_collections), "Did not return true when both collections evaluated true") end def test_unevaluated_resources config = mkconfig resources = {} config.instance_variable_set("@resource_table", resources) # First test it when the table is empty assert_nil(config.send(:unevaluated_resources), "Somehow found unevaluated resources in an empty table") # Then add a builtin resources resources["one"] = mock("builtin only") resources["one"].expects(:builtin?).returns(true) assert_nil(config.send(:unevaluated_resources), "Considered a builtin resource unevaluated") # And do both builtin and non-builtin but already evaluated resources.clear resources["one"] = mock("builtin (with eval)") resources["one"].expects(:builtin?).returns(true) resources["two"] = mock("evaled (with builtin)") resources["two"].expects(:builtin?).returns(false) resources["two"].expects(:evaluated?).returns(true) assert_nil(config.send(:unevaluated_resources), "Considered either a builtin or evaluated resource unevaluated") # Now a single unevaluated resource. resources.clear resources["one"] = mock("unevaluated") resources["one"].expects(:builtin?).returns(false) resources["one"].expects(:evaluated?).returns(false) assert_equal([resources["one"]], config.send(:unevaluated_resources), "Did not find unevaluated resource") # With two uneval'ed resources, and an eval'ed one thrown in resources.clear resources["one"] = mock("unevaluated one") resources["one"].expects(:builtin?).returns(false) resources["one"].expects(:evaluated?).returns(false) resources["two"] = mock("unevaluated two") resources["two"].expects(:builtin?).returns(false) resources["two"].expects(:evaluated?).returns(false) resources["three"] = mock("evaluated") resources["three"].expects(:builtin?).returns(false) resources["three"].expects(:evaluated?).returns(true) result = config.send(:unevaluated_resources) %w{one two}.each do |name| assert(result.include?(resources[name]), "Did not find %s in the unevaluated list" % name) end end def test_evaluate_definitions # First try the case where there's nothing to return config = mkconfig config.expects(:unevaluated_resources).returns(nil) assert_nothing_raised("Could not test for unevaluated resources") do assert(! config.send(:evaluate_definitions), "evaluate_definitions returned true when no resources were evaluated") end # Now try it with resources left to evaluate resources = [] res1 = mock("resource1") res1.expects(:evaluate) res2 = mock("resource2") res2.expects(:evaluate) resources << res1 << res2 config = mkconfig config.expects(:unevaluated_resources).returns(resources) assert_nothing_raised("Could not test for unevaluated resources") do assert(config.send(:evaluate_definitions), "evaluate_definitions returned false when resources were evaluated") end end def test_evaluate_generators # First try the case where we have nothing to do config = mkconfig config.expects(:evaluate_definitions).returns(false) config.expects(:evaluate_collections).returns(false) assert_nothing_raised("Could not call :eval_iterate") do config.send(:evaluate_generators) end # FIXME I could not get this test to work, but the code is short # enough that I'm ok with it. # It's important that collections are evaluated before definitions, # so make sure that's the case by verifying that collections get tested # twice but definitions only once. #config = mkconfig #config.expects(:evaluate_collections).returns(true).returns(false) #config.expects(:evaluate_definitions).returns(false) #config.send(:eval_iterate) end def test_store config = mkconfig Puppet.features.expects(:rails?).returns(true) Puppet::Rails.expects(:connect) node = mock 'node' resource_table = mock 'resources' resource_table.expects(:values).returns(:resources) config.instance_variable_set("@node", node) config.instance_variable_set("@resource_table", resource_table) config.expects(:store_to_active_record).with(node, :resources) config.send(:store) end def test_store_to_active_record config = mkconfig node = mock 'node' node.expects(:name).returns("myname") Puppet::Rails::Host.stubs(:transaction).yields Puppet::Rails::Host.expects(:store).with(node, :resources) config.send(:store_to_active_record, node, :resources) end # Make sure that 'finish' gets called on all of our resources. def test_finish config = mkconfig table = config.instance_variable_get("@resource_table") # Add a resource that does respond to :finish yep = mock("finisher") yep.expects(:respond_to?).with(:finish).returns(true) yep.expects(:finish) table["yep"] = yep # And one that does not dnf = mock("dnf") dnf.expects(:respond_to?).with(:finish).returns(false) table["dnf"] = dnf config.send(:finish) end def test_extract config = mkconfig config.expects(:extraction_format).returns(:whatever) config.expects(:extract_to_whatever).returns(:result) assert_equal(:result, config.send(:extract), "Did not return extraction result as the method result") end # We want to make sure that the scope and resource graphs translate correctly def test_extract_to_transportable_simple # Start with a really simple graph -- one scope, one resource. config = mkconfig resources = config.instance_variable_get("@resource_graph") scopes = config.instance_variable_get("@scope_graph") # Get rid of the topscope scopes.vertices.each { |v| scopes.remove_vertex!(v) } bucket = [] scope = mock("scope") bucket.expects(:copy_type_and_name).with(scope) scope.expects(:to_trans).returns(bucket) scopes.add_vertex! scope # The topscope is the key to picking out the top of the graph. config.instance_variable_set("@topscope", scope) resource = mock "resource" resource.expects(:to_trans).returns(:resource) resources.add_edge! scope, resource result = nil - assert_nothing_raised("Could not extract transportable configuration") do + assert_nothing_raised("Could not extract transportable compile") do result = config.send :extract_to_transportable end - assert_equal([:resource], result, "Did not translate simple configuration correctly") + assert_equal([:resource], result, "Did not translate simple compile correctly") end def test_extract_to_transportable_complex # Now try it with a more complicated graph -- a three tier graph, each tier # having a scope and a resource. config = mkconfig resources = config.instance_variable_get("@resource_graph") scopes = config.instance_variable_get("@scope_graph") # Get rid of the topscope scopes.vertices.each { |v| scopes.remove_vertex!(v) } fakebucket = Class.new(Array) do attr_accessor :name def initialize(n) @name = n end end # Create our scopes. top = mock("top") topbucket = fakebucket.new "top" topbucket.expects(:copy_type_and_name).with(top) top.stubs(:copy_type_and_name) top.expects(:to_trans).returns(topbucket) # The topscope is the key to picking out the top of the graph. config.instance_variable_set("@topscope", top) middle = mock("middle") middle.expects(:to_trans).returns(fakebucket.new("middle")) scopes.add_edge! top, middle bottom = mock("bottom") bottom.expects(:to_trans).returns(fakebucket.new("bottom")) scopes.add_edge! middle, bottom topres = mock "topres" topres.expects(:to_trans).returns(:topres) resources.add_edge! top, topres midres = mock "midres" midres.expects(:to_trans).returns(:midres) resources.add_edge! middle, midres botres = mock "botres" botres.expects(:to_trans).returns(:botres) resources.add_edge! bottom, botres result = nil - assert_nothing_raised("Could not extract transportable configuration") do + assert_nothing_raised("Could not extract transportable compile") do result = config.send :extract_to_transportable end - assert_equal([[[:botres], :midres], :topres], result, "Did not translate medium configuration correctly") + assert_equal([[[:botres], :midres], :topres], result, "Did not translate medium compile correctly") end def test_verify_uniqueness config = mkconfig resources = config.instance_variable_get("@resource_table") resource = mock("noconflict") resource.expects(:ref).returns("File[yay]") assert_nothing_raised("Raised an exception when there should have been no conflict") do config.send(:verify_uniqueness, resource) end # Now try the case where our type is isomorphic resources["thing"] = true isoconflict = mock("isoconflict") isoconflict.expects(:ref).returns("thing") isoconflict.expects(:type).returns("testtype") faketype = mock("faketype") faketype.expects(:isomorphic?).returns(false) faketype.expects(:name).returns("whatever") Puppet::Type.expects(:type).with("testtype").returns(faketype) assert_nothing_raised("Raised an exception when was a conflict in non-isomorphic types") do config.send(:verify_uniqueness, isoconflict) end # Now test for when we actually have an exception initial = mock("initial") resources["thing"] = initial initial.expects(:file).returns(false) conflict = mock("conflict") conflict.expects(:ref).returns("thing").times(2) conflict.expects(:type).returns("conflict") conflict.expects(:file).returns(false) conflict.expects(:line).returns(false) faketype = mock("faketype") faketype.expects(:isomorphic?).returns(true) Puppet::Type.expects(:type).with("conflict").returns(faketype) assert_raise(Puppet::ParseError, "Did not fail when two isomorphic resources conflicted") do config.send(:verify_uniqueness, conflict) end end def test_store_resource # Run once when there's no conflict config = mkconfig table = config.instance_variable_get("@resource_table") resource = mock("resource") resource.expects(:ref).returns("yay") config.expects(:verify_uniqueness).with(resource) scope = mock("scope") graph = config.instance_variable_get("@resource_graph") graph.expects(:add_edge!).with(scope, resource) assert_nothing_raised("Could not store resource") do config.store_resource(scope, resource) end assert_equal(resource, table["yay"], "Did not store resource in table") # Now for conflicts config = mkconfig table = config.instance_variable_get("@resource_table") resource = mock("resource") config.expects(:verify_uniqueness).with(resource).raises(ArgumentError) assert_raise(ArgumentError, "Did not raise uniqueness exception") do config.store_resource(scope, resource) end assert(table.empty?, "Conflicting resource was stored in table") end def test_fail_on_unevaluated config = mkconfig config.expects(:fail_on_unevaluated_overrides) config.expects(:fail_on_unevaluated_resource_collections) config.send :fail_on_unevaluated end def test_store_override # First test the case when the resource is not present. config = mkconfig overrides = config.instance_variable_get("@resource_overrides") override = Object.new override.expects(:ref).returns(:myref).times(2) override.expects(:override=).with(true) assert_nothing_raised("Could not call store_override") do config.store_override(override) end assert_instance_of(Array, overrides[:myref], "Overrides table is not a hash of arrays") assert_equal(override, overrides[:myref][0], "Did not store override in appropriately named array") # And when the resource already exists. resource = mock 'resource' resources = config.instance_variable_get("@resource_table") resources[:resref] = resource override = mock 'override' resource.expects(:merge).with(override) override.expects(:override=).with(true) override.expects(:ref).returns(:resref) assert_nothing_raised("Could not call store_override when the resource already exists.") do config.store_override(override) end end def test_resource_overrides config = mkconfig overrides = config.instance_variable_get("@resource_overrides") overrides[:test] = :yay resource = mock 'resource' resource.expects(:ref).returns(:test) assert_equal(:yay, config.resource_overrides(resource), "Did not return overrides from table") end def test_fail_on_unevaluated_resource_collections config = mkconfig collections = config.instance_variable_get("@collections") # Make sure we're fine when the list is empty assert_nothing_raised("Failed when no collections were present") do config.send :fail_on_unevaluated_resource_collections end # And that we're fine when we've got collections but with no resources collections << mock('coll') collections[0].expects(:resources).returns(nil) assert_nothing_raised("Failed when no resource collections were present") do config.send :fail_on_unevaluated_resource_collections end # But that we do fail when we've got resource collections left. collections.clear # return both an array and a string, because that's tested internally collections << mock('coll returns one') collections[0].expects(:resources).returns(:something) collections << mock('coll returns many') collections[1].expects(:resources).returns([:one, :two]) assert_raise(Puppet::ParseError, "Did not fail on unevaluated resource collections") do config.send :fail_on_unevaluated_resource_collections end end def test_fail_on_unevaluated_overrides config = mkconfig overrides = config.instance_variable_get("@resource_overrides") # Make sure we're fine when the list is empty assert_nothing_raised("Failed when no collections were present") do config.send :fail_on_unevaluated_overrides end # But that we fail if there are any overrides left in the table. overrides[:yay] = [] overrides[:foo] = [] overrides[:bar] = [mock("override")] overrides[:bar][0].expects(:ref).returns("yay") assert_raise(Puppet::ParseError, "Failed to fail when overrides remain") do config.send :fail_on_unevaluated_overrides end end def test_find_resource config = mkconfig resources = config.instance_variable_get("@resource_table") assert_nothing_raised("Could not call findresource when the resource table was empty") do assert_nil(config.findresource("yay", "foo"), "Returned a non-existent resource") assert_nil(config.findresource("yay[foo]"), "Returned a non-existent resource") end resources["Foo[bar]"] = :yay assert_nothing_raised("Could not call findresource when the resource table was not empty") do assert_equal(:yay, config.findresource("foo", "bar"), "Returned a non-existent resource") assert_equal(:yay, config.findresource("Foo[bar]"), "Returned a non-existent resource") end end # #620 - Nodes and classes should conflict, else classes don't get evaluated def test_nodes_and_classes_name_conflict # Test node then class config = mkconfig node = stub :nodescope? => true klass = stub :nodescope? => false config.class_set("one", node) assert_raise(Puppet::ParseError, "Did not fail when replacing node with class") do config.class_set("one", klass) end # and class then node config = mkconfig node = stub :nodescope? => true klass = stub :nodescope? => false config.class_set("two", klass) assert_raise(Puppet::ParseError, "Did not fail when replacing node with class") do config.class_set("two", node) end end end diff --git a/test/lib/puppettest/parsertesting.rb b/test/lib/puppettest/parsertesting.rb index 326c25756..34aeca00a 100644 --- a/test/lib/puppettest/parsertesting.rb +++ b/test/lib/puppettest/parsertesting.rb @@ -1,408 +1,408 @@ require 'puppettest' require 'puppet/rails' module PuppetTest::ParserTesting include PuppetTest AST = Puppet::Parser::AST - Config = Puppet::Parser::Configuration + Compile = Puppet::Parser::Compile # A fake class that we can use for testing evaluation. class FakeAST attr_writer :evaluate def evaluated? defined? @evaluated and @evaluated end def evaluate(*args) @evaluated = true return @evaluate end def initialize(val = nil) if val @evaluate = val end end def reset @evaluated = nil end def safeevaluate(*args) evaluate() end end def astarray(*args) AST::ASTArray.new( :children => args ) end def mkconfig(parser = nil) require 'puppet/network/handler/node' parser ||= mkparser node = mknode - return Config.new(node, parser) + return Compile.new(node, parser) end def mknode(name = nil) require 'puppet/node' name ||= "nodename" Puppet::Network::Handler.handler(:node) Puppet::Node.new(name) end def mkinterp(args = {}) args[:Code] ||= "" unless args.include?(:Manifest) args[:Local] ||= true Puppet::Parser::Interpreter.new(args) end def mkparser Puppet::Parser::Parser.new() end def mkscope(hash = {}) hash[:parser] ||= mkparser config ||= mkconfig(hash[:parser]) config.topscope.source = (hash[:parser].findclass("", "") || hash[:parser].newclass("")) unless config.topscope.source raise "Could not find source for scope" end config.topscope end def classobj(name, hash = {}) hash[:file] ||= __FILE__ hash[:line] ||= __LINE__ hash[:type] ||= name AST::HostClass.new(hash) end def tagobj(*names) args = {} newnames = names.collect do |name| if name.is_a? AST name else nameobj(name) end end args[:type] = astarray(*newnames) assert_nothing_raised("Could not create tag %s" % names.inspect) { return AST::Tag.new(args) } end 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 virt_resourcedef(*args) res = resourcedef(*args) res.virtual = true res end def resourceoverride(type, title, params) assert_nothing_raised("Could not create %s %s" % [type, name]) { 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 resourcedef("file", path, hash) } end def nameobj(name) assert_nothing_raised("Could not create name %s" % name) { return AST::Name.new( :file => tempfile(), :line => rand(100), :value => name ) } end def typeobj(name) assert_nothing_raised("Could not create type %s" % name) { return AST::Type.new( :file => tempfile(), :line => rand(100), :value => name ) } end def nodedef(name) assert_nothing_raised("Could not create node %s" % name) { return AST::NodeDef.new( :file => tempfile(), :line => rand(100), :names => nameobj(name), :code => AST::ASTArray.new( :children => [ varobj("%svar" % name, "%svalue" % name), fileobj("/%s" % name) ] ) ) } end def resourceinst(hash) assert_nothing_raised("Could not create resource instance") { params = hash.collect { |param, value| resourceparam(param, value) } return AST::ResourceInst.new( :file => tempfile(), :line => rand(100), :children => params ) } end 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::ResourceParam.new( :file => tempfile(), :line => rand(100), :param => param, :value => value ) } end def stringobj(value) AST::String.new( :file => tempfile(), :line => rand(100), :value => value ) end def varobj(name, value) unless value.is_a? AST value = stringobj(value) end assert_nothing_raised("Could not create %s code" % name) { return AST::VarDef.new( :file => tempfile(), :line => rand(100), :name => nameobj(name), :value => value ) } end def varref(name) assert_nothing_raised("Could not create %s variable" % name) { return AST::Variable.new( :file => __FILE__, :line => __LINE__, :value => name ) } end def argobj(name, value) assert_nothing_raised("Could not create %s compargument" % name) { return AST::CompArgument.new( :children => [nameobj(name), stringobj(value)] ) } end def defaultobj(type, params) pary = [] params.each { |p,v| pary << AST::ResourceParam.new( :file => __FILE__, :line => __LINE__, :param => p, :value => stringobj(v) ) } past = AST::ASTArray.new( :file => __FILE__, :line => __LINE__, :children => pary ) assert_nothing_raised("Could not create defaults for %s" % type) { return AST::ResourceDefaults.new( :file => __FILE__, :line => __LINE__, :type => type, :params => past ) } end def taggedobj(name, ftype = :statement) functionobj("tagged", name, ftype) end def functionobj(function, name, ftype = :statement) func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => function, :ftype => ftype, :arguments => AST::ASTArray.new( :children => [nameobj(name)] ) ) end return func end # This assumes no nodes def assert_creates(manifest, *files) interp = nil assert_nothing_raised { interp = Puppet::Parser::Interpreter.new( :Manifest => manifest, :UseNodes => false ) } config = nil assert_nothing_raised { config = interp.compile(mknode) } comp = nil assert_nothing_raised { comp = config.to_type } assert_apply(comp) files.each do |file| assert(FileTest.exists?(file), "Did not create %s" % file) end end def mk_transobject(file = "/etc/passwd") obj = nil assert_nothing_raised { obj = Puppet::TransObject.new("file", file) obj["owner"] = "root" obj["mode"] = "644" } return obj end def mk_transbucket(*resources) bucket = nil assert_nothing_raised { bucket = Puppet::TransBucket.new bucket.name = "yayname" bucket.type = "yaytype" } resources.each { |o| bucket << o } return bucket end # Make a tree of resources, yielding if desired def mk_transtree(depth = 4, width = 2) top = nil assert_nothing_raised { top = Puppet::TransBucket.new top.name = "top" top.type = "bucket" } bucket = top file = tempfile() depth.times do |i| resources = [] width.times do |j| path = tempfile + i.to_s obj = Puppet::TransObject.new("file", path) obj["owner"] = "root" obj["mode"] = "644" # Yield, if they want if block_given? yield(obj, i, j) end resources << obj end newbucket = mk_transbucket(*resources) bucket.push newbucket bucket = newbucket end return top end # 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 ) } trans = nil scope = nil assert_nothing_raised { scope = Puppet::Parser::Scope.new() trans = scope.evaluate(:ast => top) } return trans end end # $Id$