diff options
Diffstat (limited to 'lib/puppet/parser/interpreter.rb')
-rw-r--r-- | lib/puppet/parser/interpreter.rb | 1059 |
1 files changed, 685 insertions, 374 deletions
diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index 26bf8104e..111fe2ed3 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -3,463 +3,774 @@ # and calls out to other objects. require 'puppet' +require 'timeout' require 'puppet/parser/parser' require 'puppet/parser/scope' +class Puppet::Parser::Interpreter + include Puppet::Util + + Puppet.setdefaults("ldap", + :ldapnodes => [false, + "Whether to search for node configurations in LDAP."], + :ldapssl => [false, + "Whether SSL should be used when searching for nodes. + Defaults to false because SSL usually requires certificates + to be set up on the client side."], + :ldaptls => [false, + "Whether TLS should be used when searching for nodes. + Defaults to false because TLS usually requires certificates + to be set up on the client side."], + :ldapserver => ["ldap", + "The LDAP server. Only used if ``ldapnodes`` is enabled."], + :ldapport => [389, + "The LDAP port. Only used if ``ldapnodes`` is enabled."], + :ldapstring => ["(&(objectclass=puppetClient)(cn=%s))", + "The search string used to find an LDAP node."], + :ldapattrs => ["puppetclass", + "The LDAP attributes to use to define Puppet classes. Values + should be comma-separated."], + :ldapparentattr => ["parentnode", + "The attribute to use to define the parent node."], + :ldapuser => ["", + "The user to use to connect to LDAP. Must be specified as a + full DN."], + :ldappassword => ["", + "The password to use to connect to LDAP."], + :ldapbase => ["", + "The search base for LDAP searches. It's impossible to provide + a meaningful default here, although the LDAP libraries might + have one already set. Generally, it should be the 'ou=Hosts' + branch under your main directory."] + ) + + Puppet.setdefaults(:puppetmaster, + :storeconfigs => [false, + "Whether to store each client's configuration. This + requires ActiveRecord from Ruby on Rails."] + ) + + attr_accessor :usenodes + + class << self + attr_writer :ldap + end + # just shorten the constant path a bit, using what amounts to an alias + AST = Puppet::Parser::AST + + include Puppet::Util::Errors + + # Create an ldap connection. This is a class method so others can call + # it and use the same variables and such. + def self.ldap + unless defined? @ldap and @ldap + if Puppet[:ldapssl] + @ldap = LDAP::SSLConn.new(Puppet[:ldapserver], Puppet[:ldapport]) + elsif Puppet[:ldaptls] + @ldap = LDAP::SSLConn.new( + Puppet[:ldapserver], Puppet[:ldapport], true + ) + else + @ldap = LDAP::Conn.new(Puppet[:ldapserver], Puppet[:ldapport]) + end + @ldap.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) + @ldap.set_option(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_ON) + @ldap.simple_bind(Puppet[:ldapuser], Puppet[:ldappassword]) + end -module Puppet - module Parser - class Interpreter - include Puppet::Util - - Puppet.setdefaults("ldap", - :ldapnodes => [false, - "Whether to search for node configurations in LDAP."], - :ldapssl => [false, - "Whether SSL should be used when searching for nodes. - Defaults to false because SSL usually requires certificates - to be set up on the client side."], - :ldaptls => [false, - "Whether TLS should be used when searching for nodes. - Defaults to false because TLS usually requires certificates - to be set up on the client side."], - :ldapserver => ["ldap", - "The LDAP server. Only used if ``ldapnodes`` is enabled."], - :ldapport => [389, - "The LDAP port. Only used if ``ldapnodes`` is enabled."], - :ldapstring => ["(&(objectclass=puppetClient)(cn=%s))", - "The search string used to find an LDAP node."], - :ldapattrs => ["puppetclass", - "The LDAP attributes to use to define Puppet classes. Values - should be comma-separated."], - :ldapparentattr => ["parentnode", - "The attribute to use to define the parent node."], - :ldapuser => ["", - "The user to use to connect to LDAP. Must be specified as a - full DN."], - :ldappassword => ["", - "The password to use to connect to LDAP."], - :ldapbase => ["", - "The search base for LDAP searches. It's impossible to provide - a meaningful default here, although the LDAP libraries might - have one already set. Generally, it should be the 'ou=Hosts' - branch under your main directory."] - ) - - Puppet.setdefaults(:puppetmaster, - :storeconfigs => [false, - "Whether to store each client's configuration. This - requires ActiveRecord from Ruby on Rails."] - ) + return @ldap + end - attr_accessor :ast + def clear + initparsevars + end - class << self - attr_writer :ldap - end - # just shorten the constant path a bit, using what amounts to an alias - AST = Puppet::Parser::AST - - # Create an ldap connection. This is a class method so others can call - # it and use the same variables and such. - def self.ldap - unless defined? @ldap and @ldap - if Puppet[:ldapssl] - @ldap = LDAP::SSLConn.new(Puppet[:ldapserver], Puppet[:ldapport]) - elsif Puppet[:ldaptls] - @ldap = LDAP::SSLConn.new( - Puppet[:ldapserver], Puppet[:ldapport], true - ) - else - @ldap = LDAP::Conn.new(Puppet[:ldapserver], Puppet[:ldapport]) + # Iteratively evaluate all of the objects. This finds all fo the + # objects that represent definitions and evaluates the definitions appropriately. + # It also adds defaults and overrides as appropriate. + def evaliterate(scope) + count = 0 + begin + timeout 300 do + while ary = scope.unevaluated + ary.each do |resource| + resource.evaluate end - @ldap.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) - @ldap.set_option(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_ON) - @ldap.simple_bind(Puppet[:ldapuser], Puppet[:ldappassword]) end - - return @ldap end + rescue Timeout::Error + raise Puppet::DevError, "Got a timeout trying to evaluate all definitions" + end + end - # create our interpreter - def initialize(hash) - if @code = hash[:Code] - @file = nil # to avoid warnings - elsif ! @file = hash[:Manifest] - raise Puppet::DevError, "You must provide code or a manifest" - end + # Evaluate a specific node. + def evalnode(client, scope, facts) + return unless self.usenodes - if hash.include?(:UseNodes) - @usenodes = hash[:UseNodes] - else - @usenodes = true - end + unless client + raise Puppet::Error, + "Cannot evaluate nodes with a nil client" + end + names = [client] + + # Make sure both the fqdn and the short name of the + # host can be used in the manifest + if client =~ /\./ + names << client.sub(/\..+/,'') + else + names << "#{client}.#{facts['domain']}" + end - # By default, we only search the parse tree. - @nodesources = [] + if names.empty? + raise Puppet::Error, + "Cannot evaluate nodes with a nil client" + end - if Puppet[:ldapnodes] - @nodesources << :ldap - end + # Look up our node object. + if nodeclass = nodesearch(*names) + nodeclass.safeevaluate :scope => scope + else + raise Puppet::Error, "Could not find %s with names %s" % + [client, names.join(", ")] + end + end - if hash[:NodeSources] - hash[:NodeSources].each do |src| - if respond_to? "nodesearch_#{src.to_s}" - @nodesources << src.to_s.intern - else - Puppet.warning "Node source '#{src}' not supported" - end - end - end + # Evaluate all of the code we can find that's related to our client. + def evaluate(client, facts) - @setup = false + scope = Puppet::Parser::Scope.new(:interp => self) # no parent scope + scope.name = "top" + scope.type = "main" - # Set it to either the value or nil. This is currently only used - # by the cfengine module. - @classes = hash[:Classes] || [] + scope.host = facts["hostname"] || Facter.value("hostname") - @local = hash[:Local] || false + classes = @classes.dup - if hash.include?(:ForkSave) - @forksave = hash[:ForkSave] - else - # This is just too dangerous right now. Sorry, it's going - # to have to be slow. - @forksave = false - end + # Okay, first things first. Set our facts. + scope.setfacts(facts) - if Puppet[:storeconfigs] - Puppet::Rails.init - end + # Everyone will always evaluate the top-level class, if there is one. + if klass = findclass("", "") + # Set the source, so objects can tell where they were defined. + scope.source = klass + klass.safeevaluate :scope => scope, :nosubscope => true + end - @files = [] + # Next evaluate the node + evalnode(client, scope, facts) - # Create our parser object - parsefiles + # If we were passed any classes, evaluate those. + if classes + classes.each do |klass| + if klassobj = findclass("", klass) + klassobj.safeevaluate :scope => scope + end end + end - # Search for our node in the various locations. This only searches - # locations external to the files; the scope is responsible for - # searching the parse tree. - def nodesearch(*nodes) - # At this point, stop at the first source that defines - # the node - @nodesources.each do |source| - method = "nodesearch_%s" % source - parent = nil - nodeclasses = nil - if self.respond_to? method - nodes.each do |node| - parent, nodeclasses = self.send(method, node) - - if parent or (nodeclasses and !nodeclasses.empty?) - Puppet.info "Found %s in %s" % [node, source] - return parent, nodeclasses - else - # Look for a default node. - parent, nodeclasses = self.send(method, "default") - if parent or (nodeclasses and !nodeclasses.empty?) - Puppet.info "Found default node for %s in %s" % - [node, source] - return parent, nodeclasses - end - end - end - end - end + # That was the first pass evaluation. Now iteratively evaluate + # until we've gotten rid of all of everything or thrown an error. + evaliterate(scope) + + # Now make sure we fail if there's anything left to do + failonleftovers(scope) - return nil, nil + # Now perform the collections + scope.collections.each do |coll| + coll.evaluate + end + + # Now finish everything. This recursively calls finish on the + # contained scopes and resources. + scope.finish + + # Store everything. We need to do this before translation, because + # it operates on resources, not transobjects. + if Puppet[:storeconfigs] + args = { + :resources => scope.resources, + :name => client, + :facts => facts + } + unless scope.classlist.empty? + args[:classes] = scope.classlist end - # Find the ldap node and extra the info, returning just - # the critical data. - def nodesearch_ldap(node) - unless defined? @ldap and @ldap - setup_ldap() - unless @ldap - Puppet.info "Skipping ldap source; no ldap connection" - return nil, [] - end - end + storeconfigs(args) + end - if node =~ /\./ - node = node.sub(/\..+/, '') - end + # Now, finally, convert our scope tree + resources into a tree of + # buckets and objects. + objects = scope.translate - filter = Puppet[:ldapstring] - attrs = Puppet[:ldapattrs].split("\s*,\s*") - sattrs = attrs.dup - pattr = nil - if pattr = Puppet[:ldapparentattr] - if pattr == "" - pattr = nil - else - sattrs << pattr - end - end + # Add the class list + unless scope.classlist.empty? + objects.classes = scope.classlist + end - if filter =~ /%s/ - filter = filter.gsub(/%s/, node) - end + return objects + end - parent = nil - classes = [] - - found = false - count = 0 - begin - # We're always doing a sub here; oh well. - @ldap.search(Puppet[:ldapbase], 2, filter, sattrs) do |entry| - found = true - if pattr - if values = entry.vals(pattr) - if values.length > 1 - raise Puppet::Error, - "Node %s has more than one parent: %s" % - [node, values.inspect] - end - unless values.empty? - parent = values.shift - end - end - end + # Fail if there any overrides left to perform. + def failonleftovers(scope) + overrides = scope.overrides + if overrides.empty? + return nil + else + fail Puppet::ParseError, + "Could not find object(s) %s" % overrides.collect { |o| + o.ref + }.join(", ") + end + end - attrs.each { |attr| - if values = entry.vals(attr) - values.each do |v| classes << v end - end - } - end - rescue => detail - if count == 0 - # Try reconnecting to ldap - @ldap = nil - setup_ldap() - retry - else - raise Puppet::Error, "LDAP Search failed: %s" % detail - end - end + # Find a class definition, relative to the current namespace. + def findclass(namespace, name) + fqfind namespace, name, @classtable + end - classes.flatten! + # Find a component definition, relative to the current namespace. + def finddefine(namespace, name) + fqfind namespace, name, @definetable + end - return parent, classes - end + # The recursive method used to actually look these objects up. + def fqfind(namespace, name, table) + if name =~ /^::/ or namespace == "" + return table[name.sub(/^::/, '')] + end + ary = namespace.split("::") - def parsedate - parsefiles() - @parsedate + while ary.length > 0 + newname = (ary + [name]).join("::").sub(/^::/, '') + if obj = table[newname] + return obj end - # Add a new file to check for updateness. - def newfile(file) - unless @files.find { |f| f.file == file } - @files << Puppet::LoadedFile.new(file) - end - end + # Delete the second to last object, which reduces our namespace by one. + ary.pop + end - # evaluate our whole tree - def run(client, facts) - # We have to leave this for after initialization because there - # seems to be a problem keeping ldap open after a fork. - unless @setup - @nodesources.each { |source| - method = "setup_%s" % source.to_s - if respond_to? method - begin - self.send(method) - rescue => detail - raise Puppet::Error, - "Could not set up node source %s" % source - end - end - } - end - parsefiles() + # If we've gotten to this point without finding it, see if the name + # exists at the top namespace + if obj = table[name] + return obj + end + + return nil + end + + # Create a new node, just from a list of names, classes, and an optional parent. + def gennode(name, hash) + facts = hash[:facts] + classes = hash[:classes] + parent = hash[:parentnode] + arghash = { + :name => name, + :code => AST::ASTArray.new(:pin => "[]"), + :interp => self, + :fqname => name + } + classes = [classes] unless classes.is_a?(Array) + + classcode = @parser.ast(AST::ASTArray, :children => classes.collect do |klass| + @parser.ast(AST::FlatString, :value => klass) + end) + + # Now generate a function call. + code = @parser.ast(AST::Function, + :name => "include", + :arguments => classcode, + :ftype => :statement + ) + + if parent + arghash[:parentclass] = parent + end + + # Create the node + return @parser.ast(AST::Node, arghash) + end + + # create our interpreter + def initialize(hash) + if @code = hash[:Code] + @file = nil # to avoid warnings + elsif ! @file = hash[:Manifest] + devfail "You must provide code or a manifest" + end + + if hash.include?(:UseNodes) + @usenodes = hash[:UseNodes] + else + @usenodes = true + end + + # By default, we only search for parsed nodes. + @nodesources = [:code] - # Really, we should stick multiple names in here - # but for now just make a simple array - names = [client] + if Puppet[:ldapnodes] + # Nodes in the file override nodes in ldap. + @nodesources << :ldap + end - # Make sure both the fqdn and the short name of the - # host can be used in the manifest - if client =~ /\./ - names << client.sub(/\..+/,'') + if hash[:NodeSources] + unless hash[:NodeSources].is_a?(Array) + hash[:NodeSources] = [hash[:NodeSources]] + end + hash[:NodeSources].each do |src| + if respond_to? "nodesearch_#{src.to_s}" + @nodesources << src.to_s.intern else - names << "#{client}.#{facts['domain']}" + Puppet.warning "Node source '#{src}' not supported" end + end + end - scope = Puppet::Parser::Scope.new() # no parent scope - scope.name = "top" - scope.type = "puppet" - scope.interp = self + @setup = false - classes = @classes.dup + initparsevars() - args = {:ast => @ast, :facts => facts, :classes => classes} + # Set it to either the value or nil. This is currently only used + # by the cfengine module. + @classes = hash[:Classes] || [] - if @usenodes - unless client - raise Puppet::Error, - "Cannot evaluate nodes with a nil client" - end + @local = hash[:Local] || false - args[:names] = names + if hash.include?(:ForkSave) + @forksave = hash[:ForkSave] + else + # This is just too dangerous right now. Sorry, it's going + # to have to be slow. + @forksave = false + end + + # The class won't always be defined during testing. + if Puppet[:storeconfigs] and defined? ActiveRecord::Base + Puppet::Rails.init + end + + @files = [] + + # Create our parser object + parsefiles + end + + # Initialize or reset the variables related to parsing. + def initparsevars + @classtable = {} + @namespace = "main" + + @nodetable = {} + + @definetable = {} + end + + # Find the ldap node and extra the info, returning just + # the critical data. + def ldapsearch(node) + unless defined? @ldap and @ldap + setup_ldap() + unless @ldap + Puppet.info "Skipping ldap source; no ldap connection" + return nil, [] + end + end - parent, nodeclasses = nodesearch(*names) + if node =~ /\./ + node = node.sub(/\..+/, '') + end - args[:classes] += nodeclasses if nodeclasses + filter = Puppet[:ldapstring] + attrs = Puppet[:ldapattrs].split("\s*,\s*") + sattrs = attrs.dup + pattr = nil + if pattr = Puppet[:ldapparentattr] + if pattr == "" + pattr = nil + else + sattrs << pattr + end + end - args[:parentnode] = parent if parent + if filter =~ /%s/ + filter = filter.gsub(/%s/, node) + end - if nodeclasses or parent - args[:searched] = true + parent = nil + classes = [] + + found = false + count = 0 + begin + # We're always doing a sub here; oh well. + @ldap.search(Puppet[:ldapbase], 2, filter, sattrs) do |entry| + found = true + if pattr + if values = entry.vals(pattr) + if values.length > 1 + raise Puppet::Error, + "Node %s has more than one parent: %s" % + [node, values.inspect] + end + unless values.empty? + parent = values.shift + end end end - begin - objects = scope.evaluate(args) - rescue Puppet::DevError, Puppet::Error, Puppet::ParseError => except - raise - rescue => except - error = Puppet::DevError.new("%s: %s" % - [except.class, except.message]) - error.set_backtrace except.backtrace - raise error - end + attrs.each { |attr| + if values = entry.vals(attr) + values.each do |v| classes << v end + end + } + end + rescue => detail + if count == 0 + # Try reconnecting to ldap + @ldap = nil + setup_ldap() + retry + else + raise Puppet::Error, "LDAP Search failed: %s" % detail + end + end - if Puppet[:storeconfigs] - storeconfigs( - :objects => objects, - :host => client, - :facts => facts - ) - end + classes.flatten! + + return parent, classes + end + + # Split an fq name into a namespace and name + def namesplit(fullname) + ary = fullname.split("::") + n = ary.pop || "" + ns = ary.join("::") + return ns, n + end - return objects + # Create a new class, or merge with an existing class. + def newclass(fqname, options = {}) + if @definetable.include?(fqname) + raise Puppet::ParseError, "Cannot redefine class %s as a definition" % + fqname + end + code = options[:code] + parent = options[:parent] + + # If the class is already defined, then add code to it. + if other = @classtable[fqname] + # Make sure the parents match + if parent and other.parentclass and (parent != other.parentclass) + @parser.error @parser.addcontext("Class %s is already defined" % fqname) + + " with parent %s" % [fqname, other.parentclass] end - # Connect to the LDAP Server - def setup_ldap - self.class.ldap = nil - begin - require 'ldap' - rescue LoadError - Puppet.notice( - "Could not set up LDAP Connection: Missing ruby/ldap libraries" - ) - @ldap = nil - return + # This might be dangerous... + if parent and ! other.parentclass + other.parentclass = parent + end + + # This might just be an empty, stub class. + if code + tmp = fqname + if tmp == "" + tmp = "main" end - begin - @ldap = self.class.ldap() - rescue => detail - raise Puppet::Error, "Could not connect to LDAP: %s" % detail + + Puppet.debug @parser.addcontext("Adding code to %s" % tmp) + # Else, add our code to it. + if other.code and code + other.code.children += code.children + else + other.code ||= code end end + else + # Define it anew. + ns, name = namesplit(fqname) + args = {:type => name, :namespace => ns, :fqname => fqname, :interp => self} + args[:code] = code if code + args[:parentclass] = parent if parent + @classtable[fqname] = @parser.ast AST::HostClass, args + end - def scope - return @scope - end + return @classtable[fqname] + end - private + # Create a new definition. + def newdefine(fqname, options = {}) + if @classtable.include?(fqname) + raise Puppet::ParseError, "Cannot redefine class %s as a definition" % + fqname + end + # Make sure our definition doesn't already exist + if other = @definetable[fqname] + @parser.error @parser.addcontext( + "%s is already defined at line %s" % [fqname, other.line], + other + ) + end - # Check whether any of our files have changed. - def checkfiles - if @files.find { |f| f.changed? } - @parsedate = Time.now.to_i - end + ns, name = namesplit(fqname) + args = { + :type => name, + :namespace => ns, + :arguments => options[:arguments], + :code => options[:code], + :fqname => fqname + } + + [:code, :arguments].each do |param| + args[param] = options[param] if options[param] + end + + @definetable[fqname] = @parser.ast AST::Component, args + end + + # Create a new node. Nodes are special, because they're stored in a global + # table, not according to namespaces. + def newnode(names, options = {}) + names = [names] unless names.instance_of?(Array) + names.collect do |name| + if other = @nodetable[name] + @parser.error @parser.addcontext("Node %s is already defined" % [other.name], other) + end + name = name.to_s if name.is_a?(Symbol) + args = { + :name => name, + } + if options[:code] + args[:code] = options[:code] + end + if options[:parent] + args[:parentclass] = options[:parent] end + @nodetable[name] = @parser.ast(AST::Node, args) + @nodetable[name].fqname = name + @nodetable[name] + @nodetable[name].interp = self + @nodetable[name] + end + end + + # Add a new file to be checked when we're checking to see if we should be + # reparsed. + def newfile(*files) + files.each do |file| + unless file.is_a? Puppet::LoadedFile + file = Puppet::LoadedFile.new(file) + end + @files << file + end + end - # Parse the files, generating our parse tree. This automatically - # reparses only if files are updated, so it's safe to call multiple - # times. - def parsefiles - # First check whether there are updates to any non-puppet files - # like templates. If we need to reparse, this will get quashed, - # but it needs to be done first in case there's no reparse - # but there are other file changes. - checkfiles() - - # Check if the parser should reparse. - if @file - if defined? @parser - if stamp = @parser.reparse? - Puppet.notice "Reloading files" - else - return false + # Search for our node in the various locations. + def nodesearch(*nodes) + # At this point, stop at the first source that defines + # the node + @nodesources.each do |source| + method = "nodesearch_%s" % source + if self.respond_to? method + # Do an inverse sort on the length, so the longest match always + # wins + nodes.sort { |a,b| b.length <=> a.length }.each do |node| + node = node.to_s if node.is_a?(Symbol) + if obj = self.send(method, node) + nsource = obj.file || source + Puppet.info "Found %s in %s" % [node, nsource] + return obj + else + # Look for a default node. + if defobj = self.send(method, "default") + Puppet.info "Found default node for %s in %s" % + [node, source] + return defobj end end + end + end + end - unless FileTest.exists?(@file) - if @ast - return - else - raise Puppet::Error, "Manifest %s must exist" % @file - end + return nil + end + + # See if our node was defined in the code. + def nodesearch_code(name) + @nodetable[name] + end + + # Look for our node in ldap. + def nodesearch_ldap(node) + parent, classes = ldapsearch(node) + if parent or classes + args = {} + args[:classes] = classes if classes and ! classes.empty? + args[:parentnode] = parent if parent + return gennode(node, args) + else + return nil + end + end + + def parsedate + parsefiles() + @parsedate + end + + # evaluate our whole tree + def run(client, facts) + # We have to leave this for after initialization because there + # seems to be a problem keeping ldap open after a fork. + unless @setup + @nodesources.each { |source| + method = "setup_%s" % source.to_s + if respond_to? method + exceptwrap :type => Puppet::Error, + :message => "Could not set up node source %s" % source do + self.send(method) end end + } + end + parsefiles() + + # Evaluate all of the appropriate code. + objects = evaluate(client, facts) + + # And return it all. + return objects + end + + # Connect to the LDAP Server + def setup_ldap + self.class.ldap = nil + begin + require 'ldap' + rescue LoadError + Puppet.notice( + "Could not set up LDAP Connection: Missing ruby/ldap libraries" + ) + @ldap = nil + return + end + begin + @ldap = self.class.ldap() + rescue => detail + raise Puppet::Error, "Could not connect to LDAP: %s" % detail + end + end + + def scope + return @scope + end + + # Iteratively make sure that every object in the scope tree is translated. + def translate(scope) + end + + private - # should i be creating a new parser each time...? - @parser = Puppet::Parser::Parser.new() - if @code - @parser.string = @code + # Check whether any of our files have changed. + def checkfiles + if @files.find { |f| f.changed? } + @parsedate = Time.now.to_i + end + end + + # Parse the files, generating our parse tree. This automatically + # reparses only if files are updated, so it's safe to call multiple + # times. + def parsefiles + # First check whether there are updates to any non-puppet files + # like templates. If we need to reparse, this will get quashed, + # but it needs to be done first in case there's no reparse + # but there are other file changes. + checkfiles() + + # Check if the parser should reparse. + if @file + if defined? @parser + if stamp = @parser.reparse? + Puppet.notice "Reloading files" else - @parser.file = @file - # Mark when we parsed, so we can check freshness - @parsedate = File.stat(@file).ctime.to_i + return false end + end - if @local - @ast = @parser.parse + unless FileTest.exists?(@file) + # If we've already parsed, then we're ok. + if findclass("", "") + return else - benchmark(:info, "Parsed manifest") do - @ast = @parser.parse - end + raise Puppet::Error, "Manifest %s must exist" % @file end - @parsedate = Time.now.to_i end + end - # Store the configs into the database. - def storeconfigs(hash) - unless defined? ActiveRecord - require 'puppet/rails' - unless defined? ActiveRecord - raise LoadError, - "storeconfigs is enabled but rails is unavailable" - end - end + # Reset our parse tables. + clear() + + # Create a new parser, just to keep things fresh. + @parser = Puppet::Parser::Parser.new(self) + if @code + @parser.string = @code + else + @parser.file = @file + # Mark when we parsed, so we can check freshness + @parsedate = File.stat(@file).ctime.to_i + end - Puppet::Rails.init - - # Fork the storage, since we don't need the client waiting - # on that. How do I avoid this duplication? - if @forksave - fork { - # We store all of the objects, even the collectable ones - benchmark(:info, "Stored configuration for #{hash[:client]}") do - # Try to batch things a bit, by putting them into - # a transaction - Puppet::Rails::Host.transaction do - Puppet::Rails::Host.store(hash) - end - end - } - else - # We store all of the objects, even the collectable ones - benchmark(:info, "Stored configuration for #{hash[:client]}") do - Puppet::Rails::Host.transaction do - Puppet::Rails::Host.store(hash) - end + # Parsing stores all classes and defines and such in their + # various tables, so we don't worry about the return. + if @local + @parser.parse + else + benchmark(:info, "Parsed manifest") do + @parser.parse + end + end + @parsedate = Time.now.to_i + end + + # Store the configs into the database. + def storeconfigs(hash) + unless defined? ActiveRecord + require 'puppet/rails' + unless defined? ActiveRecord + raise LoadError, + "storeconfigs is enabled but rails is unavailable" + end + end + + Puppet::Rails.init + + # Fork the storage, since we don't need the client waiting + # on that. How do I avoid this duplication? + if @forksave + fork { + # We store all of the objects, even the collectable ones + benchmark(:info, "Stored configuration for #{hash[:name]}") do + # Try to batch things a bit, by putting them into + # a transaction + Puppet::Rails::Host.transaction do + Puppet::Rails::Host.store(hash) end end - - # Now that we've stored everything, we need to strip out - # the collectable objects so that they are not sent on - # to the host - hash[:objects].collectstrip! + } + else + # We store all of the objects, even the collectable ones + benchmark(:info, "Stored configuration for #{hash[:name]}") do + Puppet::Rails::Host.transaction do + Puppet::Rails::Host.store(hash) + end end end + + # Now that we've stored everything, we need to strip out + # the collectable objects so that they are not sent on + # to the host + #hash[:objects].collectstrip! end end |