diff options
author | Luke Kanies <luke@madstop.com> | 2007-08-16 21:21:40 -0500 |
---|---|---|
committer | Luke Kanies <luke@madstop.com> | 2007-08-16 21:21:40 -0500 |
commit | a846ea900f9fa7a2baaa4fbd0742f080e7fd7a04 (patch) | |
tree | 5e9d6127c2d99992cf604f3e59767c916b15c005 /lib/puppet/parser/interpreter.rb | |
parent | 1527f4a615f9c429e90becd90f9ed1e8c1e83249 (diff) | |
download | puppet-a846ea900f9fa7a2baaa4fbd0742f080e7fd7a04.tar.gz puppet-a846ea900f9fa7a2baaa4fbd0742f080e7fd7a04.tar.xz puppet-a846ea900f9fa7a2baaa4fbd0742f080e7fd7a04.zip |
The new parser configuration object works now,
but the rest of the compiling process is hosed
(although the parser itself should still be fine).
The configuration object is unifying a lot of work
that was scattered around either the interpreter or
the scopes, and it simplifies the whole system.
However, its new simplicity has made the complexity
of the rest of the system that much more apparent,
and I am resolved to fixing the system rather than
hacking it sufficiently to just make it work.
Diffstat (limited to 'lib/puppet/parser/interpreter.rb')
-rw-r--r-- | lib/puppet/parser/interpreter.rb | 401 |
1 files changed, 13 insertions, 388 deletions
diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index 18bf31087..f0b9d0179 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -5,268 +5,26 @@ require 'puppet/util/methodhelper' require 'puppet/parser/parser' require 'puppet/parser/scope' -# The interpreter'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. +# The interpreter is a very simple entry-point class that +# manages the existence of the parser (e.g., replacing it +# when files are reparsed). You can feed it a node and +# get the node's configuration back. class Puppet::Parser::Interpreter - class NodeDef - include Puppet::Util::MethodHelper - attr_accessor :name, :classes, :parameters, :source - - def evaluate(options) - begin - parameters.each do |param, value| - # Don't try to override facts with these parameters - options[:scope].setvar(param, value) unless options[:scope].lookupvar(param, false) != :undefined - end - - # Also, set the 'nodename', since it might not be obvious how the node was looked up - options[:scope].setvar("nodename", @name) unless options[:scope].lookupvar(@nodename, false) != :undefined - rescue => detail - raise Puppet::ParseError, "Could not set parameters for %s: %s" % [name, detail] - end - - # Then evaluate the classes. - begin - options[:scope].function_include(classes.find_all { |c| options[:scope].findclass(c) }) - rescue => detail - puts detail.backtrace - raise Puppet::ParseError, "Could not evaluate classes for %s: %s" % [name, detail] - end - end - - def initialize(args) - set_options(args) - - raise Puppet::DevError, "NodeDefs require names" unless self.name - - if self.classes.is_a?(String) - @classes = [@classes] - else - @classes ||= [] - end - @parameters ||= {} - end - - def safeevaluate(args) - evaluate(args) - end - end - include Puppet::Util 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 - - # Make sure we don't have any remaining collections that specifically - # look for resources, because we want to consider those to be - # parse errors. - def check_resource_collections(scope) - remaining = [] - scope.collections.each do |coll| - if r = coll.resources - if r.is_a?(Array) - remaining += r - else - remaining << r - end - end - end - unless remaining.empty? - raise Puppet::ParseError, "Failed to find virtual resources %s" % - remaining.join(', ') - end - end - - # Iteratively evaluate all of the objects. This finds all of the objects - # that represent definitions and evaluates the definitions appropriately. - # It also adds defaults and overrides as appropriate. - def evaliterate(scope) - count = 0 - loop do - count += 1 - done = true - # First perform collections, so we can collect defined types. - if coll = scope.collections and ! coll.empty? - exceptwrap do - coll.each do |c| - # Only keep the loop going if we actually successfully - # collected something. - if o = c.evaluate - done = false - end - end - end - end - - # Then evaluate any defined types. - if ary = scope.unevaluated - ary.each do |resource| - resource.evaluate - end - # If we evaluated, then loop through again. - done = false - end - break if done - - if count > 1000 - raise Puppet::ParseError, "Got 1000 class levels, which is unsupported" - end - 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 || facts["hostname"] || Facter.value(:hostname) - - classes = @classes.dup - - # Okay, first things first. Set our facts. - scope.setfacts(facts) - - # Everyone will always evaluate the top-level class, if there is one. - if klass = findclass("", "") - # Set the source, so objects can tell where they were defined. - scope.source = klass - klass.safeevaluate :scope => scope, :nosubscope => true - end - - # Next evaluate the node. We pass the facts so they can be used - # when building the list of names for which to search. - 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 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 => scope.host, - :facts => facts - } - unless scope.classlist.empty? - args[:classes] = scope.classlist - end - - storeconfigs(args) - end - - # Now, finally, convert our scope tree + resources into a tree of - # buckets and objects. - objects = scope.translate - - # Add the class list - unless scope.classlist.empty? - objects.classes = scope.classlist - end - - return objects - end - - # Fail if there any overrides left to perform. - def failonleftovers(scope) - overrides = scope.overrides - unless overrides.empty? - fail Puppet::ParseError, - "Could not find object(s) %s" % overrides.collect { |o| - o.ref - }.join(", ") - end - - # Now check that there aren't any extra resource collections. - check_resource_collections(scope) - end - # Create proxy methods, so the scopes can call the interpreter, since # they don't have access to the parser. def findclass(namespace, name) + raise "move findclass() out of the interpreter" @parser.findclass(namespace, name) end + def finddefine(namespace, name) + raise "move finddefine() out of the interpreter" @parser.finddefine(namespace, name) end @@ -284,33 +42,13 @@ class Puppet::Parser::Interpreter @usenodes = true end - - if Puppet[:ldapnodes] - # Nodes in the file override nodes in ldap. - @nodesource = :ldap - elsif Puppet[:external_nodes] != "none" - @nodesource = :external - else - # By default, we only search for parsed nodes. - @nodesource = :code - end + # By default, we only search for parsed nodes. + @nodesource = :code @setup = false - # 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] if Puppet.features.rails? @@ -329,6 +67,7 @@ class Puppet::Parser::Interpreter # Pass these methods through to the parser. [:newclass, :newdefine, :newnode].each do |name| define_method(name) do |*args| + raise("move %s out of the interpreter" % name) @parser.send(name, *args) end end @@ -336,6 +75,7 @@ class Puppet::Parser::Interpreter # Add a new file to be checked when we're checking to see if we should be # reparsed. def newfile(*files) + raise "who uses newfile?" files.each do |file| unless file.is_a? Puppet::Util::LoadedFile file = Puppet::Util::LoadedFile.new(file) @@ -344,90 +84,16 @@ class Puppet::Parser::Interpreter end end - # Search for our node in the various locations. - def nodesearch(*nodes) - nodes = nodes.collect { |n| n.to_s.downcase } - - method = "nodesearch_%s" % @nodesource - # 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) - if obj.is_a?(AST::Node) - nsource = obj.file - else - nsource = obj.source - end - Puppet.info "Found %s in %s" % [node, nsource] - return obj - 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) - @parser.nodes[name] - 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 - method = "setup_%s" % @nodesource.to_s - if respond_to? method - exceptwrap :type => Puppet::Error, - :message => "Could not set up node source %s" % @nodesource do - self.send(method) - end - end - end + def compile(node) 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 - unless Puppet.features.ldap? - 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 + return Puppet::Parser::Configuration.new(node).compile end private @@ -504,47 +170,6 @@ class Puppet::Parser::Interpreter Puppet.err "Could not parse; using old configuration: %s" % detail end end - - # Store the configs into the database. - def storeconfigs(hash) - unless Puppet.features.rails? - raise Puppet::Error, - "storeconfigs is enabled but rails is unavailable" - end - - unless ActiveRecord::Base.connected? - Puppet::Rails.connect - end - - # 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 - end end # $Id$ |