diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index 5fe50d144..5144c4970 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -1,800 +1,800 @@ # The interepreter's job is to convert from a parsed file to the configuration # for a given client. It really doesn't do any work on its own, it just collects # and calls out to other objects. require 'puppet' require 'timeout' #require 'puppet/rails' require 'puppet/parser/parser' require 'puppet/parser/scope' class Puppet::Parser::Interpreter include Puppet::Util Puppet.setdefaults("ldap", :ldapnodes => [false, "Whether to search for node configurations in LDAP."], :ldapssl => [false, "Whether SSL should be used when searching for nodes. Defaults to false because SSL usually requires certificates to be set up on the client side."], :ldaptls => [false, "Whether TLS should be used when searching for nodes. Defaults to false because TLS usually requires certificates to be set up on the client side."], :ldapserver => ["ldap", "The LDAP server. Only used if ``ldapnodes`` is enabled."], :ldapport => [389, "The LDAP port. Only used if ``ldapnodes`` is enabled."], :ldapstring => ["(&(objectclass=puppetClient)(cn=%s))", "The search string used to find an LDAP node."], :ldapattrs => ["puppetclass", "The LDAP attributes to use to define Puppet classes. Values should be comma-separated."], :ldapparentattr => ["parentnode", "The attribute to use to define the parent node."], :ldapuser => ["", "The user to use to connect to LDAP. Must be specified as a full DN."], :ldappassword => ["", "The password to use to connect to LDAP."], :ldapbase => ["", "The search base for LDAP searches. It's impossible to provide a meaningful default here, although the LDAP libraries might have one already set. Generally, it should be the 'ou=Hosts' branch under your main directory."] ) Puppet.setdefaults(:puppetmaster, :storeconfigs => [false, "Whether to store each client's configuration. This requires ActiveRecord from Ruby on Rails."] ) attr_accessor :usenodes class << self attr_writer :ldap end # just shorten the constant path a bit, using what amounts to an alias AST = Puppet::Parser::AST include Puppet::Util::Errors # Create an ldap connection. This is a class method so others can call # it and use the same variables and such. def self.ldap unless defined? @ldap and @ldap if Puppet[:ldapssl] @ldap = LDAP::SSLConn.new(Puppet[:ldapserver], Puppet[:ldapport]) elsif Puppet[:ldaptls] @ldap = LDAP::SSLConn.new( Puppet[:ldapserver], Puppet[:ldapport], true ) else @ldap = LDAP::Conn.new(Puppet[:ldapserver], Puppet[:ldapport]) end @ldap.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) @ldap.set_option(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_ON) @ldap.simple_bind(Puppet[:ldapuser], Puppet[:ldappassword]) end return @ldap end def clear initparsevars end # Iteratively evaluate all of the objects. This finds all fo the # objects that represent definitions and evaluates the definitions appropriately. # It also adds defaults and overrides as appropriate. def evaliterate(scope) count = 0 begin timeout 300 do while ary = scope.unevaluated ary.each do |resource| resource.evaluate end end end rescue Timeout::Error raise Puppet::DevError, "Got a timeout trying to evaluate all definitions" end end # Evaluate a specific node. def evalnode(client, scope, facts) return unless self.usenodes unless client raise Puppet::Error, "Cannot evaluate nodes with a nil client" end names = [client] # Make sure both the fqdn and the short name of the # host can be used in the manifest if client =~ /\./ names << client.sub(/\..+/,'') else names << "#{client}.#{facts['domain']}" end if names.empty? raise Puppet::Error, "Cannot evaluate nodes with a nil client" end # Look up our node object. if nodeclass = nodesearch(*names) nodeclass.safeevaluate :scope => scope else raise Puppet::Error, "Could not find %s with names %s" % [client, names.join(", ")] end end # Evaluate all of the code we can find that's related to our client. def evaluate(client, facts) scope = Puppet::Parser::Scope.new(:interp => self) # no parent scope scope.name = "top" scope.type = "main" - scope.host = client + scope.host = client || facts["hostname"] || Facter.value(:hostname) classes = @classes.dup # Okay, first things first. Set our facts. scope.setfacts(facts) # Everyone will always evaluate the top-level class, if there is one. if klass = findclass("", "") # Set the source, so objects can tell where they were defined. scope.source = klass klass.safeevaluate :scope => scope, :nosubscope => true end # Next evaluate the node evalnode(client, scope, facts) # If we were passed any classes, evaluate those. if classes classes.each do |klass| if klassobj = findclass("", klass) klassobj.safeevaluate :scope => scope end end end # That was the first pass evaluation. Now iteratively evaluate # until we've gotten rid of all of everything or thrown an error. evaliterate(scope) # Now make sure we fail if there's anything left to do failonleftovers(scope) # Now perform the collections exceptwrap do scope.collections.each do |coll| coll.evaluate end end # Now finish everything. This recursively calls finish on the # contained scopes and resources. scope.finish # Store everything. We need to do this before translation, because # it operates on resources, not transobjects. if Puppet[:storeconfigs] args = { :resources => scope.resources, - :name => client, + :name => scope.host, :facts => facts } unless scope.classlist.empty? args[:classes] = scope.classlist end storeconfigs(args) end # Now, finally, convert our scope tree + resources into a tree of # buckets and objects. objects = scope.translate # Add the class list unless scope.classlist.empty? objects.classes = scope.classlist end return objects end # Fail if there any overrides left to perform. def failonleftovers(scope) overrides = scope.overrides if overrides.empty? return nil else fail Puppet::ParseError, "Could not find object(s) %s" % overrides.collect { |o| o.ref }.join(", ") end end # Find a class definition, relative to the current namespace. def findclass(namespace, name) fqfind namespace, name, @classtable end # Find a component definition, relative to the current namespace. def finddefine(namespace, name) fqfind namespace, name, @definetable end # The recursive method used to actually look these objects up. def fqfind(namespace, name, table) if name =~ /^::/ or namespace == "" return table[name.sub(/^::/, '')] end ary = namespace.split("::") while ary.length > 0 newname = (ary + [name]).join("::").sub(/^::/, '') if obj = table[newname] return obj end # Delete the second to last object, which reduces our namespace by one. ary.pop end # If we've gotten to this point without finding it, see if the name # exists at the top namespace if obj = table[name] return obj end return nil end # Create a new node, just from a list of names, classes, and an optional parent. def gennode(name, hash) facts = hash[:facts] classes = hash[:classes] parent = hash[:parentnode] arghash = { :name => name, :interp => self, :fqname => name } if (classes.is_a?(Array) and classes.empty?) or classes.nil? arghash[:code] = AST::ASTArray.new(:children => []) else classes = [classes] unless classes.is_a?(Array) classcode = @parser.ast(AST::ASTArray, :children => classes.collect do |klass| @parser.ast(AST::FlatString, :value => klass) end) # Now generate a function call. code = @parser.ast(AST::Function, :name => "include", :arguments => classcode, :ftype => :statement ) arghash[:code] = code end if parent arghash[:parentclass] = parent end # Create the node return @parser.ast(AST::Node, arghash) end # create our interpreter def initialize(hash) if @code = hash[:Code] @file = nil # to avoid warnings elsif ! @file = hash[:Manifest] devfail "You must provide code or a manifest" end if hash.include?(:UseNodes) @usenodes = hash[:UseNodes] else @usenodes = true end # By default, we only search for parsed nodes. @nodesources = [] if Puppet[:ldapnodes] # Nodes in the file override nodes in ldap. @nodesources << :ldap end if hash[:NodeSources] unless hash[:NodeSources].is_a?(Array) hash[:NodeSources] = [hash[:NodeSources]] end hash[:NodeSources].each do |src| if respond_to? "nodesearch_#{src.to_s}" @nodesources << src.to_s.intern else Puppet.warning "Node source '#{src}' not supported" end end end unless @nodesources.include?(:code) @nodesources << :code end @setup = false initparsevars() # Set it to either the value or nil. This is currently only used # by the cfengine module. @classes = hash[:Classes] || [] @local = hash[:Local] || false if hash.include?(:ForkSave) @forksave = hash[:ForkSave] else # This is just too dangerous right now. Sorry, it's going # to have to be slow. @forksave = false end # The class won't always be defined during testing. if Puppet[:storeconfigs] and Puppet.features.rails? require 'puppet/rails' Puppet::Rails.init end @files = [] # Create our parser object parsefiles end # Initialize or reset the variables related to parsing. def initparsevars @classtable = {} @namespace = "main" @nodetable = {} @definetable = {} end # Find the ldap node and extra the info, returning just # the critical data. def ldapsearch(node) unless defined? @ldap and @ldap setup_ldap() unless @ldap Puppet.info "Skipping ldap source; no ldap connection" return nil, [] end end if node =~ /\./ node = node.sub(/\..+/, '') end filter = Puppet[:ldapstring] attrs = Puppet[:ldapattrs].split("\s*,\s*") sattrs = attrs.dup pattr = nil if pattr = Puppet[:ldapparentattr] if pattr == "" pattr = nil else sattrs << pattr end end if filter =~ /%s/ filter = filter.gsub(/%s/, node) end parent = nil classes = [] found = false count = 0 begin # We're always doing a sub here; oh well. @ldap.search(Puppet[:ldapbase], 2, filter, sattrs) do |entry| found = true if pattr if values = entry.vals(pattr) if values.length > 1 raise Puppet::Error, "Node %s has more than one parent: %s" % [node, values.inspect] end unless values.empty? parent = values.shift end end end attrs.each { |attr| if values = entry.vals(attr) values.each do |v| classes << v end end } end rescue => detail if count == 0 # Try reconnecting to ldap @ldap = nil setup_ldap() retry else raise Puppet::Error, "LDAP Search failed: %s" % detail end end classes.flatten! if classes.empty? classes = nil end return parent, classes end # Split an fq name into a namespace and name def namesplit(fullname) ary = fullname.split("::") n = ary.pop || "" ns = ary.join("::") return ns, n end # Create a new class, or merge with an existing class. def newclass(fqname, options = {}) if @definetable.include?(fqname) raise Puppet::ParseError, "Cannot redefine class %s as a definition" % fqname end code = options[:code] parent = options[:parent] # If the class is already defined, then add code to it. if other = @classtable[fqname] # Make sure the parents match if parent and other.parentclass and (parent != other.parentclass) @parser.error("Class %s is already defined at %s:%s; cannot redefine" % [fqname, other.file, other.line]) end # This might be dangerous... if parent and ! other.parentclass other.parentclass = parent end # This might just be an empty, stub class. if code tmp = fqname if tmp == "" tmp = "main" end Puppet.debug @parser.addcontext("Adding code to %s" % tmp) # Else, add our code to it. if other.code and code other.code.children += code.children else other.code ||= code end end else # Define it anew. ns, name = namesplit(fqname) # Note we're doing something somewhat weird here -- we're setting # the class's namespace to its fully qualified name. This means # anything inside that class starts looking in that namespace first. args = {:type => name, :namespace => fqname, :fqname => fqname, :interp => self} args[:code] = code if code args[:parentclass] = parent if parent @classtable[fqname] = @parser.ast AST::HostClass, args end return @classtable[fqname] end # Create a new definition. def newdefine(fqname, options = {}) if @classtable.include?(fqname) raise Puppet::ParseError, "Cannot redefine class %s as a definition" % fqname end # Make sure our definition doesn't already exist if other = @definetable[fqname] @parser.error("%s is already defined at %s:%s; cannot redefine" % [fqname, other.file, other.line]) end ns, name = namesplit(fqname) args = { :type => name, :namespace => ns, :arguments => options[:arguments], :code => options[:code], :fqname => fqname } [:code, :arguments].each do |param| args[param] = options[param] if options[param] end @definetable[fqname] = @parser.ast AST::Component, args end # Create a new node. Nodes are special, because they're stored in a global # table, not according to namespaces. def newnode(names, options = {}) names = [names] unless names.instance_of?(Array) names.collect do |name| if other = @nodetable[name] @parser.error("Node %s is already defined at %s:%s; cannot redefine" % [other.name, other.file, other.line]) end name = name.to_s if name.is_a?(Symbol) args = { :name => name, } if options[:code] args[:code] = options[:code] end if options[:parent] args[:parentclass] = options[:parent] end @nodetable[name] = @parser.ast(AST::Node, args) @nodetable[name].fqname = name @nodetable[name] @nodetable[name].interp = self @nodetable[name] end end # Add a new file to be checked when we're checking to see if we should be # reparsed. def newfile(*files) files.each do |file| unless file.is_a? Puppet::LoadedFile file = Puppet::LoadedFile.new(file) end @files << file end end # Search for our node in the various locations. def nodesearch(*nodes) # At this point, stop at the first source that defines # the node @nodesources.each do |source| method = "nodesearch_%s" % source if self.respond_to? method # Do an inverse sort on the length, so the longest match always # wins nodes.sort { |a,b| b.length <=> a.length }.each do |node| node = node.to_s if node.is_a?(Symbol) if obj = self.send(method, node) nsource = obj.file || source Puppet.info "Found %s in %s" % [node, nsource] return obj end end end end # If they made it this far, we haven't found anything, so look for a # default node. unless nodes.include?("default") if defobj = self.nodesearch("default") Puppet.notice "Using default node for %s" % [nodes[0]] return defobj end end return nil end # See if our node was defined in the code. def nodesearch_code(name) @nodetable[name] end # Look for our node in ldap. def nodesearch_ldap(node) parent, classes = ldapsearch(node) if parent or classes args = {} args[:classes] = classes if classes args[:parentnode] = parent if parent return gennode(node, args) else return nil end end def parsedate parsefiles() @parsedate end # evaluate our whole tree def run(client, facts) # We have to leave this for after initialization because there # seems to be a problem keeping ldap open after a fork. unless @setup @nodesources.each { |source| method = "setup_%s" % source.to_s if respond_to? method exceptwrap :type => Puppet::Error, :message => "Could not set up node source %s" % source do self.send(method) end end } end parsefiles() # Evaluate all of the appropriate code. objects = evaluate(client, facts) # And return it all. return objects end # Connect to the LDAP Server def setup_ldap self.class.ldap = nil begin require 'ldap' rescue LoadError Puppet.notice( "Could not set up LDAP Connection: Missing ruby/ldap libraries" ) @ldap = nil return end begin @ldap = self.class.ldap() rescue => detail raise Puppet::Error, "Could not connect to LDAP: %s" % detail end end def scope return @scope end private # Check whether any of our files have changed. def checkfiles if @files.find { |f| f.changed? } @parsedate = Time.now.to_i end end # Parse the files, generating our parse tree. This automatically # reparses only if files are updated, so it's safe to call multiple # times. def parsefiles # First check whether there are updates to any non-puppet files # like templates. If we need to reparse, this will get quashed, # but it needs to be done first in case there's no reparse # but there are other file changes. checkfiles() # Check if the parser should reparse. if @file if defined? @parser if stamp = @parser.reparse? Puppet.notice "Reloading files" else return false end end unless FileTest.exists?(@file) # If we've already parsed, then we're ok. if findclass("", "") return else raise Puppet::Error, "Manifest %s must exist" % @file end end end # Reset our parse tables. clear() # Create a new parser, just to keep things fresh. @parser = Puppet::Parser::Parser.new(self) if @code @parser.string = @code else @parser.file = @file # Mark when we parsed, so we can check freshness @parsedate = File.stat(@file).ctime.to_i end # Parsing stores all classes and defines and such in their # various tables, so we don't worry about the return. if @local @parser.parse else benchmark(:info, "Parsed manifest") do @parser.parse end end @parsedate = Time.now.to_i end # Store the configs into the database. def storeconfigs(hash) unless defined? ActiveRecord require 'puppet/rails' unless defined? ActiveRecord raise LoadError, "storeconfigs is enabled but rails is unavailable" end end Puppet::Rails.init # Fork the storage, since we don't need the client waiting # on that. How do I avoid this duplication? if @forksave fork { # We store all of the objects, even the collectable ones benchmark(:info, "Stored configuration for #{hash[:name]}") do # Try to batch things a bit, by putting them into # a transaction Puppet::Rails::Host.transaction do Puppet::Rails::Host.store(hash) end end } else begin # We store all of the objects, even the collectable ones benchmark(:info, "Stored configuration for #{hash[:name]}") do Puppet::Rails::Host.transaction do Puppet::Rails::Host.store(hash) end end rescue => detail if Puppet[:trace] puts detail.backtrace end Puppet.err "Could not store configs: %s" % detail.to_s end end # Now that we've stored everything, we need to strip out # the collectable objects so that they are not sent on # to the host #hash[:objects].collectstrip! end end # $Id$ diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb index 857b5fa39..05e0af4c1 100644 --- a/lib/puppet/parser/resource.rb +++ b/lib/puppet/parser/resource.rb @@ -1,340 +1,330 @@ # 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, :params, :translated attr_reader :exported attr_writer :tags # Proxy a few methods to our @ref object. [:builtin?, :type, :title].each do |method| define_method(method) do @ref.send(method) end end # Set up some boolean test methods [:exported, :translated, :override].each do |method| newmeth = (method.to_s + "?").intern define_method(newmeth) do self.send(method) end end def [](param) param = symbolize(param) if param == :title return self.title end if @params.has_key?(param) @params[param].value else nil end end # Add default values from our definition. def adddefaults defaults = scope.lookupdefaults(self.type) defaults.each do |name, param| unless @params.include?(param.name) self.debug "Adding default for %s" % param.name @params[param.name] = param end end end # Add any metaparams defined in our scope. This actually adds any metaparams # from any parent scope, and there's currently no way to turn that off. def addmetaparams Puppet::Type.eachmetaparam do |name| next if self[name] if val = scope.lookupvar(name.to_s, false) unless val == :undefined set Param.new(:name => name, :value => val, :source => scope.source) end end end end # Add any overrides for this object. def addoverrides overrides = scope.lookupoverrides(self) overrides.each do |over| self.merge(over) end overrides.clear end def builtin=(bool) @ref.builtin = bool end # Retrieve the associated definition and evaluate it. def evaluate if klass = @ref.definedtype finish() scope.deleteresource(self) return klass.evaluate(:scope => scope, :type => self.type, :name => self.title, :arguments => self.to_hash, :scope => self.scope, :exported => self.exported ) elsif builtin? devfail "Cannot evaluate a builtin type" else self.fail "Cannot find definition %s" % self.type end ensure @evaluated = true end def exported=(value) if value @virtual = true @exported = value else @exported = value end end def evaluated? if defined? @evaluated and @evaluated true else false end end # Do any finishing work on this object, called before evaluation or # before storage/translation. def finish addoverrides() adddefaults() addmetaparams() end def initialize(options) options = symbolize_options(options) # Collect the options necessary to make the reference. refopts = [:type, :title].inject({}) do |hash, param| hash[param] = options[param] options.delete(param) hash end @params = {} tmpparams = nil if tmpparams = options[:params] options.delete(:params) end # Now set the rest of the options. set_options(options) @ref = Reference.new(refopts) requiredopts(:scope, :source) @ref.scope = self.scope if tmpparams tmpparams.each do |param| # We use the method here, because it does type-checking. set(param) end end end # Merge an override resource in. def merge(resource) # Some of these might fail, but they'll fail in the way we want. resource.params.each do |name, param| set(param) end end # This *significantly* reduces the number of calls to Puppet.[]. def paramcheck? unless defined? @@paramcheck @@paramcheck = Puppet[:paramcheck] end @@paramcheck 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) # 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 (param == "name" or param == "title") # always allow these true elsif paramcheck? self.fail Puppet::ParseError, "Invalid parameter '%s' for type '%s'" % [param.inspect, @ref.type] end end # A temporary occasion, until I get paths in the scopes figured out. def path to_s end # Return the short version of our name. def ref @ref.to_s end # You have to pass a Resource::Param to this. def set(param) # Because definitions are now parse-time, I can paramcheck immediately. paramcheck(param.name) if current = @params[param.name] # XXX Should we ignore any settings that have the same values? if param.source.child_of?(current.source) # Replace it, keeping all of its info. @params[param.name] = param else if Puppet[:trace] puts caller end fail Puppet::ParseError, "Parameter %s is already set on %s by %s" % [param.name, self.to_s, param.source] end else if self.source == param.source or param.source.child_of?(self.source) @params[param.name] = param else fail Puppet::ParseError, "Only subclasses can set parameters" end end end #def tags # unless defined? @tags # @tags = scope.tags # @tags << self.type # end # @tags #end def to_hash @params.inject({}) do |hash, ary| param = ary[1] hash[param.name] = param.value hash end end # Turn our parser resource into a Rails resource. def to_rails(host) args = {} #FIXME: support files/lines, etc. #%w{type title tags file line exported}.each do |param| %w{type title exported}.each do |param| if value = self.send(param) args[param] = value end end # 'type' isn't a valid column name, so we have to use something else. args = symbolize_options(args) args[:restype] = args[:type] args.delete(:type) # Let's see if the object exists if obj = host.resources.find_by_restype_and_title(self.type, self.title) # We exist args.each do |param, value| obj[param] = value end else # Else create it anew obj = host.resources.build(args) end if l = self.line obj.line = l end # Either way, now add our parameters - exists = {} - obj.param_names.each do |pn| exists[pn.name] = pn end - @params.each do |name, param| + obj.collection_merge(:param_names, @params) do |name, param| param.to_rails(obj) - exists.delete(name.to_s) if exists.include?(name.to_s) - end - - unless exists.empty? - obj.save - exists.each do |name, param| - obj.param_names.delete(param) - end end return obj end def to_s self.ref end # Translate our object to a transportable object. def to_trans unless builtin? devfail "Tried to translate a non-builtin resource" end return nil if virtual? # Now convert to a transobject obj = Puppet::TransObject.new(@ref.title, @ref.type) to_hash.each do |p, v| if v.is_a?(Reference) v = v.to_ref elsif v.is_a?(Array) v = v.collect { |av| if av.is_a?(Reference) av = av.to_ref end av } end obj[p.to_s] = v end obj.file = self.file obj.line = self.line #obj.tags = self.tags return obj end def virtual? self.virtual end end # $Id$ diff --git a/lib/puppet/parser/resource/param.rb b/lib/puppet/parser/resource/param.rb index 6f24f1486..1f7a66aae 100644 --- a/lib/puppet/parser/resource/param.rb +++ b/lib/puppet/parser/resource/param.rb @@ -1,61 +1,48 @@ # The parameters we stick in Resources. class Puppet::Parser::Resource::Param attr_accessor :name, :value, :source, :line, :file 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.type}>" end # Store this parameter in a Rails db. def to_rails(res) values = value.is_a?(Array) ? value : [value] unless pn = res.param_names.find_by_name(self.name.to_s) # We're creating it anew. pn = res.param_names.build(:name => self.name.to_s) end + + value_objects = [] if l = self.line pn.line = Integer(l) end - exists = {} - pn.param_values.each { |pv| exists[pv.value] = pv } - values.each do |value| - unless pn.param_values.find_by_value(value) - pn.param_values.build(:value => value) - end - # Mark that this is still valid. - if exists.include?(value) - exists.delete(value) - end - end - - # And remove any existing values that are not in the current value list. - unless exists.empty? - # We have to save the current state else the deletion somehow deletes - # our new values. - pn.save - exists.each do |value, obj| - pn.param_values.delete(obj) + pn.collection_merge(:param_values, values) do |value| + unless pv = pn.param_values.find_by_value(value) + pv = pn.param_values.build(:value => value) end + pv end return pn end def to_s "%s => %s" % [self.name, self.value] end end # $Id$ diff --git a/lib/puppet/rails/host.rb b/lib/puppet/rails/host.rb index a46fa92a5..fcd078cb7 100644 --- a/lib/puppet/rails/host.rb +++ b/lib/puppet/rails/host.rb @@ -1,92 +1,86 @@ require 'puppet/rails/resource' +require 'puppet/util/rails/collection_merger' class Puppet::Rails::Host < ActiveRecord::Base + include Puppet::Util::CollectionMerger + has_many :fact_values, :through => :fact_names has_many :fact_names, :dependent => :destroy belongs_to :puppet_classes has_many :source_files has_many :resources, :include => [ :param_names, :param_values ], :dependent => :destroy acts_as_taggable - def facts(name) - if fv = self.fact_values.find(:first, :conditions => "fact_names.name = '#{name}'") - return fv.value - else - return nil - end - end - # If the host already exists, get rid of its objects def self.clean(host) if obj = self.find_by_name(host) obj.rails_objects.clear return obj else return nil end end # Store our host in the database. def self.store(hash) - unless hash[:name] + unless name = hash[:name] raise ArgumentError, "You must specify the hostname for storage" end args = {} - if hash[:facts].include?("ipaddress") - args[:ip] = hash[:facts]["ipaddress"] + unless host = find_by_name(name) + host = new(:name => name) end - unless host = find_by_name(hash[:facts]["hostname"]) - host = new(:name => hash[:facts]["hostname"]) + if ip = hash[:facts]["ipaddress"] + host.ip = ip end - # Store the facts into the - hash[:facts].each do |name, value| - fn = host.fact_names.find_by_name(name) || host.fact_names.build(:name => name) - unless fn.fact_values.find_by_value(value) - fn.fact_values.build(:value => value) - end - end + # Store the facts into the database. + host.setfacts(hash[:facts]) unless hash[:resources] raise ArgumentError, "You must pass resources" end - resources = [] - hash[:resources].each do |resource| - resources << resource.to_rails(host) - end + host.setresources(hash[:resources]) host.save return host end - # Add all of our RailsObjects - def addobjects(objects) - objects.each do |tobj| - params = {} - tobj.each do |p,v| params[p] = v end - - args = {:ptype => tobj.type, :name => tobj.name} - [:tags, :file, :line].each do |param| - if val = tobj.send(param) - args[param] = val - end + # Return the value of a fact. + def fact(name) + if fv = self.fact_values.find(:first, :conditions => "fact_names.name = '#{name}'") + return fv.value + else + return nil + end + end + + def setfacts(facts) + collection_merge(:fact_names, facts) do |name, value| + fn = fact_names.find_by_name(name) || fact_names.build(:name => name) + # We're only ever going to have one fact value, at this point. + unless fv = fn.fact_values.find_by_value(value) + fv = fn.fact_values.build(:value => value) end + fn.fact_values = [fv] - robj = rails_objects.build(args) + fn + end + end - robj.addparams(params) - if tobj.collectable - robj.toggle(:collectable) - end + # Set our resources. + def setresources(list) + collection_merge(:resources, list) do |resource| + resource.to_rails(self) end end end # $Id$ diff --git a/lib/puppet/rails/param_name.rb b/lib/puppet/rails/param_name.rb index dba6960da..b609f10c5 100644 --- a/lib/puppet/rails/param_name.rb +++ b/lib/puppet/rails/param_name.rb @@ -1,17 +1,20 @@ +require 'puppet/util/rails/collection_merger' + class Puppet::Rails::ParamName < ActiveRecord::Base + include Puppet::Util::CollectionMerger has_many :param_values, :dependent => :destroy belongs_to :resource def to_resourceparam(source) hash = {} hash[:name] = self.name.to_sym hash[:source] = source hash[:value] = self.param_values.find(:all).collect { |v| v.value } if hash[:value].empty? hash[:value] = nil end Puppet::Parser::Resource::Param.new hash end end # $Id$ diff --git a/lib/puppet/rails/resource.rb b/lib/puppet/rails/resource.rb index c0a7fbd8c..d78b02b88 100644 --- a/lib/puppet/rails/resource.rb +++ b/lib/puppet/rails/resource.rb @@ -1,69 +1,72 @@ require 'puppet' require 'puppet/rails/lib/init' require 'puppet/rails/param_name' +require 'puppet/util/rails/collection_merger' class Puppet::Rails::Resource < ActiveRecord::Base + include Puppet::Util::CollectionMerger + has_many :param_values, :through => :param_names has_many :param_names, :dependent => :destroy has_many :source_files belongs_to :host acts_as_taggable def [](param) return super || parameter(param) end def parameter(param) if pn = param_names.find_by_name(param) if pv = pn.param_values.find(:first) return pv.value else return nil end end end def parameters hash = {} self.param_values.find(:all).each do |pvalue| pname = pvalue.param_name.name hash.store(pname, pvalue.value) end return hash end def ref "%s[%s]" % [self[:restype], self[:title]] end # Convert our object to a resource. Do not retain whether the object # is exported, though, since that would cause it to get stripped # from the configuration. def to_resource(scope) hash = self.attributes hash["type"] = hash["restype"] hash.delete("restype") # FIXME At some point, we're going to want to retain this information # for logging and auditing. hash.delete("host_id") hash.delete("source_file_id") hash.delete("id") hash.each do |p, v| hash.delete(p) if v.nil? end hash[:scope] = scope hash[:source] = scope.source obj = Puppet::Parser::Resource.new(hash) self.param_names.each do |pname| obj.set(pname.to_resourceparam(scope.source)) end # Store the ID, so we can check if we're re-collecting the same resource. obj.rails_id = self.id return obj end end diff --git a/lib/puppet/util/rails/collection_merger.rb b/lib/puppet/util/rails/collection_merger.rb new file mode 100644 index 000000000..7afd76f9c --- /dev/null +++ b/lib/puppet/util/rails/collection_merger.rb @@ -0,0 +1,25 @@ +module Puppet::Util::CollectionMerger + # Merge new values with the old list. This is only necessary + # because deletion seems to mess things up on unsaved objects. + def collection_merge(collection, list) + remove = send(collection).dup + + list.each do |value| + object = yield(value) + if remove.include?(object) + remove.delete(object) + end + end + + unless remove.empty? + # We have to save the current state else the deletion somehow deletes + # our new values. + save + remove.each do |r| + send(collection).delete(r) + end + end + end +end + +# $Id$ diff --git a/test/language/ast.rb b/test/language/ast.rb index 733ca4b36..d42ac7936 100755 --- a/test/language/ast.rb +++ b/test/language/ast.rb @@ -1,537 +1,537 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet' require 'puppet/rails' require 'puppet/parser/interpreter' require 'puppet/parser/parser' require 'puppet/client' require 'puppettest' require 'puppettest/resourcetesting' require 'puppettest/parsertesting' require 'puppettest/railstesting' class TestAST < Test::Unit::TestCase include PuppetTest::RailsTesting include PuppetTest::ParserTesting include PuppetTest::ResourceTesting # A fake class that we can use for testing evaluation. class FakeAST attr_writer :evaluate def evaluate(*args) return @evaluate end def initialize(val = nil) if val @evaluate = val end end def safeevaluate(*args) evaluate() end end if defined? ActiveRecord # Verify that our collection stuff works. def test_collection collectable = [] non = [] # First put some objects into the database. bucket = mk_transtree do |object, depth, width| # and mark some of them collectable if width % 2 == 1 object.collectable = true collectable << object else non << object end end # Now collect our facts facts = {} Facter.each do |fact, value| facts[fact] = value end assert_nothing_raised { Puppet::Rails.init } # Now try storing our crap assert_nothing_raised { host = Puppet::Rails::Host.store( :objects => bucket, :facts => facts, :host => facts["hostname"] ) } # Now create an ast tree that collects that. They should all be files. coll = nil assert_nothing_raised { coll = AST::Collection.new( :type => nameobj("file") ) } top = nil assert_nothing_raised("Could not create top object") { top = AST::ASTArray.new( :children => [coll] ) } objects = nil assert_nothing_raised("Could not evaluate") { scope = mkscope objects = scope.evaluate(:ast => top).flatten } assert(objects.length > 0, "Did not receive any collected objects") end else $stderr.puts "No ActiveRecord -- skipping collection tests" end 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(:scope => "yay") } assert_equal(:if, ret) # Now set it to false and check that faketest.evaluate = false assert_nothing_raised { ret = astif.evaluate(:scope => "yay") } assert_equal(:else, ret) end # Make sure our override object behaves "correctly" def test_override interp, scope, source = mkclassframing ref = nil assert_nothing_raised do ref = resourceoverride("resource", "yaytest", "one" => "yay", "two" => "boo") end ret = nil assert_nothing_raised do ret = ref.evaluate :scope => scope end assert_instance_of(Puppet::Parser::Resource, ret) assert(ret.override?, "Resource was not an override resource") assert(scope.overridetable[ret.ref].include?(ret), "Was not stored in the override table") end # make sure our resourcedefaults ast object works correctly. def test_resourcedefaults interp, scope, source = mkclassframing # Now make some defaults for files args = {:source => "/yay/ness", :group => "yayness"} assert_nothing_raised do obj = defaultobj "file", args obj.evaluate :scope => scope end 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_hostclass interp, scope, source = mkclassframing # Create the class we're testing, first with no parent klass = interp.newclass "first", :code => AST::ASTArray.new( :children => [resourcedef("file", "/tmp", "owner" => "nobody", "mode" => "755")] ) assert_nothing_raised do klass.evaluate(:scope => scope) end # Then try it again assert_nothing_raised do klass.evaluate(:scope => scope) end assert(scope.setclass?(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 = interp.newclass "newbase", :code => AST::ASTArray.new( :children => [resourcedef("file", "/tmp/other", "owner" => "nobody", "mode" => "644")] ) newsub = interp.newclass "newsub", :parent => "newbase", :code => AST::ASTArray.new( :children => [resourcedef("file", "/tmp/yay", "owner" => "nobody", "mode" => "755"), resourceoverride("file", "/tmp/other", "owner" => "daemon") ] ) # Override a different variable in the top scope. moresub = interp.newclass "moresub", :parent => "newbase", :code => AST::ASTArray.new( :children => [resourceoverride("file", "/tmp/other", "mode" => "755")] ) assert_nothing_raised do newsub.evaluate(:scope => scope) end assert_nothing_raised do moresub.evaluate(:scope => scope) end assert(scope.setclass?(newbase), "Did not eval newbase") assert(scope.setclass?(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 def test_component interp, scope, source = mkclassframing # Create a new definition klass = interp.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 # Now call it a couple of times # First try it without a required param assert_raise(Puppet::ParseError) do klass.evaluate(:scope => scope, :name => "bad", :arguments => {"owner" => "nobody"} ) end # And make sure it didn't create the file assert_nil(scope.findresource("file[/tmp/bad]"), "Made file with invalid params") assert_nothing_raised do klass.evaluate(:scope => scope, :name => "first", :arguments => {"mode" => "755"} ) end firstobj = scope.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 => scope, :name => "first", :arguments => {"mode" => "755"} ) end # Now create another with different args assert_nothing_raised do klass.evaluate(:scope => scope, :name => "second", :arguments => {"mode" => "755", "owner" => "daemon"} ) end secondobj = scope.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 # Make sure that classes set their namespaces to themselves. This # way they start looking for definitions in their own namespace. def test_hostclass_namespace interp, scope, source = mkclassframing # Create a new class klass = nil assert_nothing_raised do klass = interp.newclass "funtest" end # Now define a definition in that namespace define = nil assert_nothing_raised do define = interp.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) assert_equal("funtest", newscope.namespace, "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 def test_node interp = mkinterp scope = mkscope(:interp => interp) # Define a base node basenode = interp.newnode "basenode", :code => AST::ASTArray.new(:children => [ resourcedef("file", "/tmp/base", "owner" => "root") ]) # Now define a subnode nodes = interp.newnode ["mynode", "othernode"], :code => AST::ASTArray.new(:children => [ resourcedef("file", "/tmp/mynode", "owner" => "root"), resourcedef("file", "/tmp/basenode", "owner" => "daemon") ]) assert_instance_of(Array, nodes) # Make sure we can find them all. %w{mynode othernode}.each do |node| assert(interp.nodesearch_code(node), "Could not find %s" % node) end mynode = interp.nodesearch_code("mynode") # Now try evaluating the node assert_nothing_raised do mynode.evaluate :scope => scope 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 = interp.newnode(%w{child}, :parent => "basenode").shift newscope = mkscope :interp => interp assert_nothing_raised do child.evaluate :scope => newscope end assert(newscope.findresource("file[/tmp/base]"), "Could not find base resource") end def test_collection interp = mkinterp scope = mkscope(:interp => interp) 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 => scope end assert_instance_of(Puppet::Parser::Collector, ret) # Now make sure we get it back from the scope assert_equal([ret], scope.collections) end def test_virtual_collexp @interp, @scope, @source = mkclassframing # make a resource resource = mkresource(:type => "file", :title => "/tmp/testing", :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 => @scope end assert_instance_of(Proc, code) assert_nothing_raised do assert_equal(result, code.call(resource), "'#{string}' failed") end end end if defined? ActiveRecord::Base def test_exported_collexp railsinit Puppet[:storeconfigs] = true @interp, @scope, @source = mkclassframing # make a rails resource railsresource "file", "/tmp/testing", :owner => "root", :group => "bin", :mode => "644" run_collection_queries(:exported) do |string, result, query| code = nil str = nil # We don't support anything but the title in rails right now retval = nil bad = false # Figure out if the search is for anything rails will ignore string.scan(/(\w+) [!=]= \w+/) do |s| unless s[0] == "title" bad = true break end end # And if it is, make sure we throw an error. if bad assert_raise(Puppet::ParseError, "Evaluated '#{string}'") do str, code = query.evaluate :scope => @scope end next else assert_nothing_raised("Could not evaluate '#{string}'") do str, code = query.evaluate :scope => @scope end end assert_nothing_raised("Could not find resource") do retval = Puppet::Rails::Resource.find(:all, :include => :param_names, :conditions => str) end if result assert_equal(1, retval.length, "Did not find resource with '#{string}'") res = retval.shift # Not sure this is correct, maybe we need a case statement to convert to/from # the autogenerated Puppet classes to - assert_equal(PuppetFile, res.class) + assert_equal("file", res.restype) assert_equal("/tmp/testing", res.title) else assert_equal(0, retval.length, "found a resource with '#{string}'") end end end end def run_collection_queries(form) {true => [%{title == "/tmp/testing"}, %{(title == "/tmp/testing")}, %{title == "/tmp/testing" and group == bin}, %{title == bin or group == bin}, %{title == "/tmp/testing" or title == bin}, %{title == "/tmp/testing"}, %{(title == "/tmp/testing" or title == bin) and group == bin}], false => [%{title == bin}, %{title == bin or (title == bin and group == bin)}, %{title != "/tmp/testing"}, %{title != "/tmp/testing" and group != bin}] }.each do |res, ary| ary.each do |str| if form == :virtual code = "File <| #{str} |>" else code = "File <<| #{str} |>>" end parser = mkparser query = nil assert_nothing_raised("Could not parse '#{str}'") do query = parser.parse(code)[0].query end yield str, res, query end end end end # $Id$ diff --git a/test/language/interpreter.rb b/test/language/interpreter.rb index 3e542acb4..a19bec288 100755 --- a/test/language/interpreter.rb +++ b/test/language/interpreter.rb @@ -1,915 +1,915 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'facter' require 'puppet' require 'puppet/parser/interpreter' require 'puppet/parser/parser' require 'puppet/client' require 'puppet/rails' require 'puppettest' require 'puppettest/resourcetesting' require 'puppettest/parsertesting' require 'puppettest/servertest' require 'puppettest/railstesting' require 'timeout' class TestInterpreter < Test::Unit::TestCase include PuppetTest include PuppetTest::ServerTest include PuppetTest::ParserTesting include PuppetTest::ResourceTesting include PuppetTest::RailsTesting AST = Puppet::Parser::AST # create a simple manifest that uses nodes to create a file def mknodemanifest(node, file) createdfile = tempfile() File.open(file, "w") { |f| f.puts "node %s { file { \"%s\": ensure => file, mode => 755 } }\n" % [node, createdfile] } return [file, createdfile] end def test_simple file = tempfile() File.open(file, "w") { |f| f.puts "file { \"/etc\": owner => root }" } assert_nothing_raised { Puppet::Parser::Interpreter.new(:Manifest => file) } end def test_reloadfiles hostname = Facter["hostname"].value file = tempfile() # Create a first version createdfile = mknodemanifest(hostname, file) interp = nil assert_nothing_raised { interp = Puppet::Parser::Interpreter.new(:Manifest => file) } config = nil assert_nothing_raised { config = interp.run(hostname, {}) } sleep(1) # Now create a new file createdfile = mknodemanifest(hostname, file) newconfig = nil assert_nothing_raised { newconfig = interp.run(hostname, {}) } assert(config != newconfig, "Configs are somehow the same") end if defined? ActiveRecord def test_hoststorage assert_nothing_raised { Puppet[:storeconfigs] = true } file = tempfile() File.open(file, "w") { |f| f.puts "file { \"/etc\": owner => root }" } interp = nil assert_nothing_raised { interp = Puppet::Parser::Interpreter.new( :Manifest => file, :UseNodes => false, :ForkSave => false ) } facts = {} Facter.each { |fact, val| facts[fact] = val } objects = nil assert_nothing_raised { objects = interp.run(facts["hostname"], facts) } obj = Puppet::Rails::Host.find_by_name(facts["hostname"]) assert(obj, "Could not find host object") end else $stderr.puts "No ActiveRecord -- skipping collection tests" end if Facter["domain"].value == "madstop.com" begin require 'ldap' $haveldap = true rescue LoadError $stderr.puts "Missing ldap; skipping ldap source tests" $haveldap = false end # Only test ldap stuff on luke's network, since that's the only place we # have data for. if $haveldap def ldapconnect @ldap = LDAP::Conn.new("ldap", 389) @ldap.set_option( LDAP::LDAP_OPT_PROTOCOL_VERSION, 3 ) @ldap.simple_bind("", "") return @ldap end def ldaphost(node) parent = nil classes = nil @ldap.search( "ou=hosts, dc=madstop, dc=com", 2, "(&(objectclass=puppetclient)(cn=%s))" % node ) do |entry| parent = entry.vals("parentnode").shift classes = entry.vals("puppetclass") || [] end return parent, classes end def test_ldapsearch Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" Puppet[:ldapnodes] = true ldapconnect() interp = mkinterp :NodeSources => [:ldap, :code] # Make sure we get nil and nil back when we search for something missing parent, classes = nil assert_nothing_raised do parent, classes = interp.ldapsearch("nosuchhost") end assert_nil(parent, "Got a parent for a non-existent host") assert_nil(classes, "Got classes for a non-existent host") # Make sure we can find 'culain' in ldap assert_nothing_raised do parent, classes = interp.ldapsearch("culain") end realparent, realclasses = ldaphost("culain") assert_equal(realparent, parent) assert_equal(realclasses, classes) end def test_ldapnodes Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" Puppet[:ldapnodes] = true ldapconnect() interp = mkinterp :NodeSources => [:ldap, :code] # culain uses basenode, so create that basenode = interp.newnode([:basenode])[0] # Make sure we get nothing for nonexistent hosts none = nil assert_nothing_raised do none = interp.nodesearch_ldap("nosuchhost") end assert_nil(none, "Got a node for a non-existent host") # Make sure we can find 'culain' in ldap culain = nil assert_nothing_raised do culain = interp.nodesearch_ldap("culain") end assert(culain, "Did not find culain in ldap") assert_nothing_raised do assert_equal(basenode.fqname.to_s, culain.parentclass.fqname.to_s, "Did not get parent class") end end if Puppet::SUIDManager.uid == 0 and Facter["hostname"].value == "culain" def test_ldapreconnect Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" Puppet[:ldapnodes] = true interp = nil assert_nothing_raised { interp = Puppet::Parser::Interpreter.new( :Manifest => mktestmanifest() ) } hostname = "culain.madstop.com" # look for our host assert_nothing_raised { parent, classes = interp.nodesearch_ldap(hostname) } # Now restart ldap system("/etc/init.d/slapd restart 2>/dev/null >/dev/null") sleep(1) # and look again assert_nothing_raised { parent, classes = interp.nodesearch_ldap(hostname) } # Now stop ldap system("/etc/init.d/slapd stop 2>/dev/null >/dev/null") cleanup do system("/etc/init.d/slapd start 2>/dev/null >/dev/null") end # And make sure we actually fail here assert_raise(Puppet::Error) { parent, classes = interp.nodesearch_ldap(hostname) } end else $stderr.puts "Run as root for ldap reconnect tests" end end else $stderr.puts "Not in madstop.com; skipping ldap tests" end # Test that node info and default node info in different sources isn't # bad. def test_multiple_nodesources # Create another node source Puppet::Parser::Interpreter.send(:define_method, :nodesearch_multi) do |*names| if names[0] == "default" gennode("default", {:facts => {}}) else nil end end interp = mkinterp :NodeSources => [:multi, :code] interp.newnode(["node"]) obj = nil assert_nothing_raised do obj = interp.nodesearch("node") end assert(obj, "Did not find node") assert_equal("node", obj.fqname) end # Make sure searchnode behaves as we expect. def test_nodesearch # We use two sources here to catch a weird bug where the default # node is used if the host isn't in the first source. interp = mkinterp # Make some nodes names = %w{node1 node2 node2.domain.com} interp.newnode names interp.newnode %w{default} nodes = {} # Make sure we can find them all, using the direct method names.each do |name| nodes[name] = interp.nodesearch_code(name) assert(nodes[name], "Could not find %s" % name) nodes[name].file = __FILE__ end # Now let's try it with the nodesearch method names.each do |name| node = interp.nodesearch(name) assert(node, "Could not find #{name} via nodesearch") end # Make sure we find the default node when we search for nonexistent nodes assert_nothing_raised do default = interp.nodesearch("nosuchnode") assert(default, "Did not find default node") assert_equal("default", default.fqname) end # Now make sure the longest match always wins node = interp.nodesearch(*%w{node2 node2.domain.com}) assert(node, "Did not find node2") assert_equal("node2.domain.com", node.fqname, "Did not get longest match") end def test_parsedate Puppet[:filetimeout] = 0 main = tempfile() sub = tempfile() mainfile = tempfile() subfile = tempfile() count = 0 updatemain = proc do count += 1 File.open(main, "w") { |f| f.puts "import '#{sub}' file { \"#{mainfile}\": content => #{count} } " } end updatesub = proc do count += 1 File.open(sub, "w") { |f| f.puts "file { \"#{subfile}\": content => #{count} } " } end updatemain.call updatesub.call interp = Puppet::Parser::Interpreter.new( :Manifest => main, :Local => true ) date = interp.parsedate # Now update the site file and make sure we catch it sleep 1 updatemain.call newdate = interp.parsedate assert(date != newdate, "Parsedate was not updated") date = newdate # And then the subfile sleep 1 updatesub.call newdate = interp.parsedate assert(date != newdate, "Parsedate was not updated") end # Make sure our node gets added to the node table. def test_newnode interp = mkinterp # First just try calling it directly assert_nothing_raised { interp.newnode("mynode", :code => :yay) } assert_equal(:yay, interp.nodesearch_code("mynode").code) # Now make sure that trying to redefine it throws an error. assert_raise(Puppet::ParseError) { interp.newnode("mynode", {}) } # Now try one with no code assert_nothing_raised { interp.newnode("simplenode", :parent => :foo) } # Make sure trying to get the parentclass throws an error assert_raise(Puppet::ParseError) do interp.nodesearch_code("simplenode").parentclass end # Now define the parent node interp.newnode(:foo) # And make sure we get things back correctly assert_equal("foo", interp.nodesearch_code("simplenode").parentclass.fqname) assert_nil(interp.nodesearch_code("simplenode").code) # Now make sure that trying to redefine it throws an error. assert_raise(Puppet::ParseError) { interp.newnode("mynode", {}) } # Test multiple names names = ["one", "two", "three"] assert_nothing_raised { interp.newnode(names, {:code => :yay, :parent => :foo}) } names.each do |name| assert_equal(:yay, interp.nodesearch_code(name).code) assert_equal("foo", interp.nodesearch_code(name).parentclass.name) # Now make sure that trying to redefine it throws an error. assert_raise(Puppet::ParseError) { interp.newnode(name, {}) } end end # Make sure we're correctly generating a node definition. def test_gennode interp = mkinterp interp.newnode "base" interp.newclass "yaytest" # Go through the different iterations: [ [nil, "yaytest"], [nil, ["yaytest"]], [nil, nil], [nil, []], ["base", nil], ["base", []], ["base", "yaytest"], ["base", ["yaytest"]] ].each do |parent, classes| node = nil assert_nothing_raised { node = interp.gennode("nodeA", :classes => classes, :parentnode => parent) } assert_instance_of(Puppet::Parser::AST::Node, node) assert_equal("nodeA", node.name) scope = mkscope :interp => interp assert_nothing_raised do node.evaluate :scope => scope end # If there's a parent, make sure it got evaluated if parent assert(scope.classlist.include?("base"), "Did not evaluate parent node") end # If there are classes make sure they got evaluated if classes == ["yaytest"] or classes == "yaytest" assert(scope.classlist.include?("yaytest"), "Did not evaluate class") end end end def test_fqfind interp = mkinterp table = {} # Define a bunch of things. %w{a c a::b a::b::c a::c a::b::c::d a::b::c::d::e::f c::d}.each do |string| table[string] = string end check = proc do |namespace, hash| hash.each do |thing, result| assert_equal(result, interp.fqfind(namespace, thing, table), "Could not find %s in %s" % [thing, namespace]) end end # Now let's do some test lookups. # First do something really simple check.call "a", "b" => "a::b", "b::c" => "a::b::c", "d" => nil, "::c" => "c" check.call "a::b", "c" => "a::b::c", "b" => "a::b", "a" => "a" check.call "a::b::c::d::e", "c" => "a::b::c", "::c" => "c", "c::d" => "a::b::c::d", "::c::d" => "c::d" check.call "", "a" => "a", "a::c" => "a::c" end def test_newdefine interp = mkinterp assert_nothing_raised { interp.newdefine("mydefine", :code => :yay, :arguments => ["a", stringobj("b")]) } mydefine = interp.finddefine("", "mydefine") assert(mydefine, "Could not find definition") assert_equal("mydefine", interp.finddefine("", "mydefine").type) assert_equal("", mydefine.namespace) assert_equal("mydefine", mydefine.type) assert_raise(Puppet::ParseError) do interp.newdefine("mydefine", :code => :yay, :arguments => ["a", stringobj("b")]) end # Now define the same thing in a different scope assert_nothing_raised { interp.newdefine("other::mydefine", :code => :other, :arguments => ["a", stringobj("b")]) } other = interp.finddefine("other", "mydefine") assert(other, "Could not find definition") assert(interp.finddefine("", "other::mydefine"), "Could not find other::mydefine") assert_equal(:other, other.code) assert_equal("other", other.namespace) assert_equal("mydefine", other.type) assert_equal("other::mydefine", other.fqname) end def test_newclass interp = mkinterp mkcode = proc do |ary| classes = ary.collect do |string| AST::FlatString.new(:value => string) end AST::ASTArray.new(:children => classes) end scope = Puppet::Parser::Scope.new(:interp => interp) # First make sure that code is being appended code = mkcode.call(%w{original code}) klass = nil assert_nothing_raised { klass = interp.newclass("myclass", :code => code) } assert(klass, "Did not return class") assert(interp.findclass("", "myclass"), "Could not find definition") assert_equal("myclass", interp.findclass("", "myclass").type) assert_equal(%w{original code}, interp.findclass("", "myclass").code.evaluate(:scope => scope)) # Now create the same class name in a different scope assert_nothing_raised { klass = interp.newclass("other::myclass", :code => mkcode.call(%w{something diff})) } assert(klass, "Did not return class") other = interp.findclass("other", "myclass") assert(other, "Could not find class") assert(interp.findclass("", "other::myclass"), "Could not find class") assert_equal("other::myclass", other.fqname) assert_equal("other::myclass", other.namespace) assert_equal("myclass", other.type) assert_equal(%w{something diff}, interp.findclass("other", "myclass").code.evaluate(:scope => scope)) # Newclass behaves differently than the others -- it just appends # the code to the existing class. code = mkcode.call(%w{something new}) assert_nothing_raised do klass = interp.newclass("myclass", :code => code) end assert(klass, "Did not return class when appending") assert_equal(%w{original code something new}, interp.findclass("", "myclass").code.evaluate(:scope => scope)) # Make sure newclass deals correctly with nodes with no code klass = interp.newclass("nocode") assert(klass, "Did not return class") assert_nothing_raised do klass = interp.newclass("nocode", :code => mkcode.call(%w{yay test})) end assert(klass, "Did not return class with no code") assert_equal(%w{yay test}, interp.findclass("", "nocode").code.evaluate(:scope => scope)) # Then try merging something into nothing interp.newclass("nocode2", :code => mkcode.call(%w{foo test})) assert(klass, "Did not return class with no code") assert_nothing_raised do klass = interp.newclass("nocode2") end assert(klass, "Did not return class with no code") assert_equal(%w{foo test}, interp.findclass("", "nocode2").code.evaluate(:scope => scope)) # And lastly, nothing and nothing klass = interp.newclass("nocode3") assert(klass, "Did not return class with no code") assert_nothing_raised do klass = interp.newclass("nocode3") end assert(klass, "Did not return class with no code") assert_nil(interp.findclass("", "nocode3").code) end # Now make sure we get appropriate behaviour with parent class conflicts. def test_newclass_parentage interp = mkinterp interp.newclass("base1") interp.newclass("one::two::three") # First create it with no parentclass. assert_nothing_raised { interp.newclass("sub") } assert(interp.findclass("", "sub"), "Could not find definition") assert_nil(interp.findclass("", "sub").parentclass) # Make sure we can't set the parent class to ourself. assert_raise(Puppet::ParseError) { interp.newclass("sub", :parent => "sub") } # Now create another one, with a parentclass. assert_nothing_raised { interp.newclass("sub", :parent => "base1") } # Make sure we get the right parent class, and make sure it's an object. assert_equal(interp.findclass("", "base1"), interp.findclass("", "sub").parentclass) # Now make sure we get a failure if we try to conflict. assert_raise(Puppet::ParseError) { interp.newclass("sub", :parent => "one::two::three") } # Make sure that failure didn't screw us up in any way. assert_equal(interp.findclass("", "base1"), interp.findclass("", "sub").parentclass) # But make sure we can create a class with a fq parent assert_nothing_raised { interp.newclass("another", :parent => "one::two::three") } assert_equal(interp.findclass("", "one::two::three"), interp.findclass("", "another").parentclass) end def test_namesplit interp = mkinterp assert_nothing_raised do {"base::sub" => %w{base sub}, "main" => ["", "main"], "one::two::three::four" => ["one::two::three", "four"], }.each do |name, ary| result = interp.namesplit(name) assert_equal(ary, result, "%s split to %s" % [name, result]) end end end # Make sure you can't have classes and defines with the same name in the # same scope. def test_classes_beat_defines interp = mkinterp assert_nothing_raised { interp.newclass("yay::funtest") } assert_raise(Puppet::ParseError) do interp.newdefine("yay::funtest") end assert_nothing_raised { interp.newdefine("yay::yaytest") } assert_raise(Puppet::ParseError) do interp.newclass("yay::yaytest") end end # Make sure our whole chain works. def test_evaluate interp, scope, source = mkclassframing # Create a define that we'll be using interp.newdefine("wrapper", :code => AST::ASTArray.new(:children => [ resourcedef("file", varref("name"), "owner" => "root") ])) # Now create a resource that uses that define define = mkresource(:type => "wrapper", :title => "/tmp/testing", :scope => scope, :source => source, :params => :none) scope.setresource define # And a normal resource scope.setresource mkresource(:type => "file", :title => "/tmp/rahness", :scope => scope, :source => source, :params => {:owner => "root"}) # Now evaluate everything objects = nil interp.usenodes = false assert_nothing_raised do objects = interp.evaluate(nil, {}) end assert_instance_of(Puppet::TransBucket, objects) end def test_evaliterate interp, scope, source = mkclassframing # Create a top-level definition that creates a builtin object interp.newdefine("one", :arguments => [%w{owner}], :code => AST::ASTArray.new(:children => [ resourcedef("file", varref("name"), "owner" => varref("owner") ) ]) ) # Create another definition to call that one interp.newdefine("two", :arguments => [%w{owner}], :code => AST::ASTArray.new(:children => [ resourcedef("one", varref("name"), "owner" => varref("owner") ) ]) ) # And then a third interp.newdefine("three", :arguments => [%w{owner}], :code => AST::ASTArray.new(:children => [ resourcedef("two", varref("name"), "owner" => varref("owner") ) ]) ) three = Puppet::Parser::Resource.new( :type => "three", :title => "/tmp/yayness", :scope => scope, :source => source, :params => paramify(source, :owner => "root") ) scope.setresource(three) ret = nil assert_nothing_raised do ret = scope.unevaluated end assert_instance_of(Array, ret) assert(1, ret.length) assert_equal([three], ret) assert(ret.detect { |r| r.ref == "three[/tmp/yayness]"}, "Did not get three back as unevaluated") # Now translate the whole tree assert_nothing_raised do interp.evaliterate(scope) end # Now make sure we've got our file file = scope.findresource "file[/tmp/yayness]" assert(file, "Could not find file") assert_equal("root", file[:owner]) end # Make sure we fail if there are any leftover overrides to perform. # This would normally mean that someone is trying to override an object # that does not exist. def test_failonleftovers interp, scope, source = mkclassframing # Make sure we don't fail, since there are no overrides assert_nothing_raised do interp.failonleftovers(scope) end # Add an override, and make sure it causes a failure over1 = mkresource :scope => scope, :source => source, :params => {:one => "yay"} scope.setoverride(over1) assert_raise(Puppet::ParseError) do interp.failonleftovers(scope) end end def test_evalnode interp = mkinterp interp.usenodes = false scope = Parser::Scope.new(:interp => interp) facts = Facter.to_hash # First make sure we get no failures when client is nil assert_nothing_raised do interp.evalnode(nil, scope, facts) end # Now define a node interp.newnode "mynode", :code => AST::ASTArray.new(:children => [ resourcedef("file", "/tmp/testing", "owner" => "root") ]) # Eval again, and make sure it does nothing assert_nothing_raised do interp.evalnode("mynode", scope, facts) end assert_nil(scope.findresource("file[/tmp/testing]"), "Eval'ed node with nodes off") # Now enable usenodes and make sure it works. interp.usenodes = true assert_nothing_raised do interp.evalnode("mynode", scope, facts) end file = scope.findresource("file[/tmp/testing]") assert_instance_of(Puppet::Parser::Resource, file, "Could not find file") end # This is mostly used for the cfengine module def test_specificclasses interp = mkinterp :Classes => %w{klass1 klass2}, :UseNodes => false # Make sure it's not a failure to be missing classes, since # we're using the cfengine class list, which is huge. assert_nothing_raised do interp.evaluate(nil, {}) end interp.newclass("klass1", :code => AST::ASTArray.new(:children => [ resourcedef("file", "/tmp/klass1", "owner" => "root") ])) interp.newclass("klass2", :code => AST::ASTArray.new(:children => [ resourcedef("file", "/tmp/klass2", "owner" => "root") ])) ret = nil assert_nothing_raised do ret = interp.evaluate(nil, {}) end found = ret.flatten.collect do |res| res.name end assert(found.include?("/tmp/klass1"), "Did not evaluate klass1") assert(found.include?("/tmp/klass2"), "Did not evaluate klass2") end if defined? ActiveRecord::Base # We need to make sure finished objects are stored in the db. def test_finish_before_store railsinit interp = mkinterp node = interp.newnode ["myhost"], :code => AST::ASTArray.new(:children => [ resourcedef("file", "/tmp/yay", :group => "root"), defaultobj("file", :owner => "root") ]) interp.newclass "myclass", :code => AST::ASTArray.new(:children => [ ]) interp.newclass "sub", :parent => "myclass", :code => AST::ASTArray.new(:children => [ resourceoverride("file", "/tmp/yay", :owner => "root") ] ) # Now do the rails crap Puppet[:storeconfigs] = true interp.evaluate("myhost", {}) # And then retrieve the object from rails - res = Puppet::Rails::Resource.find_by_type_and_title("PuppetFile", "/tmp/yay") + res = Puppet::Rails::Resource.find_by_restype_and_title("file", "/tmp/yay") assert(res, "Did not get resource from rails") param = res.param_names.find_by_name("owner", :include => :param_values) assert(param, "Did not find owner param") - pvalue = param.param_values.find_by_value("root") + pvalue = param.param_values.find_by_value("root") assert_equal("root", pvalue[:value]) end end end # $Id$ diff --git a/test/lib/puppettest/railstesting.rb b/test/lib/puppettest/railstesting.rb index e2ce7ef3f..c8f55ead8 100644 --- a/test/lib/puppettest/railstesting.rb +++ b/test/lib/puppettest/railstesting.rb @@ -1,49 +1,49 @@ module PuppetTest::RailsTesting Parser = Puppet::Parser AST = Puppet::Parser::AST include PuppetTest::ParserTesting def railsinit Puppet::Rails.init cleanup do ActiveRecord::Base.clear_active_connections! end end def railsteardown if Puppet[:dbadapter] != "sqlite3" Puppet::Rails.teardown end end def railsresource(type = "file", title = "/tmp/testing", params = {}) railsteardown railsinit # We need a host for resources #host = Puppet::Rails::Host.new(:name => Facter.value("hostname")) # Now build a resource resources = [] - resources << mkresource(:restype => type, :title => title, :exported => true, + resources << mkresource(:type => type, :title => title, :exported => true, :params => params) # Now collect our facts facts = Facter.to_hash # Now try storing our crap host = nil assert_nothing_raised { host = Puppet::Rails::Host.store( :resources => resources, :facts => facts, :name => facts["hostname"] ) } # Now save the whole thing host.save end end # $Id$ diff --git a/test/rails/rails.rb b/test/rails/host.rb similarity index 80% copy from test/rails/rails.rb copy to test/rails/host.rb index 69e1fd7b8..38389ac64 100755 --- a/test/rails/rails.rb +++ b/test/rails/host.rb @@ -1,121 +1,136 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet' require 'puppet/rails' require 'puppet/parser/interpreter' require 'puppet/parser/parser' require 'puppet/client' require 'puppettest' require 'puppettest/parsertesting' require 'puppettest/resourcetesting' require 'puppettest/railstesting' -class TestRails < Test::Unit::TestCase +class TestRailsHost < Test::Unit::TestCase include PuppetTest::ParserTesting include PuppetTest::ResourceTesting include PuppetTest::RailsTesting def setup super railsinit end def teardown railsteardown super end def test_includerails assert_nothing_raised { require 'puppet/rails' } end # Don't do any tests w/out this class if Puppet.features.rails? - def test_hostcache + def test_store @interp, @scope, @source = mkclassframing # First make some objects resources = [] 10.times { |i| # Make a file resources << mkresource(:type => "file", :title => "/tmp/file#{i.to_s}", :params => {:owner => "user#{i}"}) # And an exec, so we're checking multiple types resources << mkresource(:type => "exec", :title => "/bin/echo file#{i.to_s}", :params => {:user => "user#{i}"}) } # Now collect our facts - facts = {"hostname" => Facter.value(:hostname), "test1" => "funtest"} + facts = {"hostname" => Facter.value(:hostname), "test1" => "funtest", + "ipaddress" => Facter.value(:ipaddress)} # Now try storing our crap host = nil assert_nothing_raised { host = Puppet::Rails::Host.store( :resources => resources, :facts => facts, :name => facts["hostname"], :classes => ["one", "two::three", "four"] ) } assert(host, "Did not create host") host = nil assert_nothing_raised { host = Puppet::Rails::Host.find_by_name(facts["hostname"]) } assert(host, "Could not find host object") assert(host.resources, "No objects on host") - assert_equal(facts["hostname"], host.facts("hostname"), - "Did not retrieve facts") + facts.each do |fact, value| + assert_equal(value, host.fact(fact), "fact %s is wrong" % fact) + end + assert_equal(facts["ipaddress"], host.ip, "IP did not get set") count = 0 host.resources.each do |resource| assert_equal(host, resource.host) count += 1 i = nil if resource[:title] =~ /file([0-9]+)/ i = $1 else raise "Got weird resource %s" % resource.inspect end assert(resource[:restype] != "", "Did not get a type from the resource") case resource["restype"] when "file": assert_equal("user#{i}", resource.parameter("owner"), "got no owner for %s" % resource.ref) when "exec": assert_equal("user#{i}", resource.parameter("user"), "got no user for %s" % resource.ref) else raise "Unknown type %s" % resource[:restype].inspect end end assert_equal(20, count, "Did not get enough resources") + # Now remove a couple of resources and change a fact + resources.reject! { |r| r.title =~ /file9/ } + facts["test2"] = "yaytest" + facts.delete("test1") host = nil assert_nothing_raised { host = Puppet::Rails::Host.store( :resources => resources, :facts => facts, :name => facts["hostname"], :classes => ["one", "two::three", "four"] ) } + + assert_nil(host.fact('test1'), "removed fact was not deleted") + facts.each do |fact, value| + assert_equal(value, host.fact(fact), "fact %s is wrong" % fact) + end + + assert(! host.resources.find(:all).detect { |r| r.title =~ /file9/ }, + "Removed resources are still present") end else $stderr.puts "Install Rails for Rails and Caching tests" end end # $Id$ diff --git a/test/rails/rails.rb b/test/rails/rails.rb index 69e1fd7b8..c0f902d65 100755 --- a/test/rails/rails.rb +++ b/test/rails/rails.rb @@ -1,121 +1,37 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet' require 'puppet/rails' require 'puppet/parser/interpreter' require 'puppet/parser/parser' require 'puppet/client' require 'puppettest' require 'puppettest/parsertesting' require 'puppettest/resourcetesting' require 'puppettest/railstesting' class TestRails < Test::Unit::TestCase include PuppetTest::ParserTesting include PuppetTest::ResourceTesting include PuppetTest::RailsTesting def setup super railsinit end def teardown railsteardown super end def test_includerails assert_nothing_raised { require 'puppet/rails' } end - - # Don't do any tests w/out this class - if Puppet.features.rails? - def test_hostcache - @interp, @scope, @source = mkclassframing - # First make some objects - resources = [] - 10.times { |i| - # Make a file - resources << mkresource(:type => "file", - :title => "/tmp/file#{i.to_s}", - :params => {:owner => "user#{i}"}) - - # And an exec, so we're checking multiple types - resources << mkresource(:type => "exec", - :title => "/bin/echo file#{i.to_s}", - :params => {:user => "user#{i}"}) - } - - # Now collect our facts - facts = {"hostname" => Facter.value(:hostname), "test1" => "funtest"} - - # Now try storing our crap - host = nil - assert_nothing_raised { - host = Puppet::Rails::Host.store( - :resources => resources, - :facts => facts, - :name => facts["hostname"], - :classes => ["one", "two::three", "four"] - ) - } - - assert(host, "Did not create host") - - host = nil - assert_nothing_raised { - host = Puppet::Rails::Host.find_by_name(facts["hostname"]) - } - assert(host, "Could not find host object") - - assert(host.resources, "No objects on host") - - assert_equal(facts["hostname"], host.facts("hostname"), - "Did not retrieve facts") - - count = 0 - host.resources.each do |resource| - assert_equal(host, resource.host) - count += 1 - i = nil - if resource[:title] =~ /file([0-9]+)/ - i = $1 - else - raise "Got weird resource %s" % resource.inspect - end - assert(resource[:restype] != "", "Did not get a type from the resource") - case resource["restype"] - when "file": - assert_equal("user#{i}", resource.parameter("owner"), - "got no owner for %s" % resource.ref) - when "exec": - assert_equal("user#{i}", resource.parameter("user"), - "got no user for %s" % resource.ref) - else - raise "Unknown type %s" % resource[:restype].inspect - end - end - - assert_equal(20, count, "Did not get enough resources") - - host = nil - assert_nothing_raised { - host = Puppet::Rails::Host.store( - :resources => resources, - :facts => facts, - :name => facts["hostname"], - :classes => ["one", "two::three", "four"] - ) - } - end - else - $stderr.puts "Install Rails for Rails and Caching tests" - end end # $Id$ diff --git a/test/rails/railsresource.rb b/test/rails/railsresource.rb index 302dd99fb..968e0e1b9 100755 --- a/test/rails/railsresource.rb +++ b/test/rails/railsresource.rb @@ -1,100 +1,100 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet' require 'puppet/rails' require 'puppettest' require 'puppettest/railstesting' require 'puppettest/resourcetesting' # Don't do any tests w/out this class if Puppet.features.rails? class TestRailsResource < Test::Unit::TestCase include PuppetTest::RailsTesting include PuppetTest::ResourceTesting def setup super railsinit end def teardown railsteardown super end def mktest_resource # We need a host for resources host = Puppet::Rails::Host.new(:name => "myhost") # Now build a resource resource = host.resources.create( :title => "/tmp/to_resource", :restype => "file", :exported => true) # Now add some params params.each do |param, value| pn = resource.param_names.find_or_create_by_name(param) pv = pn.param_values.find_or_create_by_value(value) resource.param_names << pn end host.save return resource end def params {"owner" => "root", "mode" => "644"} end # Create a resource param from a rails parameter def test_to_resource resource = mktest_resource # We need a scope interp, scope, source = mkclassframing # Find the new resource and include all it's parameters. resource = Puppet::Rails::Resource.find_by_id(resource.id, :include => [ :param_names, :param_values ]) # Now, try to convert our resource to a real resource res = nil assert_nothing_raised do res = resource.to_resource(scope) end assert_instance_of(Puppet::Parser::Resource, res) - assert_equal("root", res[:owner]) - assert_equal("644", res[:mode]) + assert_equal(["root"], res[:owner]) + assert_equal(["644"], res[:mode]) assert_equal("/tmp/to_resource", res.title) assert_equal(source, res.source) end def test_parameters resource = mktest_resource setparams = nil assert_nothing_raised do setparams = resource.parameters end assert_equal(params, setparams, "Did not get the right answer from #parameters") end # Make sure we can retrieve individual parameters by name. def test_parameter resource = mktest_resource params.each do |p,v| assert_equal(v, resource.parameter(p), "%s is not correct" % p) end end end else $stderr.puts "Install Rails for Rails and Caching tests" end # $Id$