summaryrefslogtreecommitdiffstats
path: root/lib/puppet/parser
diff options
context:
space:
mode:
authorLuke Kanies <luke@madstop.com>2007-08-14 00:09:49 -0500
committerLuke Kanies <luke@madstop.com>2007-08-14 00:09:49 -0500
commitaab419b8c1ad84e51c6f58839290bbe5d1e7b28b (patch)
tree2447b704e0b601ffe10562d9eb83e6c9280366ba /lib/puppet/parser
parentab42534ae243c24c8c702e38195a954ab52eaed9 (diff)
downloadpuppet-aab419b8c1ad84e51c6f58839290bbe5d1e7b28b.tar.gz
puppet-aab419b8c1ad84e51c6f58839290bbe5d1e7b28b.tar.xz
puppet-aab419b8c1ad84e51c6f58839290bbe5d1e7b28b.zip
An intermediate commit in the work towards adding multi-environment support.
This has required splitting the interpreter up considerably, which is much cleaner but is a large project. There is now a 'nodes' handler, but it is currently non-functional, although all the support structure is there. It just needs to have the individual methods fleshed out, and it needs to be connected to the 'facts' handler.
Diffstat (limited to 'lib/puppet/parser')
-rw-r--r--lib/puppet/parser/ast/hostclass.rb7
-rw-r--r--lib/puppet/parser/configuration.rb133
-rw-r--r--lib/puppet/parser/interpreter.rb163
-rw-r--r--lib/puppet/parser/node.rb133
-rw-r--r--lib/puppet/parser/scope.rb222
5 files changed, 311 insertions, 347 deletions
diff --git a/lib/puppet/parser/ast/hostclass.rb b/lib/puppet/parser/ast/hostclass.rb
index 642645824..d1ce370da 100644
--- a/lib/puppet/parser/ast/hostclass.rb
+++ b/lib/puppet/parser/ast/hostclass.rb
@@ -24,8 +24,11 @@ class Puppet::Parser::AST
scope = hash[:scope]
args = hash[:arguments]
- # Verify that we haven't already been evaluated
- if scope.class_scope(self)
+ # Verify that we haven't already been evaluated, and if we have been evaluated,
+ # make sure that we match the class.
+ if existing_scope = scope.class_scope(self)
+ raise "Fix this portion of the code -- check that the scopes match classes"
+ #if existing_scope.source.object_id == self.object_id
Puppet.debug "%s class already evaluated" % @type
return nil
end
diff --git a/lib/puppet/parser/configuration.rb b/lib/puppet/parser/configuration.rb
new file mode 100644
index 000000000..c7979e51f
--- /dev/null
+++ b/lib/puppet/parser/configuration.rb
@@ -0,0 +1,133 @@
+# Created by Luke A. Kanies on 2007-08-13.
+# Copyright (c) 2007. All rights reserved.
+
+require 'puppet/external/gratr/digraph'
+require 'puppet/external/gratr/import'
+require 'puppet/external/gratr/dot'
+
+# Maintain a graph of scopes, along with a bunch of data
+# about the individual configuration we're compiling.
+class Puppet::Parser::Configuration
+ attr_reader :topscope, :interpreter, :host, :facts
+
+ # Add a collection to the global list.
+ def add_collection(coll)
+ @collections << coll
+ end
+
+ # Store the fact that we've evaluated a class, and store a reference to
+ # the scope in which it was evaluated, so that we can look it up later.
+ def class_set(name, scope)
+ @class_scopes[name] = scope
+ end
+
+ # Return the scope associated with a class. This is just here so
+ # that subclasses can set their parent scopes to be the scope of
+ # their parent class, and it's also used when looking up qualified
+ # variables.
+ def class_scope(klass)
+ # They might pass in either the class or class name
+ if klass.respond_to?(:classname)
+ @class_scopes[klass.classname]
+ else
+ @class_scopes[klass]
+ end
+ end
+
+ # Return a list of all of the defined classes.
+ def classlist
+ return @class_scopes.keys.reject { |k| k == "" }
+ end
+
+ # Should the scopes behave declaratively?
+ def declarative?
+ true
+ end
+
+ # Set up our configuration. We require an interpreter
+ # and a host name, and we normally are passed facts, too.
+ def initialize(options)
+ @interpreter = options[:interpreter] or
+ raise ArgumentError, "You must pass an interpreter to the configuration"
+ @facts = options[:facts] || {}
+ @host = options[:host] or
+ raise ArgumentError, "You must pass a host name to the configuration"
+
+ # Call the setup methods from the base class.
+ super()
+
+ initvars()
+ end
+
+ # Create a new scope, with either a specified parent scope or
+ # using the top scope. Adds an edge between the scope and
+ # its parent to the graph.
+ def newscope(parent = nil)
+ parent ||= @topscope
+ scope = Puppet::Parser::Scope.new(:configuration => self)
+ @graph.add_edge!(parent, scope)
+ scope
+ end
+
+ # Find the parent of a given scope. Assumes scopes only ever have
+ # one in edge, which will always be true.
+ def parent(scope)
+ if ary = @graph.adjacent(scope, :direction => :in) and ary.length > 0
+ ary[0]
+ else
+ nil
+ end
+ end
+
+ # Return an array of all of the unevaluated objects
+ def unevaluated
+ ary = @definedtable.find_all do |name, object|
+ ! object.builtin? and ! object.evaluated?
+ end.collect { |name, object| object }
+
+ if ary.empty?
+ return nil
+ else
+ return ary
+ end
+ end
+
+ private
+
+ # Set up all of our internal variables.
+ def initvars
+ # The table for storing class singletons. This will only actually
+ # be used by top scopes and node scopes.
+ @class_scopes = {}
+
+ # The table for all defined resources.
+ @resource_table = {}
+
+ # The list of objects that will available for export.
+ @exported_resources = {}
+
+ # The list of overrides. This is used to cache overrides on objects
+ # that don't exist yet. We store an array of each override.
+ @resource_overrides = Hash.new do |overs, ref|
+ overs[ref] = []
+ end
+
+ # The list of collections that have been created. This is a global list,
+ # but they each refer back to the scope that created them.
+ @collections = []
+
+ # Create our initial scope, our scope graph, and add the initial scope to the graph.
+ @topscope = Puppet::Parser::Scope.new(:configuration => self, :type => "main", :name => "top")
+ @graph = GRATR::Digraph.new
+ @graph.add_vertex!(@topscope)
+ end
+
+ # Return the list of remaining overrides.
+ def overrides
+ @resource_overrides.values.flatten
+ end
+
+ def resources
+ @resourcetable
+ end
+end
diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb
index 3ba9c0c7a..18bf31087 100644
--- a/lib/puppet/parser/interpreter.rb
+++ b/lib/puppet/parser/interpreter.rb
@@ -178,7 +178,6 @@ class Puppet::Parser::Interpreter
# 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"
@@ -327,101 +326,6 @@ class Puppet::Parser::Interpreter
parsefiles
end
- # Find the ldap node, return the class list and parent node specially,
- # and everything else in a parameter hash.
- def ldapsearch(node)
- unless defined? @ldap and @ldap
- setup_ldap()
- unless @ldap
- Puppet.info "Skipping ldap source; no ldap connection"
- return nil
- end
- end
-
- filter = Puppet[:ldapstring]
- classattrs = Puppet[:ldapclassattrs].split("\s*,\s*")
- if Puppet[:ldapattrs] == "all"
- # A nil value here causes all attributes to be returned.
- search_attrs = nil
- else
- search_attrs = classattrs + Puppet[:ldapattrs].split("\s*,\s*")
- end
- pattr = nil
- if pattr = Puppet[:ldapparentattr]
- if pattr == ""
- pattr = nil
- else
- search_attrs << pattr unless search_attrs.nil?
- end
- end
-
- if filter =~ /%s/
- filter = filter.gsub(/%s/, node)
- end
-
- parent = nil
- classes = []
- parameters = nil
-
- found = false
- count = 0
-
- begin
- # We're always doing a sub here; oh well.
- @ldap.search(Puppet[:ldapbase], 2, filter, search_attrs) 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
-
- classattrs.each { |attr|
- if values = entry.vals(attr)
- values.each do |v| classes << v end
- end
- }
-
- parameters = entry.to_hash.inject({}) do |hash, ary|
- if ary[1].length == 1
- hash[ary[0]] = ary[1].shift
- else
- hash[ary[0]] = ary[1]
- end
- hash
- 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
-
- if parent or classes or parameters
- return parent, classes, parameters
- else
- return nil
- end
- end
-
# Pass these methods through to the parser.
[:newclass, :newdefine, :newnode].each do |name|
define_method(name) do |*args|
@@ -476,73 +380,6 @@ class Puppet::Parser::Interpreter
def nodesearch_code(name)
@parser.nodes[name]
end
-
- # Look for external node definitions.
- def nodesearch_external(name)
- return nil unless Puppet[:external_nodes] != "none"
-
- # This is a very cheap way to do this, since it will break on
- # commands that have spaces in the arguments. But it's good
- # enough for most cases.
- external_node_command = Puppet[:external_nodes].split
- external_node_command << name
- begin
- output = Puppet::Util.execute(external_node_command)
- rescue Puppet::ExecutionFailure => detail
- if $?.exitstatus == 1
- return nil
- else
- Puppet.err "Could not retrieve external node information for %s: %s" % [name, detail]
- end
- return nil
- end
-
- if output =~ /\A\s*\Z/ # all whitespace
- Puppet.debug "Empty response for %s from external node source" % name
- return nil
- end
-
- begin
- result = YAML.load(output).inject({}) { |hash, data| hash[symbolize(data[0])] = data[1]; hash }
- rescue => detail
- raise Puppet::Error, "Could not load external node results for %s: %s" % [name, detail]
- end
-
- node_args = {:source => "external node source", :name => name}
- set = false
- [:parameters, :classes].each do |param|
- if value = result[param]
- node_args[param] = value
- set = true
- end
- end
-
- if set
- return NodeDef.new(node_args)
- else
- return nil
- end
- end
-
- # Look for our node in ldap.
- def nodesearch_ldap(node)
- unless ary = ldapsearch(node)
- return nil
- end
- parent, classes, parameters = ary
-
- while parent
- parent, tmpclasses, tmpparams = ldapsearch(parent)
- classes += tmpclasses if tmpclasses
- tmpparams.each do |param, value|
- # Specifically test for whether it's set, so false values are handled
- # correctly.
- parameters[param] = value unless parameters.include?(param)
- end
- end
-
- return NodeDef.new(:name => node, :classes => classes, :source => "ldap", :parameters => parameters)
- end
def parsedate
parsefiles()
diff --git a/lib/puppet/parser/node.rb b/lib/puppet/parser/node.rb
new file mode 100644
index 000000000..c7979e51f
--- /dev/null
+++ b/lib/puppet/parser/node.rb
@@ -0,0 +1,133 @@
+# Created by Luke A. Kanies on 2007-08-13.
+# Copyright (c) 2007. All rights reserved.
+
+require 'puppet/external/gratr/digraph'
+require 'puppet/external/gratr/import'
+require 'puppet/external/gratr/dot'
+
+# Maintain a graph of scopes, along with a bunch of data
+# about the individual configuration we're compiling.
+class Puppet::Parser::Configuration
+ attr_reader :topscope, :interpreter, :host, :facts
+
+ # Add a collection to the global list.
+ def add_collection(coll)
+ @collections << coll
+ end
+
+ # Store the fact that we've evaluated a class, and store a reference to
+ # the scope in which it was evaluated, so that we can look it up later.
+ def class_set(name, scope)
+ @class_scopes[name] = scope
+ end
+
+ # Return the scope associated with a class. This is just here so
+ # that subclasses can set their parent scopes to be the scope of
+ # their parent class, and it's also used when looking up qualified
+ # variables.
+ def class_scope(klass)
+ # They might pass in either the class or class name
+ if klass.respond_to?(:classname)
+ @class_scopes[klass.classname]
+ else
+ @class_scopes[klass]
+ end
+ end
+
+ # Return a list of all of the defined classes.
+ def classlist
+ return @class_scopes.keys.reject { |k| k == "" }
+ end
+
+ # Should the scopes behave declaratively?
+ def declarative?
+ true
+ end
+
+ # Set up our configuration. We require an interpreter
+ # and a host name, and we normally are passed facts, too.
+ def initialize(options)
+ @interpreter = options[:interpreter] or
+ raise ArgumentError, "You must pass an interpreter to the configuration"
+ @facts = options[:facts] || {}
+ @host = options[:host] or
+ raise ArgumentError, "You must pass a host name to the configuration"
+
+ # Call the setup methods from the base class.
+ super()
+
+ initvars()
+ end
+
+ # Create a new scope, with either a specified parent scope or
+ # using the top scope. Adds an edge between the scope and
+ # its parent to the graph.
+ def newscope(parent = nil)
+ parent ||= @topscope
+ scope = Puppet::Parser::Scope.new(:configuration => self)
+ @graph.add_edge!(parent, scope)
+ scope
+ end
+
+ # Find the parent of a given scope. Assumes scopes only ever have
+ # one in edge, which will always be true.
+ def parent(scope)
+ if ary = @graph.adjacent(scope, :direction => :in) and ary.length > 0
+ ary[0]
+ else
+ nil
+ end
+ end
+
+ # Return an array of all of the unevaluated objects
+ def unevaluated
+ ary = @definedtable.find_all do |name, object|
+ ! object.builtin? and ! object.evaluated?
+ end.collect { |name, object| object }
+
+ if ary.empty?
+ return nil
+ else
+ return ary
+ end
+ end
+
+ private
+
+ # Set up all of our internal variables.
+ def initvars
+ # The table for storing class singletons. This will only actually
+ # be used by top scopes and node scopes.
+ @class_scopes = {}
+
+ # The table for all defined resources.
+ @resource_table = {}
+
+ # The list of objects that will available for export.
+ @exported_resources = {}
+
+ # The list of overrides. This is used to cache overrides on objects
+ # that don't exist yet. We store an array of each override.
+ @resource_overrides = Hash.new do |overs, ref|
+ overs[ref] = []
+ end
+
+ # The list of collections that have been created. This is a global list,
+ # but they each refer back to the scope that created them.
+ @collections = []
+
+ # Create our initial scope, our scope graph, and add the initial scope to the graph.
+ @topscope = Puppet::Parser::Scope.new(:configuration => self, :type => "main", :name => "top")
+ @graph = GRATR::Digraph.new
+ @graph.add_vertex!(@topscope)
+ end
+
+ # Return the list of remaining overrides.
+ def overrides
+ @resource_overrides.values.flatten
+ end
+
+ def resources
+ @resourcetable
+ end
+end
diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb
index 6feeefc46..1fb4f6906 100644
--- a/lib/puppet/parser/scope.rb
+++ b/lib/puppet/parser/scope.rb
@@ -15,36 +15,18 @@ class Puppet::Parser::Scope
include Enumerable
include Puppet::Util::Errors
- attr_accessor :parent, :level, :interp, :source, :host
- attr_accessor :name, :type, :topscope, :base, :keyword
- attr_accessor :top, :translated, :exported, :virtual
+ attr_accessor :parent, :level, :interp, :source
+ attr_accessor :name, :type, :base, :keyword
+ attr_accessor :top, :translated, :exported, :virtual, :configuration
- # Whether we behave declaratively. Note that it's a class variable,
- # so all scopes behave the same.
- @@declarative = true
-
- # Retrieve and set the declarative setting.
- def self.declarative
- return @@declarative
- end
-
- def self.declarative=(val)
- @@declarative = val
+ # Proxy accessors
+ def host
+ @configuration.host
end
-
- # This handles the shared tables that all scopes have. They're effectively
- # global tables, except that they're only global for a single scope tree,
- # which is why I can't use class variables for them.
- def self.sharedtable(*names)
- attr_accessor(*names)
- @@sharedtables ||= []
- @@sharedtables += names
+ def interpreter
+ @configuration.interpreter
end
- # This is probably not all that good of an idea, but...
- # This way a parent can share its tables with all of its children.
- sharedtable :classtable, :definedtable, :exportable, :overridetable, :collecttable
-
# Is the value true? This allows us to control the definition of truth
# in one place.
def self.true?(value)
@@ -111,44 +93,6 @@ class Puppet::Parser::Scope
return true
end
- # Return the scope associated with a class. This is just here so
- # that subclasses can set their parent scopes to be the scope of
- # their parent class.
- def class_scope(klass)
- scope = if klass.respond_to?(:classname)
- @classtable[klass.classname]
- else
- @classtable[klass]
- end
-
- return nil unless scope
-
- if scope.nodescope? and ! klass.is_a?(AST::Node)
- raise Puppet::ParseError, "Node %s has already been evaluated; cannot evaluate class with same name" % [klass.classname]
- end
-
- scope
- end
-
- # Return the list of collections.
- def collections
- @collecttable
- end
-
- def declarative=(val)
- self.class.declarative = val
- end
-
- def declarative
- self.class.declarative
- end
-
- # Test whether a given scope is declarative. Even though it's
- # a global value, the calling objects don't need to know that.
- def declarative?
- @@declarative
- end
-
# Remove a specific child.
def delete(child)
@children.delete(child)
@@ -171,14 +115,6 @@ class Puppet::Parser::Scope
@level == 1
end
- # Return a list of all of the defined classes.
- def classlist
- unless defined? @classtable
- raise Puppet::DevError, "Scope did not receive class table"
- end
- return @classtable.keys.reject { |k| k == "" }
- end
-
# Yield each child scope in turn
def each
@children.each { |child|
@@ -239,9 +175,6 @@ class Puppet::Parser::Scope
# Initialize our new scope. Defaults to having no parent and to
# being declarative.
def initialize(hash = {})
- @parent = nil
- @type = nil
- @name = nil
@finished = false
if hash.include?(:namespace)
if n = hash[:namespace]
@@ -262,26 +195,6 @@ class Puppet::Parser::Scope
@tags = []
- if @parent.nil?
- unless hash.include?(:declarative)
- hash[:declarative] = true
- end
- self.istop(hash[:declarative])
- @inside = nil
- else
- # This is here, rather than in newchild(), so that all
- # of the later variable initialization works.
- @parent.child = self
-
- @level = @parent.level + 1
- @interp = @parent.interp
- @source = hash[:source] || @parent.source
- @topscope = @parent.topscope
- #@inside = @parent.inside # Used for definition inheritance
- @host = @parent.host
- @type ||= @parent.type
- end
-
# Our child scopes and objects
@children = []
@@ -294,62 +207,6 @@ class Puppet::Parser::Scope
@defaultstable = Hash.new { |dhash,type|
dhash[type] = {}
}
-
- unless @interp
- raise Puppet::DevError, "Scopes require an interpreter"
- end
- end
-
- # Associate the object directly with the scope, so that contained objects
- # can look up what container they're running within.
- def inside(arg = nil)
- return @inside unless arg
-
- old = @inside
- @inside = arg
- yield
- ensure
- #Puppet.warning "exiting %s" % @inside.name
- @inside = old
- end
-
- # Mark that we're the top scope, and set some hard-coded info.
- def istop(declarative = true)
- # the level is mostly used for debugging
- @level = 1
-
- # The table for storing class singletons. This will only actually
- # be used by top scopes and node scopes.
- @classtable = {}
-
- self.class.declarative = declarative
-
- # The table for all defined objects.
- @definedtable = {}
-
- # The list of objects that will available for export.
- @exportable = {}
-
- # The list of overrides. This is used to cache overrides on objects
- # that don't exist yet. We store an array of each override.
- @overridetable = Hash.new do |overs, ref|
- overs[ref] = []
- end
-
- # Eventually, if we support sites, this will allow definitions
- # of nodes with the same name in different sites. For now
- # the top-level scope is always the only site scope.
- @sitescope = true
-
- @namespaces = [""]
-
- # The list of collections that have been created. This is a global list,
- # but they each refer back to the scope that created them.
- @collecttable = []
-
- @topscope = self
- @type = "puppet"
- @name = "top"
end
# Collect all of the defaults set at any higher scopes.
@@ -360,8 +217,8 @@ class Puppet::Parser::Scope
values = {}
# first collect the values from the parents
- unless @parent.nil?
- @parent.lookupdefaults(type).each { |var,value|
+ unless parent.nil?
+ parent.lookupdefaults(type).each { |var,value|
values[var] = value
}
end
@@ -426,7 +283,7 @@ class Puppet::Parser::Scope
return @symtable[name]
end
elsif self.parent
- return @parent.lookupvar(name, usestring)
+ return parent.lookupvar(name, usestring)
elsif usestring
return ""
else
@@ -438,11 +295,6 @@ class Puppet::Parser::Scope
@namespaces.dup
end
- # Add a collection to the global list.
- def newcollection(coll)
- @collecttable << coll
- end
-
# Create a new scope.
def newscope(hash = {})
hash[:parent] = self
@@ -464,6 +316,25 @@ class Puppet::Parser::Scope
@overridetable.values.flatten
end
+ # We probably shouldn't cache this value... But it's a lot faster
+ # than doing lots of queries.
+ def parent
+ unless defined?(@parent)
+ @parent = configuration.parent(self)
+ end
+ @parent
+ end
+
+ # Return the list of scopes up to the top scope, ordered with our own first.
+ # This is used for looking up variables and defaults.
+ def scope_path
+ if parent
+ [self, parent.scope_path].flatten.compact
+ else
+ [self]
+ end
+ end
+
def resources
@definedtable.values
end
@@ -472,17 +343,17 @@ class Puppet::Parser::Scope
# that gets inherited from the top scope down, rather than a global
# hash. We store the object ID, not class name, so that we
# can support multiple unrelated classes with the same name.
- def setclass(obj)
- if obj.is_a?(AST::HostClass)
- unless obj.classname
+ def setclass(klass)
+ if klass.is_a?(AST::HostClass)
+ unless klass.classname
raise Puppet::DevError, "Got a %s with no fully qualified name" %
- obj.class
+ klass.class
end
- @classtable[obj.classname] = self
+ @configuration.class_set(klass.classname, self)
else
- raise Puppet::DevError, "Invalid class %s" % obj.inspect
+ raise Puppet::DevError, "Invalid class %s" % klass.inspect
end
- if obj.is_a?(AST::Node)
+ if klass.is_a?(AST::Node)
@nodescope = true
end
nil
@@ -665,9 +536,9 @@ class Puppet::Parser::Scope
unless ! defined? @type or @type.nil? or @type == ""
tmp << @type.to_s
end
- if @parent
- #info "Looking for tags in %s" % @parent.type
- @parent.tags.each { |tag|
+ if parent
+ #info "Looking for tags in %s" % parent.type
+ parent.tags.each { |tag|
if tag.nil? or tag == ""
Puppet.debug "parent returned tag %s" % tag.inspect
next
@@ -722,19 +593,6 @@ class Puppet::Parser::Scope
end
end
- # Return an array of all of the unevaluated objects
- def unevaluated
- ary = @definedtable.find_all do |name, object|
- ! object.builtin? and ! object.evaluated?
- end.collect { |name, object| object }
-
- if ary.empty?
- return nil
- else
- return ary
- end
- end
-
def virtual?
self.virtual || self.exported?
end