summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG22
-rw-r--r--lib/puppet/configuration.rb23
-rw-r--r--lib/puppet/parser/interpreter.rb257
-rwxr-xr-xtest/language/interpreter.rb655
4 files changed, 516 insertions, 441 deletions
diff --git a/CHANGELOG b/CHANGELOG
index b93d3b3cd..7aeb6bba3 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,4 +1,24 @@
- Reworked the idatbase schema used to store configurations with the
+ COMPATIBILITY ALERT:
+ Significantly reworked external node support, in a way that's NOT
+ backward-compatible:
+
+ Only ONE node source can be used -- you can use LDAP, code, or
+ an external node program, but not more than one.
+
+ LDAP node support has two changes: First, the "ldapattrs" attribute is
+ now used for setting the attributes to retrieve from the server (in
+ addition to required attriutes), and second, all retrieved attributes
+ are set as variables in the top scope. This means you can set attributes
+ on your LDAP nodes and they will automatically appear as variables
+ in your configurations.
+
+ External node support has been completely rewritten. These programs must
+ now generate a YAML dump of a hash, with "classes" and "parameters" keys.
+ The classes should be an array, and the parameters should be a hash. The
+ external node program has no support for parent nodes -- the script must
+ handle that on its own.
+
+ Reworked the database schema used to store configurations with the
storeconfigs option.
Replaced the obsolete RRD ruby library with the maintained
diff --git a/lib/puppet/configuration.rb b/lib/puppet/configuration.rb
index 02f62bbcb..5782adc82 100644
--- a/lib/puppet/configuration.rb
+++ b/lib/puppet/configuration.rb
@@ -539,14 +539,14 @@ module Puppet
should be case-sensitive. Case insensitivity is
handled by downcasing all values before comparison."],
:external_nodes => ["none",
- "An external command that can produce node information. The
- first line of output must be either the parent node or blank,
- and if there is a second line of output it should be a list of
- whitespace-separated classes to include on that node. This command
- makes it straightforward to store your node mapping information
- in other data sources like databases.
-
- For unknown nodes, the commands should exit with an exit code of 1."])
+ "An external command that can produce node information. The output
+ must be a YAML dump of a hash, and that hash must have one or both of
+ ``classes`` and ``parameters``, where ``classes`` is an array and
+ ``parameters`` is a hash. For unknown nodes, the commands should
+ exit with a non-zero exit code.
+
+ This command makes it straightforward to store your node mapping
+ information in other data sources like databases."])
setdefaults(:ldap,
:ldapnodes => [false,
@@ -565,9 +565,14 @@ module Puppet
"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",
+ :ldapclassattrs => ["puppetclass",
"The LDAP attributes to use to define Puppet classes. Values
should be comma-separated."],
+ :ldapattrs => ["all",
+ "The LDAP attributes to include when querying LDAP for nodes. All
+ returned attributes are set as variables in the top-level scope.
+ Multiple values should be comma-separated. The value 'all' returns
+ all attributes."],
:ldapparentattr => ["parentnode",
"The attribute to use to define the parent node."],
:ldapuser => ["",
diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb
index b9391f902..3a719e03c 100644
--- a/lib/puppet/parser/interpreter.rb
+++ b/lib/puppet/parser/interpreter.rb
@@ -5,10 +5,54 @@
require 'puppet'
require 'timeout'
require 'puppet/rails'
+require 'puppet/util/methodhelper'
require 'puppet/parser/parser'
require 'puppet/parser/scope'
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)
+ rescue => detail
+ 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
@@ -156,7 +200,8 @@ class Puppet::Parser::Interpreter
klass.safeevaluate :scope => scope, :nosubscope => true
end
- # Next evaluate the node
+ # 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.
@@ -258,51 +303,6 @@ class Puppet::Parser::Interpreter
return nil
end
- # Create a new node, just from a list of names, classes, and an optional parent.
- def gennode(name, hash, source = nil)
- facts = hash[:facts]
- classes = hash[:classes]
- parent = hash[:parentnode]
- arghash = {
- :name => name,
- :interp => self,
- :classname => 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
- if source
- arghash[:file] = source
- else
- arghash[:file] = nil
- end
- arghash[:line] = nil
- node = @parser.ast(AST::Node, arghash)
- return node
- end
-
# create our interpreter
def initialize(hash)
if @code = hash[:Code]
@@ -317,33 +317,15 @@ class Puppet::Parser::Interpreter
@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
-
- unless Puppet[:external_nodes] == "none"
- @nodesources << :external
+ @nodesource = :ldap
+ elsif Puppet[:external_nodes] != "none"
+ @nodesource = :external
+ else
+ # By default, we only search for parsed nodes.
+ @nodesource = :code
end
@setup = false
@@ -389,26 +371,31 @@ class Puppet::Parser::Interpreter
@definetable = {}
end
- # Find the ldap node and extra the info, returning just
- # the critical data.
+ # 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, []
+ return nil
end
end
filter = Puppet[:ldapstring]
- attrs = Puppet[:ldapattrs].split("\s*,\s*")
- sattrs = attrs.dup
+ 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
- sattrs << pattr
+ search_attrs << pattr unless search_attrs.nil?
end
end
@@ -418,12 +405,14 @@ class Puppet::Parser::Interpreter
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, sattrs) do |entry|
+ @ldap.search(Puppet[:ldapbase], 2, filter, search_attrs) do |entry|
found = true
if pattr
if values = entry.vals(pattr)
@@ -438,11 +427,20 @@ class Puppet::Parser::Interpreter
end
end
- attrs.each { |attr|
+ 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
@@ -461,7 +459,11 @@ class Puppet::Parser::Interpreter
classes = nil
end
- return parent, classes
+ if parent or classes or parameters
+ return parent, classes, parameters
+ else
+ return nil
+ end
end
# Split an fq name into a namespace and name
@@ -590,21 +592,20 @@ class Puppet::Parser::Interpreter
# Search for our node in the various locations.
def nodesearch(*nodes)
nodes = nodes.collect { |n| n.to_s.downcase }
- # 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
+
+ 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
@@ -640,41 +641,51 @@ class Puppet::Parser::Interpreter
return nil
end
- if output =~ /\A\s+\Z/ # all whitespace
+ if output =~ /\A\s*\Z/ # all whitespace
Puppet.debug "Empty response for %s from external node source" % name
return nil
end
-
- lines = output.split("\n")
-
- args = {}
- parent = lines[0].gsub(/\s+/, '')
- args[:parentnode] = parent unless parent == ""
-
- if lines[1]
- classes = lines[1].sub(/^\s+/,'').sub(/\s+$/,'').split(/\s+/)
- args[:classes] = classes unless classes.empty?
+
+ 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
-
- if args.empty?
- Puppet.warning "Somehow got a node with no information"
- return nil
+
+ 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 gennode(name, args, Puppet[:external_nodes])
+ return nil
end
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, "ldap")
- else
+ 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
@@ -687,15 +698,13 @@ class Puppet::Parser::Interpreter
# 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
+ 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
parsefiles()
diff --git a/test/language/interpreter.rb b/test/language/interpreter.rb
index fd1847df9..cf2cba854 100755
--- a/test/language/interpreter.rb
+++ b/test/language/interpreter.rb
@@ -16,13 +16,14 @@ require 'puppettest/servertest'
require 'puppettest/railstesting'
require 'timeout'
-class TestInterpreter < Test::Unit::TestCase
+class TestInterpreter < PuppetTest::TestCase
include PuppetTest
include PuppetTest::ServerTest
include PuppetTest::ParserTesting
include PuppetTest::ResourceTesting
include PuppetTest::RailsTesting
AST = Puppet::Parser::AST
+ NodeDef = Puppet::Parser::Interpreter::NodeDef
# create a simple manifest that uses nodes to create a file
def mknodemanifest(node, file)
@@ -76,199 +77,6 @@ class TestInterpreter < Test::Unit::TestCase
assert(config != newconfig, "Configs are somehow the same")
end
- if Puppet.features.rails?
- 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"
-
- # Only test ldap stuff on luke's network, since that's the only place we
- # have data for.
- if Puppet.features.ldap?
- 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.classname.to_s, culain.parentobj.classname.to_s,
- "Did not get parent class")
- end
- end
-
- if Puppet::Util::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.classname)
- end
-
# Make sure searchnode behaves as we expect.
def test_nodesearch
# We use two sources here to catch a weird bug where the default
@@ -410,54 +218,6 @@ class TestInterpreter < Test::Unit::TestCase
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
@@ -955,45 +715,6 @@ class TestInterpreter < Test::Unit::TestCase
assert(found.include?("/tmp/klass1"), "Did not evaluate klass1")
assert(found.include?("/tmp/klass2"), "Did not evaluate klass2")
end
-
- if Puppet.features.rails?
- # We need to make sure finished objects are stored in the db.
- def test_finish_before_store
- railsinit
- interp = mkinterp
-
- node = interp.newnode ["myhost"], :code => AST::ASTArray.new(:children => [
- resourcedef("file", "/tmp/yay", :group => "root"),
- defaultobj("file", :owner => "root")
- ])
-
- interp.newclass "myclass", :code => AST::ASTArray.new(:children => [
- ])
-
- interp.newclass "sub", :parent => "myclass",
- :code => AST::ASTArray.new(:children => [
- resourceoverride("file", "/tmp/yay", :owner => "root")
- ]
- )
-
- # Now do the rails crap
- Puppet[:storeconfigs] = true
-
- interp.evaluate("myhost", {})
-
- # And then retrieve the object from rails
- res = Puppet::Rails::Resource.find_by_restype_and_title("file", "/tmp/yay")
-
- assert(res, "Did not get resource from rails")
-
- param = res.param_names.find_by_name("owner", :include => :param_values)
-
- assert(param, "Did not find owner param")
-
- pvalue = param.param_values.find_by_value("root")
- assert_equal("root", pvalue[:value])
- end
- end
def mk_node_mapper
# First, make sure our nodesearch command works as we expect
@@ -1002,17 +723,19 @@ class TestInterpreter < Test::Unit::TestCase
ruby = %x{which ruby}.chomp
File.open(mapper, "w") { |f|
f.puts "#!#{ruby}
- name = ARGV[0]
+ require 'yaml'
+ name = ARGV[0].chomp
+ result = {}
+
if name =~ /a/
- puts ARGV[0].gsub('a', 'b')
- else
- puts ''
+ result[:parameters] = {'one' => ARGV[0] + '1', 'two' => ARGV[0] + '2'}
end
+
if name =~ /p/
- puts [1,2,3].collect { |n| ARGV[0] + n.to_s }.join(' ')
- else
- puts ''
+ result['classes'] = [1,2,3].collect { |n| ARGV[0] + n.to_s }
end
+
+ puts YAML.dump(result)
"
}
File.chmod(0755, mapper)
@@ -1022,18 +745,10 @@ class TestInterpreter < Test::Unit::TestCase
def test_nodesearch_external
interp = mkinterp
- # Make a fake gennode method
- class << interp
- def gennode(name, args, source)
- args[:name] = name
- return args
- end
- end
-
mapper = mk_node_mapper
# Make sure it gives the right response
- assert_equal("bpple\napple1 apple2 apple3\n",
- %x{#{mapper} apple})
+ assert_equal({'classes' => %w{apple1 apple2 apple3}, :parameters => {"one" => "apple1", "two" => "apple2"}},
+ YAML.load(%x{#{mapper} apple}))
# First make sure we get nil back by default
assert_nothing_raised {
@@ -1043,23 +758,29 @@ class TestInterpreter < Test::Unit::TestCase
assert_nothing_raised { Puppet[:external_nodes] = mapper }
node = nil
+ # Both 'a' and 'p', so we get classes and parameters
assert_nothing_raised { node = interp.nodesearch_external("apple") }
+ assert_equal("apple", node.name, "node name was not set correctly for apple")
+ assert_equal(%w{apple1 apple2 apple3}, node.classes, "node classes were not set correctly for apple")
+ assert_equal( {"one" => "apple1", "two" => "apple2"}, node.parameters, "node parameters were not set correctly for apple")
- assert_equal({:name => "apple", :classes => %w{apple1 apple2 apple3}, :parentnode => "bpple"},
- node)
-
- assert_nothing_raised { node = interp.nodesearch_external("plum")} # no a's, thus no parent
- assert_equal({:name => "plum", :classes => %w{plum1 plum2 plum3}},
- node)
+ # A 'p' but no 'a', so we only get classes
+ assert_nothing_raised { node = interp.nodesearch_external("plum") }
+ assert_equal("plum", node.name, "node name was not set correctly for plum")
+ assert_equal(%w{plum1 plum2 plum3}, node.classes, "node classes were not set correctly for plum")
+ assert_equal({}, node.parameters, "node parameters were not set correctly for plum")
+ # An 'a' but no 'p', so we only get parameters.
assert_nothing_raised { node = interp.nodesearch_external("guava")} # no p's, thus no classes
- assert_equal({:name => "guava", :parentnode => "gubvb"},
- node)
+ assert_equal("guava", node.name, "node name was not set correctly for guava")
+ assert_equal([], node.classes, "node classes were not set correctly for guava")
+ assert_equal({"one" => "guava1", "two" => "guava2"}, node.parameters, "node parameters were not set correctly for guava")
assert_nothing_raised { node = interp.nodesearch_external("honeydew")} # neither, thus nil
assert_nil(node)
end
+ # A wrapper test, to make sure we're correctly calling the external search method.
def test_nodesearch_external_functional
mapper = mk_node_mapper
@@ -1070,7 +791,7 @@ class TestInterpreter < Test::Unit::TestCase
assert_nothing_raised do
node = interp.nodesearch("apple")
end
- assert_instance_of(Puppet::Parser::AST::Node, node, "did not create node")
+ assert_instance_of(NodeDef, node, "did not create node")
end
def test_check_resource_collections
@@ -1084,6 +805,326 @@ class TestInterpreter < Test::Unit::TestCase
interp.check_resource_collections(scope)
end
end
+
+ def test_nodedef
+ interp = mkinterp
+ interp.newclass("base")
+ interp.newclass("sub", :parent => "base")
+ interp.newclass("other")
+
+ node = nil
+ assert_nothing_raised("Could not create a node definition") do
+ node = NodeDef.new :name => "yay", :classes => "sub", :parameters => {"one" => "two", "three" => "four"}
+ end
+
+ scope = mkscope :interp => interp
+ assert_nothing_raised("Could not evaluate the node definition") do
+ node.evaluate(:scope => scope)
+ end
+
+ assert_equal("two", scope.lookupvar("one"), "NodeDef did not set variable")
+ assert_equal("four", scope.lookupvar("three"), "NodeDef did not set variable")
+
+ assert(scope.classlist.include?("sub"), "NodeDef did not evaluate class")
+ assert(scope.classlist.include?("base"), "NodeDef did not evaluate base class")
+
+ # Now try a node def with multiple classes
+ assert_nothing_raised("Could not create a node definition") do
+ node = NodeDef.new :name => "yay", :classes => %w{sub other base}, :parameters => {"one" => "two", "three" => "four"}
+ end
+
+ scope = mkscope :interp => interp
+ assert_nothing_raised("Could not evaluate the node definition") do
+ node.evaluate(:scope => scope)
+ end
+
+ assert_equal("two", scope.lookupvar("one"), "NodeDef did not set variable")
+ assert_equal("four", scope.lookupvar("three"), "NodeDef did not set variable")
+
+ assert(scope.classlist.include?("sub"), "NodeDef did not evaluate class")
+ assert(scope.classlist.include?("other"), "NodeDef did not evaluate other class")
+
+ # And a node def with no params
+ assert_nothing_raised("Could not create a node definition with no params") do
+ node = NodeDef.new :name => "yay", :classes => %w{sub other base}
+ end
+
+ scope = mkscope :interp => interp
+ assert_nothing_raised("Could not evaluate the node definition") do
+ node.evaluate(:scope => scope)
+ end
+
+ assert(scope.classlist.include?("sub"), "NodeDef did not evaluate class")
+ assert(scope.classlist.include?("other"), "NodeDef did not evaluate other class")
+ end
+
+ def test_ldapnodes
+ interp = mkinterp
+
+ nodetable = {}
+
+ # Override the ldapsearch definition, so we don't have to actually set it up.
+ interp.meta_def(:ldapsearch) do |name|
+ nodetable[name]
+ end
+
+ # Make sure we get nothing for nonexistent hosts
+ node = nil
+ assert_nothing_raised do
+ node = interp.nodesearch_ldap("nosuchhost")
+ end
+
+ assert_nil(node, "Got a node for a non-existent host")
+
+ # Now add a base node with some classes and parameters
+ nodetable["base"] = [nil, %w{one two}, {"base" => "true"}]
+
+ assert_nothing_raised do
+ node = interp.nodesearch_ldap("base")
+ end
+
+ assert_instance_of(NodeDef, node, "Did not get node from ldap nodesearch")
+ assert_equal("base", node.name, "node name was not set")
+
+ assert_equal(%w{one two}, node.classes, "node classes were not set")
+ assert_equal({"base" => "true"}, node.parameters, "node parameters were not set")
+
+ # Now use a different with this as the base
+ nodetable["middle"] = ["base", %w{three}, {"center" => "boo"}]
+ assert_nothing_raised do
+ node = interp.nodesearch_ldap("middle")
+ end
+
+ assert_instance_of(NodeDef, node, "Did not get node from ldap nodesearch")
+ assert_equal("middle", node.name, "node name was not set")
+
+ assert_equal(%w{one two three}.sort, node.classes.sort, "node classes were not set correctly with a parent node")
+ assert_equal({"base" => "true", "center" => "boo"}, node.parameters, "node parameters were not set correctly with a parent node")
+
+ # And one further, to make sure we fully recurse
+ nodetable["top"] = ["middle", %w{four five}, {"master" => "far"}]
+ assert_nothing_raised do
+ node = interp.nodesearch_ldap("top")
+ end
+
+ assert_instance_of(NodeDef, node, "Did not get node from ldap nodesearch")
+ assert_equal("top", node.name, "node name was not set")
+
+ assert_equal(%w{one two three four five}.sort, node.classes.sort, "node classes were not set correctly with the top node")
+ assert_equal({"base" => "true", "center" => "boo", "master" => "far"}, node.parameters, "node parameters were not set correctly with the top node")
+ end
+end
+
+class LdapNodeTest < PuppetTest::TestCase
+ include PuppetTest
+ include PuppetTest::ServerTest
+ include PuppetTest::ParserTesting
+ include PuppetTest::ResourceTesting
+ include PuppetTest::RailsTesting
+ AST = Puppet::Parser::AST
+ NodeDef = Puppet::Parser::Interpreter::NodeDef
+ confine "LDAP is not available" => Puppet.features.ldap?
+ confine "No LDAP test data for networks other than Luke's" => Facter.value(:domain) == "madstop.com"
+ 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(name)
+ node = NodeDef.new(:name => name)
+ parent = nil
+ found = false
+ @ldap.search( "ou=hosts, dc=madstop, dc=com", 2,
+ "(&(objectclass=puppetclient)(cn=%s))" % name
+ ) do |entry|
+ node.classes = entry.vals("puppetclass") || []
+ node.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
+ parent = node.parameters["parentnode"]
+ found = true
+ end
+ raise "Could not find node %s" % name unless found
+
+ return node, parent
+ 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, parameters = nil
+ assert_nothing_raised do
+ parent, classes, parameters = 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, parameters = interp.ldapsearch("culain")
+ end
+
+ node, realparent = ldaphost("culain")
+ assert_equal(realparent, parent, "did not get correct parent node from ldap")
+ assert_equal(node.classes, classes, "did not get correct ldap classes from ldap")
+ assert_equal(node.parameters, parameters, "did not get correct ldap parameters from ldap")
+
+ # Now compare when we specify the attributes to get.
+ Puppet[:ldapattrs] = "cn"
+ assert_nothing_raised do
+ parent, classes, parameters = interp.ldapsearch("culain")
+ end
+ assert_equal(realparent, parent, "did not get correct parent node from ldap")
+ assert_equal(node.classes, classes, "did not get correct ldap classes from ldap")
+
+ list = %w{cn puppetclass parentnode dn}
+ should = node.parameters.inject({}) { |h, a| h[a[0]] = a[1] if list.include?(a[0]); h }
+ assert_equal(should, parameters, "did not get correct ldap parameters from ldap")
+ end
+end
+
+class LdapReconnectTests < PuppetTest::TestCase
+ include PuppetTest
+ include PuppetTest::ServerTest
+ include PuppetTest::ParserTesting
+ include PuppetTest::ResourceTesting
+ include PuppetTest::RailsTesting
+ AST = Puppet::Parser::AST
+ NodeDef = Puppet::Parser::Interpreter::NodeDef
+ confine "Not running on culain as root" => (Puppet::Util::SUIDManager.uid == 0 and Facter.value("hostname") == "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
+end
+
+class InterpreterRailsTests < PuppetTest::TestCase
+ include PuppetTest
+ include PuppetTest::ServerTest
+ include PuppetTest::ParserTesting
+ include PuppetTest::ResourceTesting
+ include PuppetTest::RailsTesting
+ AST = Puppet::Parser::AST
+ NodeDef = Puppet::Parser::Interpreter::NodeDef
+ confine "No rails support" => Puppet.features.rails?
+
+ # We need to make sure finished objects are stored in the db.
+ def test_finish_before_store
+ railsinit
+ interp = mkinterp
+
+ node = interp.newnode ["myhost"], :code => AST::ASTArray.new(:children => [
+ resourcedef("file", "/tmp/yay", :group => "root"),
+ defaultobj("file", :owner => "root")
+ ])
+
+ interp.newclass "myclass", :code => AST::ASTArray.new(:children => [
+ ])
+
+ interp.newclass "sub", :parent => "myclass",
+ :code => AST::ASTArray.new(:children => [
+ resourceoverride("file", "/tmp/yay", :owner => "root")
+ ]
+ )
+
+ # Now do the rails crap
+ Puppet[:storeconfigs] = true
+
+ interp.evaluate("myhost", {})
+
+ # And then retrieve the object from rails
+ res = Puppet::Rails::Resource.find_by_restype_and_title("file", "/tmp/yay")
+
+ assert(res, "Did not get resource from rails")
+
+ param = res.param_names.find_by_name("owner", :include => :param_values)
+
+ assert(param, "Did not find owner param")
+
+ pvalue = param.param_values.find_by_value("root")
+ assert_equal("root", pvalue[:value])
+ end
+
+ 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
end
# $Id$