From ebe7290bf0c9119e268c9037c33da515e527aa5b Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Sat, 22 Sep 2007 00:16:39 -0500 Subject: All indirections are working, and they have all been migrated over to the new organization. Where we would have previously had an 'ldap' node terminus at puppet/indirector/node/ldap.rb, we would not have it at puppet/indirector/ldap/node.rb, and it would be a subclass of puppet/indirector/ldap.rb. These are called terminus classes, and there are now three categories of them: The base class itself, abstract classes that provide most of the functionality (e.g., the ldap and yaml classes), and the classes themselves that implement the functionality for a given model like Node or Facts. The base terminus class handles auto-loading any of these classes from disk. --- lib/puppet/defaults.rb | 2 +- lib/puppet/indirector.rb | 2 +- lib/puppet/indirector/exec.rb | 57 ++++++++ lib/puppet/indirector/exec/node.rb | 50 +++++++ lib/puppet/indirector/indirection.rb | 5 +- lib/puppet/indirector/ldap.rb | 90 ++++++++++++ lib/puppet/indirector/ldap/node.rb | 75 ++++++++++ lib/puppet/indirector/node/external.rb | 89 ------------ lib/puppet/indirector/node/ldap.rb | 140 ------------------- lib/puppet/indirector/node/none.rb | 14 -- lib/puppet/indirector/null.rb | 9 ++ lib/puppet/indirector/null/node.rb | 14 ++ lib/puppet/indirector/terminus.rb | 8 ++ spec/unit/indirector/exec.rb | 49 +++++++ spec/unit/indirector/exec/node.rb | 62 +++++++++ spec/unit/indirector/indirection.rb | 20 ++- spec/unit/indirector/ldap.rb | 147 ++++++++++++++++++++ spec/unit/indirector/ldap/node.rb | 84 ++++++++++++ spec/unit/indirector/node/external.rb | 119 ---------------- spec/unit/indirector/node/ldap.rb | 243 --------------------------------- spec/unit/indirector/node/none.rb | 32 ----- spec/unit/indirector/null.rb | 27 ++++ spec/unit/indirector/null/node.rb | 18 +++ spec/unit/indirector/terminus.rb | 14 ++ 24 files changed, 722 insertions(+), 648 deletions(-) create mode 100644 lib/puppet/indirector/exec.rb create mode 100644 lib/puppet/indirector/exec/node.rb create mode 100644 lib/puppet/indirector/ldap.rb create mode 100644 lib/puppet/indirector/ldap/node.rb delete mode 100644 lib/puppet/indirector/node/external.rb delete mode 100644 lib/puppet/indirector/node/ldap.rb delete mode 100644 lib/puppet/indirector/node/none.rb create mode 100644 lib/puppet/indirector/null.rb create mode 100644 lib/puppet/indirector/null/node.rb create mode 100755 spec/unit/indirector/exec.rb create mode 100755 spec/unit/indirector/exec/node.rb create mode 100755 spec/unit/indirector/ldap.rb create mode 100755 spec/unit/indirector/ldap/node.rb delete mode 100755 spec/unit/indirector/node/external.rb delete mode 100755 spec/unit/indirector/node/ldap.rb delete mode 100755 spec/unit/indirector/node/none.rb create mode 100755 spec/unit/indirector/null.rb create mode 100755 spec/unit/indirector/null/node.rb diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index fa119bb5a..fa06c7fdb 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -554,7 +554,7 @@ module Puppet setdefaults(:parser, :typecheck => [true, "Whether to validate types during parsing."], :paramcheck => [true, "Whether to validate parameters during parsing."], - :node_source => ["none", "Where to look for node configuration information. + :node_terminus => ["null", "Where to look for node configuration information. The default node source, ``none``, just returns a node with its facts filled in, which is required for normal functionality. See the `NodeSourceReference`:trac: for more information."] diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb index b89017c9a..bd2487e33 100644 --- a/lib/puppet/indirector.rb +++ b/lib/puppet/indirector.rb @@ -23,7 +23,7 @@ module Puppet::Indirector # instantiate the actual Terminus for that type and this name (:ldap, w/ args :node) # & hook the instantiated Terminus into this class (Node: @indirection = terminus) - @indirection = Puppet::Indirector::Indirection.new(indirection) + @indirection = Puppet::Indirector::Indirection.new(self, indirection) end module ClassMethods diff --git a/lib/puppet/indirector/exec.rb b/lib/puppet/indirector/exec.rb new file mode 100644 index 000000000..7e4ac8d18 --- /dev/null +++ b/lib/puppet/indirector/exec.rb @@ -0,0 +1,57 @@ +require 'puppet/indirector/terminus' +require 'puppet/util' + +class Puppet::Indirector::Exec < Puppet::Indirector::Terminus + # Look for external node definitions. + def find(name) + # Run the command. + unless output = query(name) + return nil + end + + # Translate the output to ruby. + return output + end + + private + + # Proxy the execution, so it's easier to test. + def execute(command) + Puppet::Util.execute(command) + end + + # Call the external command and see if it returns our output. + def query(name) + external_command = command + + # Make sure it's an arry + unless external_command.is_a?(Array) + raise Puppet::DevError, "Exec commands must be an array" + end + + # Make sure it's fully qualified. + unless external_command[0][0] == File::SEPARATOR[0] + raise ArgumentError, "You must set the exec parameter to a fully qualified command" + end + + # Add our name to it. + external_command << name + begin + output = execute(external_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 exec %s terminus" % [name, self.name] + return nil + else + return output + end + end +end diff --git a/lib/puppet/indirector/exec/node.rb b/lib/puppet/indirector/exec/node.rb new file mode 100644 index 000000000..033afe3f0 --- /dev/null +++ b/lib/puppet/indirector/exec/node.rb @@ -0,0 +1,50 @@ +require 'puppet/indirector/exec' + +class Puppet::Indirector::Exec::Node < Puppet::Indirector::Exec + desc "Call an external program to get node information." + include Puppet::Util + + def command + command = Puppet[:external_nodes] + unless command != "none" + raise ArgumentError, "You must set the 'external_nodes' parameter to use the external node terminus" + end + command.split + end + + # Look for external node definitions. + def find(name) + output = super or return nil + + # Translate the output to ruby. + result = translate(name, output) + + return create_node(name, result) + end + + private + + # Turn our outputted objects into a Puppet::Node instance. + def create_node(name, result) + node = Puppet::Node.new(name) + set = false + [:parameters, :classes].each do |param| + if value = result[param] + node.send(param.to_s + "=", value) + set = true + end + end + + node.fact_merge + return node + end + + # Translate the yaml string into Ruby objects. + def translate(name, output) + begin + 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 + end +end diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb index 31b9d68a5..12b8cf328 100644 --- a/lib/puppet/indirector/indirection.rb +++ b/lib/puppet/indirector/indirection.rb @@ -13,7 +13,7 @@ class Puppet::Indirector::Indirection @@indirections.find { |i| i.name == name } end - attr_accessor :name, :termini + attr_accessor :name, :model # Clear our cached list of termini. # This is only used for testing. @@ -26,7 +26,8 @@ class Puppet::Indirector::Indirection @@indirections.delete(self) if @@indirections.include?(self) end - def initialize(name, options = {}) + def initialize(model, name, options = {}) + @model = model @name = name options.each do |name, value| begin diff --git a/lib/puppet/indirector/ldap.rb b/lib/puppet/indirector/ldap.rb new file mode 100644 index 000000000..63848d10a --- /dev/null +++ b/lib/puppet/indirector/ldap.rb @@ -0,0 +1,90 @@ +require 'puppet/indirector/terminus' + +class Puppet::Indirector::Ldap < Puppet::Indirector::Terminus + # Perform our ldap search and process the result. + def find(name) + # We have to use 'yield' here because the LDAP::Entry objects + # get destroyed outside the scope of the search, strangely. + ldapsearch(name) { |entry| return process(entry) } + + # Return nil if we haven't found something. + return nil + end + + # Process the found entry. We assume that we don't just want the + # ldap object. + def process(entry) + raise Puppet::DevError, "The 'process' method has not been overridden for the LDAP terminus for %s" % self.name + end + + # Default to all attributes. + def search_attributes + nil + end + + def search_base + Puppet[:ldapbase] + end + + # The ldap search filter to use. + def search_filter(name) + raise Puppet::DevError, "No search string set for LDAP terminus for %s" % self.name + end + + # Find the ldap node, return the class list and parent node specially, + # and everything else in a parameter hash. + def ldapsearch(node) + raise ArgumentError.new("You must pass a block to ldapsearch") unless block_given? + + found = false + count = 0 + + begin + connection.search(search_base, 2, search_filter(node), search_attributes) do |entry| + found = true + yield entry + end + rescue => detail + if count == 0 + # Try reconnecting to ldap if we get an exception and we haven't yet retried. + count += 1 + @connection = nil + Puppet.warning "Retrying LDAP connection" + retry + else + raise Puppet::Error, "LDAP Search failed: %s" % detail + end + end + + return found + end + + private + + # Create an ldap connection. + def connection + unless defined? @connection and @connection + unless Puppet.features.ldap? + raise Puppet::Error, "Could not set up LDAP Connection: Missing ruby/ldap libraries" + end + begin + if Puppet[:ldapssl] + @connection = LDAP::SSLConn.new(Puppet[:ldapserver], Puppet[:ldapport]) + elsif Puppet[:ldaptls] + @connection = LDAP::SSLConn.new( + Puppet[:ldapserver], Puppet[:ldapport], true + ) + else + @connection = LDAP::Conn.new(Puppet[:ldapserver], Puppet[:ldapport]) + end + @connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) + @connection.set_option(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_ON) + @connection.simple_bind(Puppet[:ldapuser], Puppet[:ldappassword]) + rescue => detail + raise Puppet::Error, "Could not connect to LDAP: %s" % detail + end + end + + return @connection + end +end diff --git a/lib/puppet/indirector/ldap/node.rb b/lib/puppet/indirector/ldap/node.rb new file mode 100644 index 000000000..187c74b94 --- /dev/null +++ b/lib/puppet/indirector/ldap/node.rb @@ -0,0 +1,75 @@ +require 'puppet/indirector/ldap' + +class Puppet::Indirector::Ldap::Node < Puppet::Indirector::Ldap + desc "Search in LDAP for node configuration information." + + # The attributes that Puppet class information is stored in. + def class_attributes + Puppet[:ldapclassattrs].split(/\s*,\s*/) + end + + # Look for our node in ldap. + def find(name) + unless ary = ldapsearch(name) + 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 + + node = Puppet::Node.new(name, :classes => classes, :source => "ldap", :parameters => parameters) + node.fact_merge + return node + end + + # The parent attribute, if we have one. + def parent_attribute + if pattr = Puppet[:ldapparentattr] and ! pattr.empty? + pattr + else + nil + end + end + + # Process the found entry. We assume that we don't just want the + # ldap object. + def process(entry) + raise Puppet::DevError, "The 'process' method has not been overridden for the LDAP terminus for %s" % self.name + end + + # Default to all attributes. + def search_attributes + ldapattrs = Puppet[:ldapattrs] + + # results in everything getting returned + return nil if ldapattrs == "all" + + search_attrs = class_attributes + ldapattrs.split(/\s*,\s*/) + + if pattr = parent_attribute + search_attrs << pattr + end + + search_attrs + end + + # The ldap search filter to use. + def search_filter(name) + filter = Puppet[:ldapstring] + + if filter.include? "%s" + # Don't replace the string in-line, since that would hard-code our node + # info. + filter = filter.gsub('%s', name) + end + filter + end +end diff --git a/lib/puppet/indirector/node/external.rb b/lib/puppet/indirector/node/external.rb deleted file mode 100644 index 66aca6048..000000000 --- a/lib/puppet/indirector/node/external.rb +++ /dev/null @@ -1,89 +0,0 @@ -require 'puppet/node/facts' - -Puppet::Indirector.register_terminus :node, :external do - desc "Call an external program to get node information." - - include Puppet::Util - - # Proxy the execution, so it's easier to test. - def execute(command) - Puppet::Util.execute(command) - end - - # Look for external node definitions. - def find(name) - unless Puppet[:external_nodes] != "none" - raise ArgumentError, "You must set the 'external_nodes' parameter to use the external node source" - end - - unless Puppet[:external_nodes][0] == File::SEPARATOR[0] - raise ArgumentError, "You must set the 'external_nodes' parameter to a fully qualified command" - end - - # Run the command. - unless output = query(name) - return nil - end - - # Translate the output to ruby. - result = translate(name, output) - - return create_node(name, result) - end - - private - - # Turn our outputted objects into a Puppet::Node instance. - def create_node(name, result) - node = Puppet::Node.new(name) - set = false - [:parameters, :classes].each do |param| - if value = result[param] - node.send(param.to_s + "=", value) - set = true - end - end - - if set - node.fact_merge - return node - else - return nil - end - end - - # Call the external command and see if it returns our output. - def query(name) - # 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 = 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 - else - return output - end - end - - # Translate the yaml string into Ruby objects. - def translate(name, output) - begin - 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 - end -end diff --git a/lib/puppet/indirector/node/ldap.rb b/lib/puppet/indirector/node/ldap.rb deleted file mode 100644 index 230ca2467..000000000 --- a/lib/puppet/indirector/node/ldap.rb +++ /dev/null @@ -1,140 +0,0 @@ -Puppet::Indirector.register_terminus :node, :ldap do - desc "Search in LDAP for node configuration information." - - # Look for our node in ldap. - def find(name) - unless ary = ldapsearch(name) - 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 - - node = Puppet::Node.new(name, :classes => classes, :source => "ldap", :parameters => parameters) - node.fact_merge - return node - end - - # Find the ldap node, return the class list and parent node specially, - # and everything else in a parameter hash. - def ldapsearch(node) - 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 - 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 - - private - - # Create an ldap connection. - def ldap - unless defined? @ldap and @ldap - unless Puppet.features.ldap? - raise Puppet::Error, "Could not set up LDAP Connection: Missing ruby/ldap libraries" - end - begin - 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]) - rescue => detail - raise Puppet::Error, "Could not connect to LDAP: %s" % detail - end - end - - return @ldap - end -end diff --git a/lib/puppet/indirector/node/none.rb b/lib/puppet/indirector/node/none.rb deleted file mode 100644 index 034c0b349..000000000 --- a/lib/puppet/indirector/node/none.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'puppet/node/facts' - -Puppet::Indirector.register_terminus :node, :none do - desc "Always return an empty node object. This is the node source you should - use when you don't have some other, functional source you want to use, - as the compiler will not work without this node information." - - # Just return an empty node. - def find(name) - node = Puppet::Node.new(name) - node.fact_merge - node - end -end diff --git a/lib/puppet/indirector/null.rb b/lib/puppet/indirector/null.rb new file mode 100644 index 000000000..db2b1db1c --- /dev/null +++ b/lib/puppet/indirector/null.rb @@ -0,0 +1,9 @@ +require 'puppet/indirector/terminus' + +# An empty terminus type, meant to just return empty objects. +class Puppet::Indirector::Null < Puppet::Indirector::Terminus + # Just return nothing. + def find(name) + indirection.model.new(name) + end +end diff --git a/lib/puppet/indirector/null/node.rb b/lib/puppet/indirector/null/node.rb new file mode 100644 index 000000000..eb08f5697 --- /dev/null +++ b/lib/puppet/indirector/null/node.rb @@ -0,0 +1,14 @@ +require 'puppet/indirector/null' + +class Puppet::Indirector::Null::Node < Puppet::Indirector::Null + desc "Always return an empty node object. This is the node source you should + use when you don't have some other, functional source you want to use, + as the compiler will not work without this node information." + + # Just return an empty node. + def find(name) + node = super + node.fact_merge + node + end +end diff --git a/lib/puppet/indirector/terminus.rb b/lib/puppet/indirector/terminus.rb index f3797f471..bcff08d79 100644 --- a/lib/puppet/indirector/terminus.rb +++ b/lib/puppet/indirector/terminus.rb @@ -68,6 +68,10 @@ class Puppet::Indirector::Terminus @abstract_terminus = true end + def model + indirection.model + end + # Register a class, probably autoloaded. def register_terminus_class(klass) setup_instance_loading klass.terminus_type @@ -102,6 +106,10 @@ class Puppet::Indirector::Terminus def name self.class.name end + + def model + self.class.model + end def indirection self.class.indirection diff --git a/spec/unit/indirector/exec.rb b/spec/unit/indirector/exec.rb new file mode 100755 index 000000000..42fbe0955 --- /dev/null +++ b/spec/unit/indirector/exec.rb @@ -0,0 +1,49 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/indirector/exec' + +describe Puppet::Indirector::Exec do + before do + @indirection = mock 'indirection' + Puppet::Indirector::Indirection.expects(:instance).with(:testing).returns(@indirection) + @exec_class = Class.new(Puppet::Indirector::Exec) do + def self.to_s + "Testing" + end + + attr_accessor :command + end + + @searcher = @exec_class.new + @searcher.command = ["/echo"] + end + + it "should throw an exception if the command is not an array" do + @searcher.command = "/usr/bin/echo" + proc { @searcher.find("foo") }.should raise_error(Puppet::DevError) + end + + it "should throw an exception if the command is not fully qualified" do + @searcher.command = ["mycommand"] + proc { @searcher.find("foo") }.should raise_error(ArgumentError) + end + + it "should execute the command with the object name as the only argument" do + @searcher.expects(:execute).with(%w{/echo yay}) + @searcher.find("yay") + end + + it "should return the output of the script" do + @searcher.expects(:execute).with(%w{/echo yay}).returns("whatever") + @searcher.find("yay").should == "whatever" + end + + it "should return nil when the command produces no output" do + @searcher.expects(:execute).with(%w{/echo yay}).returns(nil) + @searcher.find("yay").should be_nil + end + + it "should be able to execute commands with multiple arguments" +end diff --git a/spec/unit/indirector/exec/node.rb b/spec/unit/indirector/exec/node.rb new file mode 100755 index 000000000..2eaf3c12e --- /dev/null +++ b/spec/unit/indirector/exec/node.rb @@ -0,0 +1,62 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/exec/node' + +describe Puppet::Indirector::Exec::Node, " when constructing the command to run" do + before do + @indirection = mock 'indirection' + Puppet.config.stubs(:value).with(:external_nodes).returns("/echo") + @searcher = Puppet::Indirector::Exec::Node.new + end + + it "should use the external_node script as the command" do + Puppet.expects(:[]).with(:external_nodes).returns("/bin/echo") + @searcher.command.should == %w{/bin/echo} + end + + it "should throw an exception if no external node command is set" do + Puppet.expects(:[]).with(:external_nodes).returns("none") + proc { @searcher.find("foo") }.should raise_error(ArgumentError) + end +end + +describe Puppet::Indirector::Exec::Node, " when handling the results of the command" do + before do + @indirection = mock 'indirection' + Puppet.config.stubs(:value).with(:external_nodes).returns("/echo") + @searcher = Puppet::Indirector::Exec::Node.new + @node = stub 'node', :fact_merge => nil + @name = "yay" + Puppet::Node.expects(:new).with(@name).returns(@node) + @result = {} + # Use a local variable so the reference is usable in the execute() definition. + result = @result + @searcher.meta_def(:execute) do |command| + return YAML.dump(result) + end + end + + it "should translate the YAML into a Node instance" do + # Use an empty hash + @searcher.find(@name).should equal(@node) + end + + it "should set the resulting parameters as the node parameters" do + @result[:parameters] = {"a" => "b", "c" => "d"} + @node.expects(:parameters=).with "a" => "b", "c" => "d" + @searcher.find(@name) + end + + it "should set the resulting classes as the node classes" do + @result[:classes] = %w{one two} + @node.expects(:classes=).with %w{one two} + @searcher.find(@name) + end + + it "should merge the node's facts with its parameters" do + @node.expects(:fact_merge) + @searcher.find(@name) + end +end diff --git a/spec/unit/indirector/indirection.rb b/spec/unit/indirector/indirection.rb index 5866f2d5f..e57f12072 100755 --- a/spec/unit/indirector/indirection.rb +++ b/spec/unit/indirector/indirection.rb @@ -5,13 +5,19 @@ require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/indirector' describe Puppet::Indirector::Indirection, " when initializing" do + it "should keep a reference to the indirecting model" do + model = mock 'model' + @indirection = Puppet::Indirector::Indirection.new(model, :myind) + @indirection.model.should equal(model) + end + it "should set the name" do - @indirection = Puppet::Indirector::Indirection.new(:myind) + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :myind) @indirection.name.should == :myind end it "should require indirections to have unique names" do - @indirection = Puppet::Indirector::Indirection.new(:test) + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) proc { Puppet::Indirector::Indirection.new(:test) }.should raise_error(ArgumentError) end @@ -22,7 +28,7 @@ end describe Puppet::Indirector::Indirection, " when managing indirection instances" do it "should allow an indirection to be retrieved by name" do - @indirection = Puppet::Indirector::Indirection.new(:test) + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) Puppet::Indirector::Indirection.instance(:test).should equal(@indirection) end @@ -37,7 +43,7 @@ end describe Puppet::Indirector::Indirection, " when choosing terminus types" do before do - @indirection = Puppet::Indirector::Indirection.new(:test) + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @terminus = mock 'terminus' @terminus_class = stub 'terminus class', :new => @terminus end @@ -82,7 +88,7 @@ end describe Puppet::Indirector::Indirection, " when managing terminus instances" do before do - @indirection = Puppet::Indirector::Indirection.new(:test) + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @terminus = mock 'terminus' @terminus_class = mock 'terminus class' Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class) @@ -111,7 +117,7 @@ describe Puppet::Indirector::Indirection, " when managing terminus instances" do it "should not create a terminus instance until one is actually needed" do Puppet::Indirector.expects(:terminus).never - indirection = Puppet::Indirector::Indirection.new(:lazytest) + indirection = Puppet::Indirector::Indirection.new(mock('model'), :lazytest) end after do @@ -122,7 +128,7 @@ end describe Puppet::Indirector::Indirection do before do - @indirection = Puppet::Indirector::Indirection.new(:test) + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @terminus = mock 'terminus' @indirection.stubs(:terminus).returns(@terminus) end diff --git a/spec/unit/indirector/ldap.rb b/spec/unit/indirector/ldap.rb new file mode 100755 index 000000000..a936936bc --- /dev/null +++ b/spec/unit/indirector/ldap.rb @@ -0,0 +1,147 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/indirector/ldap' + +describe Puppet::Indirector::Ldap, " when searching ldap" do + before do + @indirection = mock 'indirection' + Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) + @ldap_class = Class.new(Puppet::Indirector::Ldap) do + def self.to_s + "Testing" + end + end + + @connection = mock 'ldap' + + @searcher = @ldap_class.new + + # Stub everything, and we can selectively replace with an expect as + # we need to for testing. + @searcher.stubs(:connection).returns(@connection) + @searcher.stubs(:search_filter).returns(:filter) + @searcher.stubs(:search_base).returns(:base) + @searcher.stubs(:process) + end + + it "should call the ldapsearch method with the name being searched for" do + @searcher.expects(:ldapsearch).with("yay") + @searcher.find "yay" + end + + it "should fail if no block is passed to the ldapsearch method" do + proc { @searcher.ldapsearch("blah") }.should raise_error(ArgumentError) + end + + it "should use the results of the ldapbase method as the ldap search base" do + @searcher.stubs(:search_base).returns("mybase") + @connection.expects(:search).with do |*args| + args[0].should == "mybase" + true + end + @searcher.find "yay" + end + + it "should default to the value of the :search_base setting as the result of the ldapbase method" do + Puppet.expects(:[]).with(:ldapbase).returns("myldapbase") + searcher = @ldap_class.new + searcher.search_base.should == "myldapbase" + end + + it "should use the results of the :search_attributes method as the list of attributes to return" do + @searcher.stubs(:search_attributes).returns(:myattrs) + @connection.expects(:search).with do |*args| + args[3].should == :myattrs + true + end + @searcher.find "yay" + end + + it "should use the results of the :search_filter method as the search filter" do + @searcher.stubs(:search_filter).with("yay").returns("yay's filter") + @connection.expects(:search).with do |*args| + args[2].should == "yay's filter" + true + end + @searcher.find "yay" + end + + it "should use depth 2 when searching" do + @connection.expects(:search).with do |*args| + args[1].should == 2 + true + end + @searcher.find "yay" + end + + it "should call process() on the first found entry" do + @connection.expects(:search).yields("myresult") + @searcher.expects(:process).with("myresult") + @searcher.find "yay" + end + + it "should reconnect and retry the search if there is a failure" do + run = false + @connection.stubs(:search).with do |*args| + if run + true + else + run = true + raise "failed" + end + end.yields("myresult") + @searcher.expects(:process).with("myresult") + + @searcher.find "yay" + end + + it "should not reconnect on failure more than once" do + count = 0 + @connection.stubs(:search).with do |*args| + count += 1 + raise ArgumentError, "yay" + end + proc { @searcher.find("whatever") }.should raise_error(Puppet::Error) + count.should == 2 + end + + it "should return true if an entry is found" do + @connection.expects(:search).yields("result") + @searcher.ldapsearch("whatever") { |r| }.should be_true + end +end + +describe Puppet::Indirector::Ldap, " when connecting to ldap" do + confine "LDAP is not available" => Puppet.features.ldap? + confine "No LDAP test data for networks other than Luke's" => Facter.value(:domain) == "madstop.com" + + it "should only create the ldap connection when asked for it the first time" + + it "should throw an exception if it cannot connect to LDAP" + + it "should use SSL when the :ldapssl setting is true" + + it "should connect to the server specified in the :ldapserver setting" + + it "should use the port specified in the :ldapport setting" + + it "should use protocol version 3" + + it "should follow referrals" + + it "should use the user specified in the :ldapuser setting" + + it "should use the password specified in the :ldappassord setting" + + it "should have an ldap method that returns an LDAP connection object" + + it "should fail when LDAP support is missing" +end + +describe Puppet::Indirector::Ldap, " when reconnecting to ldap" do + confine "Not running on culain as root" => (Puppet::Util::SUIDManager.uid == 0 and Facter.value("hostname") == "culain") + + it "should reconnect to ldap when connections are lost" +end diff --git a/spec/unit/indirector/ldap/node.rb b/spec/unit/indirector/ldap/node.rb new file mode 100755 index 000000000..edaf77e92 --- /dev/null +++ b/spec/unit/indirector/ldap/node.rb @@ -0,0 +1,84 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/ldap/node' + +describe Puppet::Indirector::Ldap::Node, " when searching for nodes" do + before do + @searcher = Puppet::Indirector::Ldap::Node.new + end + + it "should return the value of the :ldapclassattrs split on commas as the class attributes" do + Puppet.stubs(:[]).with(:ldapclassattrs).returns("one,two") + @searcher.class_attributes.should == %w{one two} + end + + it "should return nil as the parent attribute if the :ldapparentattr is set to an empty string" do + Puppet.stubs(:[]).with(:ldapparentattr).returns("") + @searcher.parent_attribute.should be_nil + end + + it "should return the value of the :ldapparentattr as the parent attribute" do + Puppet.stubs(:[]).with(:ldapparentattr).returns("pere") + @searcher.parent_attribute.should == "pere" + end + + it "should use the value of the :ldapstring as the search filter" do + Puppet.stubs(:[]).with(:ldapstring).returns("mystring") + @searcher.search_filter("testing").should == "mystring" + end + + it "should replace '%s' with the node name in the search filter if it is present" do + Puppet.stubs(:[]).with(:ldapstring).returns("my%sstring") + @searcher.search_filter("testing").should == "mytestingstring" + end + + it "should not modify the global :ldapstring when replacing '%s' in the search filter" do + filter = mock 'filter' + filter.expects(:include?).with("%s").returns(true) + filter.expects(:gsub).with("%s", "testing").returns("mynewstring") + Puppet.stubs(:[]).with(:ldapstring).returns(filter) + @searcher.search_filter("testing").should == "mynewstring" + end +end + +describe Puppet::Indirector::Ldap::Node, " when deciding attributes to search for" do + before do + @searcher = Puppet::Indirector::Ldap::Node.new + end + + it "should use 'nil' if the :ldapattrs setting is 'all'" do + Puppet.stubs(:[]).with(:ldapattrs).returns("all") + @searcher.search_attributes.should be_nil + end + + it "should split the value of :ldapattrs on commas and use the result as the attribute list" do + Puppet.stubs(:[]).with(:ldapattrs).returns("one,two") + @searcher.stubs(:class_attributes).returns([]) + @searcher.stubs(:parent_attribute).returns(nil) + @searcher.search_attributes.should == %w{one two} + end + + it "should add the class attributes to the search attributes if not returning all attributes" do + Puppet.stubs(:[]).with(:ldapattrs).returns("one,two") + @searcher.stubs(:class_attributes).returns(%w{three four}) + @searcher.stubs(:parent_attribute).returns(nil) + # Sort them so i don't have to care about return order + @searcher.search_attributes.sort.should == %w{one two three four}.sort + end + + it "should add the parent attribute to the search attributes if not returning all attributes" do + Puppet.stubs(:[]).with(:ldapattrs).returns("one,two") + @searcher.stubs(:class_attributes).returns([]) + @searcher.stubs(:parent_attribute).returns("parent") + @searcher.search_attributes.sort.should == %w{one two parent}.sort + end + + it "should not add nil parent attributes to the search attributes" do + Puppet.stubs(:[]).with(:ldapattrs).returns("one,two") + @searcher.stubs(:class_attributes).returns([]) + @searcher.stubs(:parent_attribute).returns(nil) + @searcher.search_attributes.should == %w{one two} + end +end diff --git a/spec/unit/indirector/node/external.rb b/spec/unit/indirector/node/external.rb deleted file mode 100755 index c64a6f6e2..000000000 --- a/spec/unit/indirector/node/external.rb +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../../../spec_helper' - -require 'yaml' -require 'puppet/indirector' - -describe Puppet::Indirector.terminus(:node, :external), " when searching for nodes" do - require 'puppet/node' - - before do - Puppet.config[:external_nodes] = "/yay/ness" - @searcher = Puppet::Indirector.terminus(:node, :external).new - - # Set the searcher up so that we do not need to actually call the - # external script. - @searcher.meta_def(:execute) do |command| - name = command.last.chomp - result = {} - - if name =~ /a/ - result[:parameters] = {'one' => command.last + '1', 'two' => command.last + '2'} - end - - if name =~ /p/ - result['classes'] = [1,2,3].collect { |n| command.last + n.to_s } - end - - return YAML.dump(result) - end - end - - it "should throw an exception if the node_source is external but no external node command is set" do - Puppet[:external_nodes] = "none" - proc { @searcher.find("foo") }.should raise_error(ArgumentError) - end - - it "should throw an exception if the external node source is not fully qualified" do - Puppet[:external_nodes] = "mycommand" - proc { @searcher.find("foo") }.should raise_error(ArgumentError) - end - - it "should execute the command with the node name as the only argument" do - command = [Puppet[:external_nodes], "yay"] - @searcher.expects(:execute).with(command).returns("") - @searcher.find("yay") - end - - it "should return a node object" do - @searcher.find("apple").should be_instance_of(Puppet::Node) - end - - it "should set the node's name" do - @searcher.find("apple").name.should == "apple" - end - - # If we use a name that has a 'p' but no 'a', then our test generator - # will return classes but no parameters. - it "should be able to configure a node's classes" do - node = @searcher.find("plum") - node.classes.should == %w{plum1 plum2 plum3} - node.parameters.should == {} - end - - # If we use a name that has an 'a' but no 'p', then our test generator - # will return parameters but no classes. - it "should be able to configure a node's parameters" do - node = @searcher.find("guava") - node.classes.should == [] - node.parameters.should == {"one" => "guava1", "two" => "guava2"} - end - - it "should be able to configure a node's classes and parameters" do - node = @searcher.find("apple") - node.classes.should == %w{apple1 apple2 apple3} - node.parameters.should == {"one" => "apple1", "two" => "apple2"} - end - - it "should merge node facts with returned parameters" do - facts = Puppet::Node::Facts.new("apple", "three" => "four") - Puppet::Node::Facts.expects(:find).with("apple").returns(facts) - node = @searcher.find("apple") - node.parameters["three"].should == "four" - end - - it "should return nil when it cannot find the node" do - @searcher.find("honeydew").should be_nil - end - - # Make sure a nodesearch with arguments works - def test_nodesearch_external_arguments - mapper = mk_node_mapper - Puppet[:external_nodes] = "#{mapper} -s something -p somethingelse" - searcher = mk_searcher(:external) - node = nil - assert_nothing_raised do - node = searcher.nodesearch("apple") - end - assert_instance_of(SimpleNode, node, "did not create 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 - searcher = mk_searcher(:external) - - Puppet[:external_nodes] = mapper - - node = nil - assert_nothing_raised do - node = searcher.nodesearch("apple") - end - assert_instance_of(SimpleNode, node, "did not create node") - end - - after do - Puppet.config.clear - end -end diff --git a/spec/unit/indirector/node/ldap.rb b/spec/unit/indirector/node/ldap.rb deleted file mode 100755 index e4b0cd7d4..000000000 --- a/spec/unit/indirector/node/ldap.rb +++ /dev/null @@ -1,243 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../../../spec_helper' - -require 'yaml' -require 'puppet/indirector' - -describe Puppet::Indirector.terminus(:node, :ldap), " when searching for nodes" do - require 'puppet/node' - - def setup - Puppet.config[:external_nodes] = "/yay/ness" - @searcher = Puppet::Indirector.terminus(:node, :ldap).new - nodetable = {} - @nodetable = nodetable - # Override the ldapsearch definition, so we don't have to actually set it up. - @searcher.meta_def(:ldapsearch) do |name| - nodetable[name] - end - end - - it "should return nil for hosts that cannot be found" do - @searcher.find("foo").should be_nil - end - - it "should return Puppet::Node instances" do - @nodetable["foo"] = [nil, %w{}, {}] - @searcher.find("foo").should be_instance_of(Puppet::Node) - end - - it "should set the node name" do - @nodetable["foo"] = [nil, %w{}, {}] - @searcher.find("foo").name.should == "foo" - end - - it "should set the classes" do - @nodetable["foo"] = [nil, %w{one two}, {}] - @searcher.find("foo").classes.should == %w{one two} - end - - it "should set the parameters" do - @nodetable["foo"] = [nil, %w{}, {"one" => "two"}] - @searcher.find("foo").parameters.should == {"one" => "two"} - end - - it "should set classes and parameters from the parent node" do - @nodetable["foo"] = ["middle", %w{one two}, {"one" => "two"}] - @nodetable["middle"] = [nil, %w{three four}, {"three" => "four"}] - node = @searcher.find("foo") - node.classes.sort.should == %w{one two three four}.sort - node.parameters.should == {"one" => "two", "three" => "four"} - end - - it "should prefer child parameters to parent parameters" do - @nodetable["foo"] = ["middle", %w{}, {"one" => "two"}] - @nodetable["middle"] = [nil, %w{}, {"one" => "four"}] - @searcher.find("foo").parameters["one"].should == "two" - end - - it "should recurse indefinitely through parent relationships" do - @nodetable["foo"] = ["middle", %w{one two}, {"one" => "two"}] - @nodetable["middle"] = ["top", %w{three four}, {"three" => "four"}] - @nodetable["top"] = [nil, %w{five six}, {"five" => "six"}] - node = @searcher.find("foo") - node.parameters.should == {"one" => "two", "three" => "four", "five" => "six"} - node.classes.sort.should == %w{one two three four five six}.sort - end - - # This can stay in the main test suite because it doesn't actually use ldapsearch, - # it just overrides the method so it behaves as though it were hitting ldap. - def test_ldap_nodesearch - - # Make sure we get nothing for nonexistent hosts - node = nil - assert_nothing_raised do - node = searcher.nodesearch("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 = searcher.nodesearch("base") - end - - assert_instance_of(SimpleNode, 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 = searcher.nodesearch("middle") - end - - assert_instance_of(SimpleNode, 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 = searcher.nodesearch("top") - end - - assert_instance_of(SimpleNode, 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 - -describe Puppet::Indirector.terminus(:node, :ldap), " when interacting with ldap" do - 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 = Puppet::Node.new(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 - - it "should have tests" do - raise ArgumentError - end - - def test_ldapsearch - Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" - Puppet[:ldapnodes] = true - - searcher = Object.new - searcher.extend(Node.node_source(:ldap)) - - ldapconnect() - - # 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 = searcher.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 = searcher.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 = searcher.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 - -describe Puppet::Indirector.terminus(:node, :ldap), " when connecting to ldap" do - confine "Not running on culain as root" => (Puppet::Util::SUIDManager.uid == 0 and Facter.value("hostname") == "culain") - - it "should have tests" do - raise ArgumentError - end - - def test_ldapreconnect - Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" - Puppet[:ldapnodes] = true - - searcher = Object.new - searcher.extend(Node.node_source(:ldap)) - hostname = "culain.madstop.com" - - # look for our host - assert_nothing_raised { - parent, classes = searcher.nodesearch(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 = searcher.nodesearch(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 = searcher.nodesearch(hostname) - } - end -end diff --git a/spec/unit/indirector/node/none.rb b/spec/unit/indirector/node/none.rb deleted file mode 100755 index 2329cdfbb..000000000 --- a/spec/unit/indirector/node/none.rb +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../../../spec_helper' -require 'puppet/indirector' -require 'puppet/node/facts' - -describe Puppet::Indirector.terminus(:node, :none), " when searching for nodes" do - before do - Puppet.config[:node_source] = "none" - @searcher = Puppet::Indirector.terminus(:node, :none).new - end - - it "should create a node instance" do - @searcher.find("yay").should be_instance_of(Puppet::Node) - end - - it "should create a new node with the correct name" do - @searcher.find("yay").name.should == "yay" - end - - it "should merge the node's facts" do - facts = Puppet::Node::Facts.new("yay", "one" => "two", "three" => "four") - Puppet::Node::Facts.expects(:find).with("yay").returns(facts) - node = @searcher.find("yay") - node.parameters["one"].should == "two" - node.parameters["three"].should == "four" - end - - after do - Puppet.config.clear - end -end diff --git a/spec/unit/indirector/null.rb b/spec/unit/indirector/null.rb new file mode 100755 index 000000000..9e1dcb07c --- /dev/null +++ b/spec/unit/indirector/null.rb @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' +require 'puppet/indirector/null' + +describe Puppet::Indirector::Null do + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @model = mock 'model' + @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model + Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) + + @null_class = Class.new(Puppet::Indirector::Null) do + def self.to_s + "Testing" + end + end + + @searcher = @null_class.new + end + + it "should return return an instance of the indirected model" do + object = mock 'object' + @model.expects(:new).with("yay").returns object + @searcher.find("yay").should equal(object) + end +end diff --git a/spec/unit/indirector/null/node.rb b/spec/unit/indirector/null/node.rb new file mode 100755 index 000000000..c589e5820 --- /dev/null +++ b/spec/unit/indirector/null/node.rb @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/null/node' + +describe Puppet::Indirector::Null::Node do + before do + @searcher = Puppet::Indirector::Null::Node.new + end + + it "should call node_merge() on the returned node" do + node = mock 'node' + Puppet::Node.expects(:new).with("mynode").returns(node) + node.expects(:fact_merge) + @searcher.find("mynode") + end +end diff --git a/spec/unit/indirector/terminus.rb b/spec/unit/indirector/terminus.rb index 08e7e6ccb..dc86cf315 100755 --- a/spec/unit/indirector/terminus.rb +++ b/spec/unit/indirector/terminus.rb @@ -36,6 +36,10 @@ describe Puppet::Indirector::Terminus do @terminus.should respond_to(:terminus_type) end + it "should support a class-level model attribute" do + @terminus.should respond_to(:model) + end + it "should accept indirection instances as its indirection" do indirection = stub 'indirection', :is_a? => true, :register_terminus_type => nil proc { @terminus.indirection = indirection }.should_not raise_error @@ -177,6 +181,11 @@ describe Puppet::Indirector::Terminus, " when creating terminus classes" do it "should set the subclass's name to the indirection name" do @terminus.name.should == :myindirection end + + it "should set the subclass's model to the indirection model" do + @indirection.expects(:model).returns :yay + @terminus.model.should == :yay + end end describe Puppet::Indirector::Terminus, " when a terminus instance" do @@ -209,4 +218,9 @@ describe Puppet::Indirector::Terminus, " when a terminus instance" do it "should set the instances's type to the abstract terminus type's name" do @terminus.terminus_type.should == :abstract end + + it "should set the instances's model to the indirection's model" do + @indirection.expects(:model).returns :yay + @terminus.model.should == :yay + end end -- cgit