diff --git a/lib/puppet.rb b/lib/puppet.rb index 1da2ca6dd..d68bd21b9 100644 --- a/lib/puppet.rb +++ b/lib/puppet.rb @@ -1,397 +1,400 @@ # see the bottom of the file for further inclusions require 'singleton' require 'facter' require 'puppet/error' require 'puppet/event-loop' require 'puppet/util' require 'puppet/log' require 'puppet/autoload' require 'puppet/config' require 'puppet/feature' require 'puppet/suidmanager' #------------------------------------------------------------ # the top-level module # # all this really does is dictate how the whole system behaves, through # preferences for things like debugging # # it's also a place to find top-level commands like 'debug' module Puppet PUPPETVERSION = '0.20.1' def Puppet.version return PUPPETVERSION end class << self # So we can monitor signals and such. include SignalObserver include Puppet::Util # To keep a copy of arguments. Set within Config#addargs, because I'm # lazy. attr_accessor :args attr_reader :features end def self.name unless defined? @name @name = $0.gsub(/.+#{File::SEPARATOR}/,'').sub(/\.rb$/, '') end return @name end # the hash that determines how our system behaves @@config = Puppet::Config.new # The services running in this process. @services ||= [] # define helper messages for each of the message levels Puppet::Log.eachlevel { |level| define_method(level,proc { |args| if args.is_a?(Array) args = args.join(" ") end Puppet::Log.create( :level => level, :message => args ) }) module_function level } # I keep wanting to use Puppet.error # XXX this isn't actually working right now alias :error :err # The feature collection @features = Puppet::Feature.new('puppet/feature') # Store a new default value. def self.setdefaults(section, hash) @@config.setdefaults(section, hash) end # Load all of the configuration parameters. require 'puppet/configuration' # configuration parameter access and stuff def self.[](param) case param when :debug: if Puppet::Log.level == :debug return true else return false end else return @@config[param] end end # configuration parameter access and stuff def self.[]=(param,value) @@config[param] = value end def self.clear @@config.clear end def self.debug=(value) if value Puppet::Log.level=(:debug) else Puppet::Log.level=(:notice) end end def self.config @@config end def self.genconfig if Puppet[:configprint] != "" val = Puppet[:configprint] if val == "all" hash = {} Puppet.config.each do |name, obj| val = obj.value case val when true, false, "": val = val.inspect end hash[name] = val end hash.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, val| puts "%s = %s" % [name, val] end elsif val =~ /,/ val.split(/\s*,\s*/).sort.each do |v| puts "%s = %s" % [v, Puppet[v]] end else puts Puppet[val] end exit(0) end if Puppet[:genconfig] puts Puppet.config.to_config exit(0) end end def self.genmanifest if Puppet[:genmanifest] puts Puppet.config.to_manifest exit(0) end end # Run all threads to their ends def self.join defined? @threads and @threads.each do |t| t.join end end # Create a new service that we're supposed to run def self.newservice(service) @services ||= [] @services << service end def self.newthread(&block) @threads ||= [] @threads << Thread.new do yield end end def self.newtimer(hash, &block) timer = nil threadlock(:timers) do @timers ||= [] timer = EventLoop::Timer.new(hash) @timers << timer if block_given? observe_signal(timer, :alarm, &block) end end # In case they need it for something else. timer end # Relaunch the executable. def self.restart command = $0 + " " + self.args.join(" ") Puppet.notice "Restarting with '%s'" % command Puppet.shutdown(false) Puppet::Log.reopen exec(command) end # Trap a couple of the main signals. This should probably be handled # in a way that anyone else can register callbacks for traps, but, eh. def self.settraps [:INT, :TERM].each do |signal| trap(signal) do Puppet.notice "Caught #{signal}; shutting down" Puppet.shutdown end end # Handle restarting. trap(:HUP) do if client = @services.find { |s| s.is_a? Puppet::Client::MasterClient } and client.running? client.restart else Puppet.restart end end # Provide a hook for running clients where appropriate trap(:USR1) do done = 0 Puppet.notice "Caught USR1; triggering client run" @services.find_all { |s| s.is_a? Puppet::Client }.each do |client| if client.respond_to? :running? if client.running? Puppet.info "Ignoring running %s" % client.class else done += 1 begin client.runnow rescue => detail Puppet.err "Could not run client: %s" % detail end end else Puppet.info "Ignoring %s; cannot test whether it is running" % client.class end end unless done > 0 Puppet.notice "No clients were run" end end trap(:USR2) do Puppet::Log.reopen end end # Shutdown our server process, meaning stop all services and all threads. # Optionally, exit. def self.shutdown(leave = true) Puppet.notice "Shutting down" # Unmonitor our timers defined? @timers and @timers.each do |timer| EventLoop.current.ignore_timer timer end # This seems to exit the process, although I can't find where it does # so. Leaving it out doesn't seem to hurt anything. #if EventLoop.current.running? # EventLoop.current.quit #end # Stop our services defined? @services and @services.each do |svc| begin timeout(20) do svc.shutdown end rescue TimeoutError Puppet.err "%s could not shut down within 20 seconds" % svc.class end end # And wait for them all to die, giving a decent amount of time defined? @threads and @threads.each do |thr| begin timeout(20) do thr.join end rescue TimeoutError # Just ignore this, since we can't intelligently provide a warning end end if leave exit(0) end end # Start all of our services and optionally our event loop, which blocks, # waiting for someone, somewhere, to generate events of some kind. def self.start(block = true) # Starting everything in its own thread, fwiw defined? @services and @services.dup.each do |svc| newthread do begin svc.start rescue => detail if Puppet[:trace] puts detail.backtrace end @services.delete svc Puppet.err "Could not start %s: %s" % [svc.class, detail] end end end # We need to give the services a chance to register their timers before # we try to start monitoring them. sleep 0.5 unless @services.length > 0 Puppet.notice "No remaining services; exiting" exit(1) end if defined? @timers and ! @timers.empty? @timers.each do |timer| EventLoop.current.monitor_timer timer end end if block EventLoop.current.run end end # Create the timer that our different objects (uh, mostly the client) # check. def self.timer unless defined? @timer #Puppet.info "Interval is %s" % Puppet[:runinterval] #@timer = EventLoop::Timer.new(:interval => Puppet[:runinterval]) @timer = EventLoop::Timer.new( :interval => Puppet[:runinterval], :tolerance => 1, :start? => true ) EventLoop.current.monitor_timer @timer end @timer end # XXX this should all be done using puppet objects, not using # normal mkdir def self.recmkdir(dir,mode = 0755) if FileTest.exist?(dir) return false else tmp = dir.sub(/^\//,'') path = [File::SEPARATOR] tmp.split(File::SEPARATOR).each { |dir| path.push dir if ! FileTest.exist?(File.join(path)) begin Dir.mkdir(File.join(path), mode) rescue Errno::EACCES => detail Puppet.err detail.to_s return false rescue => detail Puppet.err "Could not create %s: %s" % [path, detail.to_s] return false end elsif FileTest.directory?(File.join(path)) next else FileTest.exist?(File.join(path)) raise Puppet::Error, "Cannot create %s: basedir %s is a file" % [dir, File.join(path)] end } return true end end # Create a new type. Just proxy to the Type class. def self.newtype(name, parent = nil, &block) Puppet::Type.newtype(name, parent, &block) end # Retrieve a type by name. Just proxy to the Type class. def self.type(name) Puppet::Type.type(name) end # Only load all of the features once the Puppet module methods are defined. @features.load end require 'puppet/server' require 'puppet/type' require 'puppet/storage' +if Puppet[:storeconfigs] + require 'puppet/rails' +end # $Id$ diff --git a/lib/puppet/parser/collector.rb b/lib/puppet/parser/collector.rb index 71213ee6a..ea54c2dcf 100644 --- a/lib/puppet/parser/collector.rb +++ b/lib/puppet/parser/collector.rb @@ -1,153 +1,153 @@ # 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 require 'puppet/rails' Puppet.info "collecting %s" % @type # First get everything from the export table. Just reuse our # collect_virtual method but tell it to use 'exported? for the test. resources = collect_virtual(true) count = 0 # We're going to collect objects from rails, but we don't want any # objects from this host. host = Puppet::Rails::Host.find_by_name(@scope.host) args = {} if host args[:conditions] = "host_id != #{host.id}" else #Puppet.info "Host %s is uninitialized" % @scope.host end Puppet.info "collecting %s" % @type # 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::RailsResource.find_all_by_restype_and_exported(@type, true, + Puppet::Rails::Resource.find_all_by_type_and_exported(@type, true, args ).each do |obj| - if existing = @scope.findresource(obj.restype, obj.title) + if existing = @scope.findresource(obj.type, obj.title) # See if we exported it; if so, just move on if @scope.host == obj.host.name next else # Next see if we've already collected this resource if existing.rails_id == obj.id # This is the one we've already collected next else raise Puppet::ParseError, "Exported resource %s[%s] cannot override local resource" % - [obj.restype, obj.title] + [obj.type, obj.title] end end end count += 1 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 # uber-lame. scope.setresource(resource) rescue => detail if Puppet[:trace] puts detail.backtrace end raise end resource.exported = false resources << resource end end scope.debug("Collected %s %s resources in %.2f seconds" % [count, @type, 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 def collect_virtual_resources @resources.collect do |ref| if res = @scope.findresource(ref.to_s) res else raise Puppet::ParseError, "Could not find resource %s" % ref end end.each do |res| res.virtual = false end end # Collect just virtual objects, from our local configuration. def collect_virtual(exported = false) if exported method = :exported? else method = :virtual? end scope.resources.find_all do |resource| resource.type == @type and resource.send(method) and match?(resource) end end # Call the collection method, mark all of the returned objects as non-virtual, # and then delete this object from the list of collections to evaluate. def evaluate if self.resources return collect_resources end method = "collect_#{@form.to_s}" objects = send(method).each do |obj| obj.virtual = false end # And then remove us from the list of collections, since we've # now been evaluated. @scope.collections.delete(self) objects end def initialize(scope, type, 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 end # $Id$ diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index d2ec1b92c..ec3267b8b 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -1,803 +1,803 @@ # 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/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 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, :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? 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 @parser.addcontext("Class %s is already defined" % fqname) + " with parent %s" % [fqname, other.parentclass] 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 @parser.addcontext( "%s is already defined at line %s" % [fqname, other.line], other ) end ns, name = namesplit(fqname) args = { :type => name, :namespace => ns, :arguments => options[:arguments], :code => options[:code], :fqname => fqname } [:code, :arguments].each do |param| args[param] = options[param] if options[param] end @definetable[fqname] = @parser.ast AST::Component, args end # Create a new node. Nodes are special, because they're stored in a global # table, not according to namespaces. def newnode(names, options = {}) names = [names] unless names.instance_of?(Array) names.collect do |name| if other = @nodetable[name] @parser.error @parser.addcontext("Node %s is already defined" % [other.name], other) end name = name.to_s if name.is_a?(Symbol) args = { :name => name, } if options[:code] args[:code] = options[:code] end if options[:parent] args[:parentclass] = options[:parent] end @nodetable[name] = @parser.ast(AST::Node, args) @nodetable[name].fqname = name @nodetable[name] @nodetable[name].interp = self @nodetable[name] end end # Add a new file to be checked when we're checking to see if we should be # reparsed. def newfile(*files) files.each do |file| unless file.is_a? Puppet::LoadedFile file = Puppet::LoadedFile.new(file) end @files << file end end # 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 751b9c0c7..0802dd882 100644 --- a/lib/puppet/parser/resource.rb +++ b/lib/puppet/parser/resource.rb @@ -1,347 +1,348 @@ # 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 # if builtin? # devfail "Cannot evaluate a builtin type" # end # # unless klass = scope.finddefine(self.type) # self.fail "Cannot find definition %s" % self.type # end # # finish() # # scope.deleteresource(self) # # return klass.evaluate(:scope => scope, # :type => self.type, # :name => self.title, # :arguments => self.to_hash, # :scope => self.scope, # :exported => self.exported # ) ensure @evaluated = true end def exported=(value) if value @virtual = true @exported = value else @exported = value end end def evaluated? if defined? @evaluated and @evaluated true else false end end # Do any finishing work on this object, called before evaluation or # before storage/translation. def finish addoverrides() adddefaults() addmetaparams() end def initialize(options) options = symbolize_options(options) # Collect the options necessary to make the reference. refopts = [:type, :title].inject({}) do |hash, param| hash[param] = options[param] 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 # Store our object as a Rails object. We need the host object we're storing it # with. def store(host) args = {} - %w{type title tags file line exported}.each do |param| + #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) + #args[:type] = args[:type] + #args.delete(:type) # Let's see if the object exists - #if obj = host.rails_resources.find_by_type_and_title(self.type, self.title) - if obj = host.rails_resources.find_by_restype_and_title(self.type, self.title) + if obj = host.resources.find_by_type_and_title(self.type, self.title) # We exist args.each do |param, value| obj[param] = value end else # Else create it anew - obj = host.rails_resources.build(args) + obj = host.resources.build(args) end # Either way, now add our parameters @params.each do |name, param| param.store(obj) end return obj end - def tags - unless defined? @tags - @tags = scope.tags - @tags << self.type - end - @tags - end + #def tags + # unless defined? @tags + # @tags = scope.tags + # @tags << self.type + # end + # @tags + #end def to_hash @params.inject({}) do |hash, ary| param = ary[1] hash[param.name] = param.value hash end end def to_s self.ref end # Translate our object to a transportable object. def to_trans unless builtin? devfail "Tried to translate a non-builtin resource" end return nil if virtual? # Now convert to a transobject obj = Puppet::TransObject.new(@ref.title, @ref.type) to_hash.each do |p, v| if v.is_a?(Reference) v = v.to_ref elsif v.is_a?(Array) v = v.collect { |av| if av.is_a?(Reference) av = av.to_ref end av } end obj[p.to_s] = v end obj.file = self.file obj.line = self.line - obj.tags = self.tags + #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 023e4e75d..2e5d78034 100644 --- a/lib/puppet/parser/resource/param.rb +++ b/lib/puppet/parser/resource/param.rb @@ -1,43 +1,42 @@ # 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 store(resource) args = {} - [:name, :value, :line, :file].each do |var| + #[:name, :value, :line, :file].each do |var| + [:name, :value].each do |var| args[var] = self.send(var) end args[:name] = args[:name].to_s - if obj = resource.rails_parameters.find_by_name(self.name) - # We exist - args.each do |p, v| - obj[p] = v + args[:name].each do |name| + pn = resource.param_names.find_or_create_by_name(name) + args[:value].each do |value| + pv = pn.param_values.find_or_create_by_value(value) end - else - # Else create it anew - obj = resource.rails_parameters.build(args) end + obj = resource.param_names.find_by_name(args[:name], :include => :param_values) return obj end def to_s "%s => %s" % [self.name, self.value] end end # $Id$ diff --git a/lib/puppet/rails.rb b/lib/puppet/rails.rb index 77afe6be1..90330c2f2 100644 --- a/lib/puppet/rails.rb +++ b/lib/puppet/rails.rb @@ -1,112 +1,150 @@ # Load the appropriate libraries, or set a class indicating they aren't available require 'facter' require 'puppet' module Puppet::Rails Puppet.config.setdefaults(:puppetmaster, :dblocation => { :default => "$statedir/clientconfigs.sqlite3", :mode => 0600, :owner => "$user", :group => "$group", :desc => "The database cache for client configurations. Used for querying within the language." }, - :dbadapter => [ "sqlite3", "The type of database to use." ], + :dbadapter => [ "mysql", "The type of database to use." ], :dbname => [ "puppet", "The name of the database to use." ], - :dbserver => [ "puppet", "The database server for Client caching. Only + :dbserver => [ "localhost", "The database server for Client caching. Only used when networked databases are used."], :dbuser => [ "puppet", "The database user for Client caching. Only used when networked databases are used."], :dbpassword => [ "puppet", "The database password for Client caching. Only used when networked databases are used."], - :railslog => {:default => "$logdir/puppetrails.log", + :railslog => {:default => "/tmp/puppetpuppetrails.log", :mode => 0600, :owner => "$user", :group => "$group", :desc => "Where Rails-specific logs are sent" } ) - + def self.clear + end + + def self.teardown + unless defined? ActiveRecord::Base + raise Puppet::DevError, "No activerecord, cannot init Puppet::Rails" + end + Puppet.config.use(:puppetmaster) + + args = {:adapter => Puppet[:dbadapter]} + case Puppet[:dbadapter] + when "sqlite3": + args[:database] = Puppet[:dblocation] + + when "mysql": + args[:host] = Puppet[:dbserver] + args[:username] = Puppet[:dbuser] + args[:password] = Puppet[:dbpassword] + args[:database] = Puppet[:dbname] + end + + begin + ActiveRecord::Base.establish_connection(args) + rescue => detail + if Puppet[:trace] + puts detail.backtrace + end + raise Puppet::Error, "Could not connect to database: %s" % detail + end + + ActiveRecord::Base.connection.tables.each do |t| + ActiveRecord::Base.connection.drop_table t + end + @inited = false end # Set up our database connection. It'd be nice to have a "use" system # that could make callbacks. def self.init unless Puppet.features.rails? raise Puppet::DevError, "No activerecord, cannot init Puppet::Rails" end # This global init does not work for testing, because we remove # the state dir on every test. #unless (defined? @inited and @inited) or defined? Test::Unit::TestCase unless (defined? @inited and @inited) Puppet.config.use(:puppet) ActiveRecord::Base.logger = Logger.new(Puppet[:railslog]) args = {:adapter => Puppet[:dbadapter]} case Puppet[:dbadapter] when "sqlite3": args[:database] = Puppet[:dblocation] #unless FileTest.exists?(Puppet[:dblocation]) # Puppet.config.use(:puppet) # Puppet.config.write(:dblocation) do |f| # f.print "" # end #end when "mysql": args[:host] = Puppet[:dbserver] args[:username] = Puppet[:dbuser] args[:password] = Puppet[:dbpassword] args[:database] = Puppet[:dbname] end begin ActiveRecord::Base.establish_connection(args) rescue => detail if Puppet[:trace] puts detail.backtrace end raise Puppet::Error, "Could not connect to database: %s" % detail end - #puts "Database initialized: #{@inited.inspect} " + unless ActiveRecord::Base.connection.tables.include?("resources") + require 'puppet/rails/database/schema' + Puppet::Rails::Schema.init + #puts "Database initialized: #{@inited.inspect} " + end + @inited = true end ActiveRecord::Base.logger = Logger.new(Puppet[:railslog]) if Puppet[:dbadapter] == "sqlite3" and ! FileTest.exists?(Puppet[:dblocation]) dbdir = nil $:.each { |d| tmp = File.join(d, "puppet/rails/database") if FileTest.directory?(tmp) dbdir = tmp end } unless dbdir raise Puppet::Error, "Could not find Puppet::Rails database dir" end begin ActiveRecord::Migrator.migrate(dbdir) rescue => detail if Puppet[:trace] puts detail.backtrace end raise Puppet::Error, "Could not initialize database: %s" % detail end end Puppet.config.use(:puppetmaster) ActiveRecord::Base.logger = Logger.new(Puppet[:railslog]) end end if Puppet.features.rails? require 'puppet/rails/host' end # $Id$ diff --git a/lib/puppet/rails/database/schema.rb b/lib/puppet/rails/database/schema.rb new file mode 100644 index 000000000..9151bee46 --- /dev/null +++ b/lib/puppet/rails/database/schema.rb @@ -0,0 +1,64 @@ +class Puppet::Rails::Schema + +def self.init + ActiveRecord::Schema.define do + create_table :resources do |t| + t.column :title, :string, :null => false + t.column :type, :string + t.column :host_id, :integer + t.column :source_file_id, :integer + t.column :exported, :boolean + end + + create_table :source_files do |t| + t.column :filename, :string + t.column :path, :string + end + + create_table :puppet_classes do |t| + t.column :name, :string + t.column :host_id, :integer + t.column :source_file_id, :integer + end + + create_table :hosts do |t| + t.column :name, :string, :null => false + t.column :ip, :string + t.column :connect, :date + #Use updated_at to automatically add timestamp on save. + t.column :updated_at, :date + t.column :source_file_id, :integer + end + + create_table :fact_names do |t| + t.column :name, :string, :null => false + t.column :host_id, :integer, :null => false + end + + create_table :fact_values do |t| + t.column :value, :string, :null => false + t.column :fact_name_id, :integer, :null => false + end + + create_table :param_values do |t| + t.column :value, :string, :null => false + t.column :param_name_id, :integer, :null => false + end + + create_table :param_names do |t| + t.column :name, :string, :null => false + t.column :resource_id, :integer + end + + create_table :tags do |t| + t.column :name, :string + end + + create_table :taggings do |t| + t.column :tag_id, :integer + t.column :taggable_id, :integer + t.column :taggable_type, :string + end + end +end +end diff --git a/lib/puppet/rails/fact_name.rb b/lib/puppet/rails/fact_name.rb new file mode 100644 index 000000000..886618ecb --- /dev/null +++ b/lib/puppet/rails/fact_name.rb @@ -0,0 +1,3 @@ +class Puppet::Rails::FactName < ActiveRecord::Base + has_many :fact_values +end diff --git a/lib/puppet/rails/fact_value.rb b/lib/puppet/rails/fact_value.rb new file mode 100644 index 000000000..4da74b713 --- /dev/null +++ b/lib/puppet/rails/fact_value.rb @@ -0,0 +1,3 @@ +class Puppet::Rails::FactValue < ActiveRecord::Base + belongs_to :fact_names +end diff --git a/lib/puppet/rails/host.rb b/lib/puppet/rails/host.rb index ccda1af64..ab435caa4 100644 --- a/lib/puppet/rails/host.rb +++ b/lib/puppet/rails/host.rb @@ -1,87 +1,107 @@ -require 'puppet/rails/rails_resource' +require 'puppet/rails/resource' +require 'pp' -#RailsObject = Puppet::Rails::RailsObject class Puppet::Rails::Host < ActiveRecord::Base - serialize :facts, Hash - serialize :classes, Array + has_many :fact_values, :through => :fact_names + has_many :fact_names + belongs_to :puppet_classes + has_many :source_files + has_many :resources, :include => [ :param_names, :param_values ] - has_many :rails_resources, :dependent => :delete_all, - :include => :rails_parameters + acts_as_taggable + + def facts(name) + fv = self.fact_values.find(:first, :conditions => "fact_names.name = '#{name}'") + return fv.value + 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] raise ArgumentError, "You must specify the hostname for storage" end args = {} - [:name, :facts, :classes].each do |param| - if hash[param] - args[param] = hash[param] - end - end if hash[:facts].include?("ipaddress") args[:ip] = hash[:facts]["ipaddress"] end + host = self.find_or_create_by_name(hash[:facts]["hostname"], args) + + hash[:facts].each do |name, value| + fn = host.fact_names.find_or_create_by_name(name) + fv = fn.fact_values.find_or_create_by_value(value) + host.fact_names << fn + end unless hash[:resources] raise ArgumentError, "You must pass resources" end - if host = self.find_by_name(hash[:name]) - args.each do |param, value| - unless host[param] == args[param] - host[param] = args[param] - end - end - else - # Create it anew - host = self.new(args) + typenames = [] + Puppet::Type.loadall + Puppet::Type.eachtype do |type| + typenames << type.name.to_s end - hash[:resources].each do |res| - res.store(host) + hash[:resources].each do |resource| + resargs = resource.to_hash.stringify_keys + + + if typenames.include?(resource.type) + rtype = "Puppet#{resource.type.to_s.capitalize}" + end + + res = host.resources.find_or_create_by_title(resource[:title]) + res.type = rtype +f = File.new("/tmp/rtype.dump", "w+") +f.puts rtype + res.save + resargs.each do |param, value| + pn = res.param_names.find_or_create_by_name(param) + pv = pn.param_values.find_or_create_by_value(value) + res.param_names << pn + end end Puppet::Util.benchmark(:info, "Saved host to database") do host.save end 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 end robj = rails_objects.build(args) robj.addparams(params) if tobj.collectable robj.toggle(:collectable) end end end end # $Id$ diff --git a/lib/puppet/rails/param_name.rb b/lib/puppet/rails/param_name.rb new file mode 100644 index 000000000..928838f5c --- /dev/null +++ b/lib/puppet/rails/param_name.rb @@ -0,0 +1,13 @@ +class Puppet::Rails::ParamName < ActiveRecord::Base + has_many :param_values + belongs_to :resources + + def to_resourceparam(source) + hash = {} + hash[:name] = self.name.to_sym + hash[:source] = source + hash[:value] = self.param_values.find(:first).value + Puppet::Parser::Resource::Param.new hash + end +end + diff --git a/lib/puppet/rails/param_value.rb b/lib/puppet/rails/param_value.rb new file mode 100644 index 000000000..b01add4a7 --- /dev/null +++ b/lib/puppet/rails/param_value.rb @@ -0,0 +1,5 @@ +class Puppet::Rails::ParamValue < ActiveRecord::Base + belongs_to :param_names + +end + diff --git a/lib/puppet/rails/puppet_class.rb b/lib/puppet/rails/puppet_class.rb new file mode 100644 index 000000000..de54a31e9 --- /dev/null +++ b/lib/puppet/rails/puppet_class.rb @@ -0,0 +1,7 @@ +class Puppet::Rails::PuppetClass < ActiveRecord::Base + has_many :resources + has_many :source_files + has_many :hosts + + acts_as_taggable +end diff --git a/lib/puppet/rails/resource.rb b/lib/puppet/rails/resource.rb new file mode 100644 index 000000000..7cf6f6852 --- /dev/null +++ b/lib/puppet/rails/resource.rb @@ -0,0 +1,50 @@ +require 'puppet' +require 'puppet/rails/lib/init' +require 'puppet/rails/param_name' + +class Puppet::Rails::Resource < ActiveRecord::Base + has_many :param_values, :through => :param_names + has_many :param_names + has_many :source_files + belongs_to :hosts + + acts_as_taggable + + Puppet::Type.loadall + Puppet::Type.eachtype do |type| + klass = Class.new(Puppet::Rails::Resource) + Object.const_set("Puppet%s" % type.name.to_s.capitalize, klass) + end + + def parameters + hash = {} + self.param_values.find(:all).each do |pvalue| + pname = self.param_names.find(:first) + hash.store(pname.name.to_sym, pvalue.value) + end + return hash + end + + # Convert our object to a resource. Do not retain whether the object + # is collectable, though, since that would cause it to get stripped + # from the configuration. + def to_resource(scope) + hash = self.attributes + hash.delete("type") + 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[:type] = self.class.to_s.gsub(/Puppet/,'').downcase + 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 + + return obj + end +end diff --git a/lib/puppet/rails/source_file.rb b/lib/puppet/rails/source_file.rb new file mode 100644 index 000000000..d300ec74c --- /dev/null +++ b/lib/puppet/rails/source_file.rb @@ -0,0 +1,3 @@ +class Puppet::Rails::SourceFile < ActiveRecord::Base + has_many :hosts, :puppet_classes, :resources +end diff --git a/test/language/ast.rb b/test/language/ast.rb index d414a4f96..733ca4b36 100755 --- a/test/language/ast.rb +++ b/test/language/ast.rb @@ -1,535 +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" + :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::RailsResource.find(:all, - :include => :rails_parameters, + 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 - assert_equal("file", res.restype) + # 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("/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/collector.rb b/test/language/collector.rb index b3d948768..0292fac3e 100755 --- a/test/language/collector.rb +++ b/test/language/collector.rb @@ -1,293 +1,302 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet/rails' require 'puppettest' require 'puppettest/parsertesting' require 'puppettest/resourcetesting' require 'puppettest/railstesting' class TestCollector < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting include PuppetTest::ResourceTesting include PuppetTest::RailsTesting Parser = Puppet::Parser AST = Parser::AST def setup super Puppet[:trace] = false @interp, @scope, @source = mkclassframing end # Test just collecting a specific resource. This is used by the 'realize' # function, and it's much faster than iterating over all of the resources. def test_collect_resource # Make a couple of virtual resources one = mkresource(:type => "file", :title => "/tmp/virtual1", :virtual => true, :params => {:owner => "root"}) two = mkresource(:type => "file", :title => "/tmp/virtual2", :virtual => true, :params => {:owner => "root"}) @scope.setresource one @scope.setresource two # Now make a collector coll = nil assert_nothing_raised do coll = Puppet::Parser::Collector.new(@scope, "file", nil, nil, :virtual) end # Now set the resource in the collector assert_nothing_raised do coll.resources = one.ref end # Now run the collector assert_nothing_raised do coll.evaluate end # And make sure the resource is no longer virtual assert(! one.virtual?, "Resource is still virtual") # But the other still is assert(two.virtual?, "Resource got realized") end def test_virtual # Make a virtual resource virtual = mkresource(:type => "file", :title => "/tmp/virtual", :virtual => true, :params => {:owner => "root"}) @scope.setresource virtual # And a non-virtual real = mkresource(:type => "file", :title => "/tmp/real", :params => {:owner => "root"}) @scope.setresource real # Now make a collector coll = nil # Make a fake query code = proc do |res| true end assert_nothing_raised do coll = Puppet::Parser::Collector.new(@scope, "file", nil, code, :virtual) end # Set it in our scope @scope.newcollection(coll) # Make sure it's in the collections assert_equal([coll], @scope.collections) # And try to collect the virtual resources. ret = nil assert_nothing_raised do ret = coll.collect_virtual end assert_equal([virtual], ret) # Now make sure evaluate does the right thing. assert_nothing_raised do ret = coll.evaluate end # Make sure it got deleted from the collection list assert_equal([], @scope.collections) # And make sure our virtual object is no longer virtual assert(! virtual.virtual?, "Virtual object did not get realized") # Now make a new collector of a different type and make sure it # finds nothing. assert_nothing_raised do coll = Puppet::Parser::Collector.new(@scope, "exec", nil, nil, :virtual) end # Remark this as virtual virtual.virtual = true assert_nothing_raised do ret = coll.evaluate end assert_equal([], ret) end if defined? ActiveRecord::Base def test_collect_exported railsinit # make an exported resource exported = mkresource(:type => "file", :title => "/tmp/exported", :exported => true, :params => {:owner => "root"}) @scope.setresource exported assert(exported.exported?, "Object was not marked exported") assert(exported.virtual?, "Object was not marked virtual") # And a non-exported real = mkresource(:type => "file", :title => "/tmp/real", :params => {:owner => "root"}) @scope.setresource real # Now make a collector coll = nil assert_nothing_raised do coll = Puppet::Parser::Collector.new(@scope, "file", nil, nil, :exported) end # Set it in our scope @scope.newcollection(coll) # Make sure it's in the collections assert_equal([coll], @scope.collections) # And try to collect the virtual resources. ret = nil assert_nothing_raised do ret = coll.collect_exported end assert_equal([exported], ret) # Now make sure evaluate does the right thing. assert_nothing_raised do ret = coll.evaluate end # Make sure it got deleted from the collection list assert_equal([], @scope.collections) # And make sure our exported object is no longer exported assert(! exported.virtual?, "Virtual object did not get realized") # But it should still be marked exported. assert(exported.exported?, "Resource got un-exported") # Now make a new collector of a different type and make sure it # finds nothing. assert_nothing_raised do coll = Puppet::Parser::Collector.new(@scope, "exec", nil, nil, :exported) end # Remark this as virtual exported.virtual = true assert_nothing_raised do ret = coll.evaluate end assert_equal([], ret) # Now create a whole new scope and make sure we can actually retrieve # the resource from the database, not just from the scope. # First create a host object and store our resource in it. - host = Puppet::Rails::Host.find_or_create_by_name("localhost") + # Now collect our facts + facts = {} + Facter.each do |fact, value| facts[fact] = value end + + + # Now try storing our crap + resources = [] + resources << exported + host = Puppet::Rails::Host.store( + :resources => resources, + :facts => facts, + :name => facts["hostname"] + ) assert(host, "did not get rails host") - assert_nothing_raised("could not store resource") do - exported.store(host) - end host.save # And make sure it's in there - newres = Puppet::Rails::RailsResource.find_by_title("/tmp/exported") + newres = host.resources.find_by_title("/tmp/exported") assert(newres, "Did not find resource in db") interp, scope, source = mkclassframing # Now make a collector coll = nil assert_nothing_raised do coll = Puppet::Parser::Collector.new(scope, "file", nil, nil, :exported) end # Set it in our scope scope.newcollection(coll) # Make sure it's in the collections assert_equal([coll], scope.collections) # And try to collect the virtual resources. ret = nil assert_nothing_raised do ret = coll.collect_exported end assert_equal(["/tmp/exported"], ret.collect { |f| f.title }) # Make sure we can evaluate the same collection multiple times and # that later collections do nothing assert_nothing_raised do ret = coll.evaluate end # Make sure it got deleted from the collection list assert_equal([], scope.collections) end def test_collection_conflicts railsinit # First make a railshost we can conflict with host = Puppet::Rails::Host.new(:name => "myhost") - host.rails_resources.build(:title => "/tmp/conflicttest", :restype => "file", + host.resources.build(:title => "/tmp/conflicttest", :type => "PuppetFile", :exported => true) host.save # Now make a normal resource normal = mkresource(:type => "file", :title => "/tmp/conflicttest", :params => {:owner => "root"}) @scope.setresource normal # Now make a collector coll = nil assert_nothing_raised do coll = Puppet::Parser::Collector.new(@scope, "file", nil, nil, :exported) end # And try to collect the virtual resources. assert_raise(Puppet::ParseError) do ret = coll.collect_exported end end # Make sure we do not collect resources from the host we're on def test_no_resources_from_me railsinit # Make our configuration host = Puppet::Rails::Host.new(:name => "myhost") - host.rails_resources.build(:title => "/tmp/hosttest", :restype => "file", + host.resources.build(:title => "/tmp/hosttest", :type => "PuppetFile", :exported => true) host.save @scope.host = "myhost" # Now make a collector coll = nil assert_nothing_raised do coll = Puppet::Parser::Collector.new(@scope, "file", nil, nil, :exported) end # And make sure we get nada back ret = nil assert_nothing_raised do ret = coll.collect_exported end assert(ret.empty?, "Found exports from our own host") end end end # $Id$ diff --git a/test/language/interpreter.rb b/test/language/interpreter.rb index 2fe9cc601..3e542acb4 100755 --- a/test/language/interpreter.rb +++ b/test/language/interpreter.rb @@ -1,914 +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::RailsResource.find_by_restype_and_title("file", "/tmp/yay") + res = Puppet::Rails::Resource.find_by_type_and_title("PuppetFile", "/tmp/yay") assert(res, "Did not get resource from rails") - param = res.rails_parameters.find_by_name("owner") + param = res.param_names.find_by_name("owner", :include => :param_values) assert(param, "Did not find owner param") - assert_equal("root", param[:value]) + pvalue = param.param_values.find_by_value("root") + assert_equal("root", pvalue[:value]) end end end # $Id$ diff --git a/test/language/resource.rb b/test/language/resource.rb index f7786a774..eb8d2e9aa 100755 --- a/test/language/resource.rb +++ b/test/language/resource.rb @@ -1,403 +1,403 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppettest' require 'puppettest/resourcetesting' require 'puppettest/railstesting' class TestResource < Test::Unit::TestCase include PuppetTest include PuppetTest::ParserTesting include PuppetTest::ResourceTesting include PuppetTest::RailsTesting Parser = Puppet::Parser AST = Parser::AST def setup super Puppet[:trace] = false @interp, @scope, @source = mkclassframing end def test_initialize args = {:type => "resource", :title => "testing", :source => @source, :scope => @scope} # Check our arg requirements args.each do |name, value| try = args.dup try.delete(name) assert_raise(Puppet::DevError) do Parser::Resource.new(try) end end args[:params] = paramify @source, :one => "yay", :three => "rah" res = nil assert_nothing_raised do res = Parser::Resource.new(args) end # Make sure it got the parameters correctly. assert_equal("yay", res[:one]) assert_equal("rah", res[:three]) assert_equal({:one => "yay", :three => "rah"}, res.to_hash) end def test_override res = mkresource # Now verify we can't override with any random class assert_raise(Puppet::ParseError) do res.set paramify(@scope.findclass("other"), "one" => "boo").shift end # And that we can with a subclass assert_nothing_raised do res.set paramify(@scope.findclass("sub1"), "one" => "boo").shift end # And that a different subclass can override a different parameter assert_nothing_raised do res.set paramify(@scope.findclass("sub2"), "three" => "boo").shift end # But not the same one assert_raise(Puppet::ParseError) do res.set paramify(@scope.findclass("sub2"), "one" => "something").shift end end def test_merge # Start with the normal one res = mkresource # Now create a resource from a different scope other = mkresource :source => other, :params => {"one" => "boo"} # Make sure we can't merge it assert_raise(Puppet::ParseError) do res.merge(other) end # Make one from a subscope other = mkresource :source => "sub1", :params => {"one" => "boo"} # Make sure it merges assert_nothing_raised do res.merge(other) end assert_equal("boo", res["one"]) end def test_paramcheck # First make a builtin resource res = nil assert_nothing_raised do res = Parser::Resource.new :type => "file", :title => tempfile(), :source => @source, :scope => @scope end %w{path group source schedule subscribe}.each do |param| assert_nothing_raised("Param %s was considered invalid" % param) do res.paramcheck(param) end end %w{this bad noness}.each do |param| assert_raise(Puppet::ParseError, "%s was considered valid" % param) do res.paramcheck(param) end end # Now create a defined resource assert_nothing_raised do res = Parser::Resource.new :type => "resource", :title => "yay", :source => @source, :scope => @scope end %w{one two three schedule subscribe}.each do |param| assert_nothing_raised("Param %s was considered invalid" % param) do res.paramcheck(param) end end %w{this bad noness}.each do |param| assert_raise(Puppet::ParseError, "%s was considered valid" % param) do res.paramcheck(param) end end end def test_to_trans # First try translating a builtin resource res = Parser::Resource.new :type => "file", :title => "/tmp", :source => @source, :scope => @scope, :params => paramify(@source, :owner => "nobody", :mode => "644") obj = nil assert_nothing_raised do obj = res.to_trans end assert_instance_of(Puppet::TransObject, obj) assert_equal(obj.type, res.type) assert_equal(obj.name, res.title) # TransObjects use strings, resources use symbols hash = obj.to_hash.inject({}) { |h,a| h[a[0].intern] = a[1]; h } assert_equal(hash, res.to_hash) end def test_adddefaults # Set some defaults at the top level top = {:one => "fun", :two => "shoe"} @scope.setdefaults("resource", paramify(@source, top)) # Make a resource at that level res = Parser::Resource.new :type => "resource", :title => "yay", :source => @source, :scope => @scope # Add the defaults assert_nothing_raised do res.adddefaults end # And make sure we got them top.each do |p, v| assert_equal(v, res[p]) end # Now got a bit lower other = @scope.newscope # And create a resource lowerres = Parser::Resource.new :type => "resource", :title => "funtest", :source => @source, :scope => other assert_nothing_raised do lowerres.adddefaults end # And check top.each do |p, v| assert_equal(v, lowerres[p]) end # Now add some of our own defaults lower = {:one => "shun", :three => "free"} other.setdefaults("resource", paramify(@source, lower)) otherres = Parser::Resource.new :type => "resource", :title => "yaytest", :source => @source, :scope => other should = top.dup # Make sure the lower defaults beat the higher ones. lower.each do |p, v| should[p] = v end otherres.adddefaults should.each do |p,v| assert_equal(v, otherres[p]) end end def test_evaluate # Make a definition that we know will, um, do something @interp.newdefine "evaltest", :arguments => [%w{one}, ["two", stringobj("755")]], :code => resourcedef("file", "/tmp", "owner" => varref("one"), "mode" => varref("two")) res = Parser::Resource.new :type => "evaltest", :title => "yay", :source => @source, :scope => @scope, :params => paramify(@source, :one => "nobody") # Now try evaluating ret = nil assert_nothing_raised do ret = res.evaluate end # Make sure we can find our object now result = @scope.findresource("file[/tmp]") # Now make sure we got the code we expected. assert_instance_of(Puppet::Parser::Resource, result) assert_equal("file", result.type) assert_equal("/tmp", result.title) assert_equal("nobody", result["owner"]) assert_equal("755", result["mode"]) # And that we cannot find the old resource assert_nil(@scope.findresource("evaltest[yay]"), "Evaluated resource was not deleted") end def test_addoverrides # First create an override for an object that doesn't yet exist over1 = mkresource :source => "sub1", :params => {:one => "yay"} assert_nothing_raised do @scope.setoverride(over1) end assert(over1.override, "Override was not marked so") # Now make the resource res = mkresource :source => "base", :params => {:one => "rah", :three => "foo"} # And add it to our scope @scope.setresource(res) # And make sure over1 has not yet taken affect assert_equal("foo", res[:three], "Lost value") # Now add an immediately binding override over2 = mkresource :source => "sub1", :params => {:three => "yay"} assert_nothing_raised do @scope.setoverride(over2) end # And make sure it worked assert_equal("yay", res[:three], "Override 2 was ignored") # Now add our late-binding override assert_nothing_raised do res.addoverrides end # And make sure they're still around assert_equal("yay", res[:one], "Override 1 lost") assert_equal("yay", res[:three], "Override 2 lost") # And finally, make sure that there are no remaining overrides assert_nothing_raised do res.addoverrides end end def test_proxymethods res = Parser::Resource.new :type => "evaltest", :title => "yay", :source => @source, :scope => @scope assert_equal("evaltest", res.type) assert_equal("yay", res.title) assert_equal(false, res.builtin?) end def test_addmetaparams mkevaltest @interp res = Parser::Resource.new :type => "evaltest", :title => "yay", :source => @source, :scope => @scope, :params => paramify(@source, :tag => "yay") assert_nil(res[:schedule], "Got schedule already") assert_nothing_raised do res.addmetaparams end @scope.setvar("schedule", "daily") # This is so we can test that it won't override already-set metaparams @scope.setvar("tag", "funtest") assert_nothing_raised do res.addmetaparams end assert_equal("daily", res[:schedule], "Did not get metaparam") assert_equal("yay", res[:tag], "Overrode explicitly-set metaparam") assert_nil(res[:noop], "Got invalid metaparam") end def test_reference_conversion # First try it as a normal string ref = Parser::Resource::Reference.new(:type => "file", :title => "/tmp/ref1") # Now create an obj that uses it res = mkresource :type => "file", :title => "/tmp/resource", :params => {:require => ref} trans = nil assert_nothing_raised do trans = res.to_trans end assert_instance_of(Array, trans["require"]) assert_equal(["file", "/tmp/ref1"], trans["require"]) # Now try it when using an array of references. two = Parser::Resource::Reference.new(:type => "file", :title => "/tmp/ref2") res = mkresource :type => "file", :title => "/tmp/resource2", :params => {:require => [ref, two]} trans = nil assert_nothing_raised do trans = res.to_trans end assert_instance_of(Array, trans["require"][0]) trans["require"].each do |val| assert_instance_of(Array, val) assert_equal("file", val[0]) assert(val[1] =~ /\/tmp\/ref[0-9]/, "Was %s instead of the file name" % val[1]) end end # This is a bit of a weird one -- the user should not actually know # that components exist, so we want references to act like they're not # builtin def test_components_are_not_builtin ref = Parser::Resource::Reference.new(:type => "component", :title => "yay") assert_nil(ref.builtintype, "Component was considered builtin") end if defined? ActiveRecord::Base def test_store railsinit res = mkresource :type => "file", :title => "/tmp/testing", :source => @source, :scope => @scope, :params => {:owner => "root", :mode => "755"} # We also need a Rails Host to store under host = Puppet::Rails::Host.new(:name => Facter.hostname) obj = nil assert_nothing_raised do obj = res.store(host) end - assert_instance_of(Puppet::Rails::RailsResource, obj) + assert_instance_of(Puppet::Rails::Resource, obj) assert_nothing_raised do Puppet::Util.benchmark(:info, "Saved host") do host.save end end # Now make sure we can find it again assert_nothing_raised do - obj = Puppet::Rails::RailsResource.find_by_host_id_and_restype_and_title( - host.id, res.type, res.title + obj = Puppet::Rails::Resource.find_by_host_id_and_title( + host.id, res.title ) end - assert_instance_of(Puppet::Rails::RailsResource, obj) + assert_instance_of(Puppet::Rails::Resource, obj) # Make sure we get the parameters back - obj.rails_parameters.each do |param| + obj.parameters.each do |param| assert_equal(res[param[:name]], param[:value], "%s was different" % param[:name]) end end end end # $Id$ diff --git a/test/lib/puppettest/railstesting.rb b/test/lib/puppettest/railstesting.rb index 3a32d0c9e..3b65f5b34 100644 --- a/test/lib/puppettest/railstesting.rb +++ b/test/lib/puppettest/railstesting.rb @@ -1,34 +1,44 @@ module PuppetTest::RailsTesting Parser = Puppet::Parser AST = Puppet::Parser::AST include PuppetTest::ParserTesting def railsinit Puppet::Rails.init end + def railsteardown + Puppet::Rails.teardown + 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")) + #host = Puppet::Rails::Host.new(:name => Facter.value("hostname")) # Now build a resource - resource = host.rails_resources.build( - :title => title, :restype => type, - :exported => true - ) - - # Now add some params - params.each do |param, value| - resource.rails_parameters.build( - :name => param, :value => value + resources = [] + 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"] ) - end + } # Now save the whole thing host.save end end # $Id$ diff --git a/test/rails/rails.rb b/test/rails/rails.rb index 8be1bc66d..8c5e37287 100755 --- a/test/rails/rails.rb +++ b/test/rails/rails.rb @@ -1,89 +1,88 @@ #!/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 test_includerails assert_nothing_raised { require 'puppet/rails' } end # Don't do any tests w/out this class if defined? ActiveRecord::Base def test_hostcache @interp, @scope, @source = mkclassframing # First make some objects resources = [] 20.times { |i| resources << mkresource(:type => "file", :title => "/tmp/file#{i.to_s}", :params => {:owner => "user#{i}"}) } # Now collect our facts facts = Facter.to_hash assert_nothing_raised { + Puppet::Rails.teardown Puppet::Rails.init } # Now try storing our crap host = nil assert_nothing_raised { host = Puppet::Rails::Host.store( :resources => resources, :facts => facts, :name => facts["hostname"], :classes => ["one", "two::three", "four"] ) } assert(host, "Did not create host") host = nil assert_nothing_raised { host = Puppet::Rails::Host.find_by_name(facts["hostname"]) } assert(host, "Could not find host object") assert(host.rails_resources, "No objects on host") assert_equal(facts["hostname"], host.facts["hostname"], "Did not retrieve facts") count = 0 host.rails_resources.each do |resource| count += 1 i = nil if resource[:title] =~ /file([0-9]+)/ i = $1 else raise "Got weird resource %s" % resource.inspect end - - assert_equal("user#{i}", - resource.rails_parameters.find_by_name("owner")[:value]) + assert_equal("user#{i}", resource.parameters["owner"]) end assert_equal(20, count, "Did not get enough resources") end else $stderr.puts "Install Rails for Rails and Caching tests" end end # $Id$