summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/puppet/defaults.rb2
-rw-r--r--lib/puppet/indirector.rb109
-rw-r--r--lib/puppet/indirector/facts/yaml.rb41
-rw-r--r--lib/puppet/indirector/node/external.rb51
-rw-r--r--lib/puppet/indirector/node/ldap.rb138
-rw-r--r--lib/puppet/indirector/node/none.rb10
-rw-r--r--lib/puppet/node.rb121
-rwxr-xr-xlib/puppet/node/facts.rb36
-rwxr-xr-xlib/puppet/util/instance_loader.rb2
-rwxr-xr-xspec/unit/indirector/facts/yaml.rb62
-rwxr-xr-xspec/unit/indirector/indirector.rb79
-rwxr-xr-xspec/unit/node/facts.rb25
-rwxr-xr-xspec/unit/node/node.rb116
-rwxr-xr-xtest/lib/puppettest.rb16
14 files changed, 774 insertions, 34 deletions
diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb
index 78364e786..4b442d094 100644
--- a/lib/puppet/defaults.rb
+++ b/lib/puppet/defaults.rb
@@ -495,7 +495,7 @@ module Puppet
)
self.setdefaults(:facts,
- :factstore => ["yaml",
+ :fact_store => ["yaml",
"The backend store to use for client facts."]
)
diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb
index 0ba538355..b8690d7d5 100644
--- a/lib/puppet/indirector.rb
+++ b/lib/puppet/indirector.rb
@@ -1,25 +1,55 @@
# Manage indirections to termini. They are organized in terms of indirections -
# - e.g., configuration, node, file, certificate -- and each indirection has one
-# or more terminus types defined. The indirection must have its preferred terminus
-# configured via a 'default' in the form of '<indirection>_terminus'; e.g.,
-# 'node_terminus = ldap'.
+# or more terminus types defined. The indirection is configured via the
+# +indirects+ method, which will be called by the class extending itself
+# with this module.
module Puppet::Indirector
+ # LAK:FIXME We need to figure out how to handle documentation for the
+ # different indirection types.
+
+ # A simple class that can function as the base class for indirected types.
+ class Terminus
+ require 'puppet/util/docs'
+ extend Puppet::Util::Docs
+ end
+
+ # This handles creating the terminus classes.
+ require 'puppet/util/classgen'
+ extend Puppet::Util::ClassGen
+
# This manages reading in all of our files for us and then retrieving
# loaded instances. We still have to define the 'newX' method, but this
# does all of the rest -- loading, storing, and retrieving by name.
require 'puppet/util/instance_loader'
- include Puppet::Util::InstanceLoader
+ extend Puppet::Util::InstanceLoader
+
+ # Register a given indirection type. The classes including this module
+ # handle creating terminus instances, but the module itself handles
+ # loading them and managing the classes.
+ def self.register_indirection(name)
+ # Set up autoloading of the appropriate termini.
+ instance_load name, "puppet/indirector/%s" % name
+ end
# Define a new indirection terminus. This method is used by the individual
# termini in their separate files. Again, the autoloader takes care of
# actually loading these files.
- def register_terminus(name, options = {}, &block)
- genclass(name, :hash => instance_hash(indirection.name), :attributes => options, :block => block)
+ # Note that the termini are being registered on the Indirector module, not
+ # on the classes including the module. This allows a given indirection to
+ # be used in multiple classes.
+ def self.register_terminus(indirection, terminus, options = {}, &block)
+ genclass(terminus,
+ :prefix => indirection.to_s.capitalize,
+ :hash => instance_hash(indirection),
+ :attributes => options,
+ :block => block,
+ :parent => options[:parent] || Terminus
+ )
end
# Retrieve a terminus class by indirection and name.
- def terminus(name)
- loaded_instance(name)
+ def self.terminus(indirection, terminus)
+ loaded_instance(indirection, terminus)
end
# Declare that the including class indirects its methods to
@@ -27,12 +57,27 @@ module Puppet::Indirector
# default, not the value -- if it's the value, then it gets
# evaluated at parse time, which is before the user has had a chance
# to override it.
- def indirects(indirection, options)
- @indirection = indirection
- @indirect_terminus = options[:to]
+ # Options are:
+ # +:to+: What parameter to use as the name of the indirection terminus.
+ def indirects(indirection, options = {})
+ if defined?(@indirection)
+ raise ArgumentError, "Already performing an indirection of %s; cannot redirect %s" % [@indirection[:name], indirection]
+ end
+ options[:name] = indirection
+ @indirection = options
+ # Validate the parameter. This requires that indirecting
+ # classes require 'puppet/defaults', because of ordering issues,
+ # but it makes problems much easier to debug.
+ if param_name = options[:to]
+ begin
+ name = Puppet[param_name]
+ rescue
+ raise ArgumentError, "Configuration parameter '%s' for indirection '%s' does not exist'" % [param_name, indirection]
+ end
+ end
# Set up autoloading of the appropriate termini.
- autoload "puppet/indirector/%s" % indirection
+ Puppet::Indirector.register_indirection indirection
end
# Define methods for each of the HTTP methods. These just point to the
@@ -47,30 +92,46 @@ module Puppet::Indirector
# those methods.
[:get, :post, :put, :delete].each do |method_name|
define_method(method_name) do |*args|
- begin
- terminus.send(method_name, *args)
- rescue NoMethodError
- raise ArgumentError, "Indirection category %s does not respond to REST method %s" % [indirection, method_name]
- end
+ redirect(method_name, *args)
end
end
private
+
# Create a new terminus instance.
- def make_terminus(indirection)
+ def make_terminus(name)
# Load our terminus class.
- unless klass = self.class.terminus(indirection, indirection.default)
- raise ArgumentError, "Could not find terminus %s for indirection %s" % [indirection.default, indirection]
+ unless klass = Puppet::Indirector.terminus(@indirection[:name], name)
+ raise ArgumentError, "Could not find terminus %s for indirection %s" % [name, indirection]
end
return klass.new
end
+ # Redirect a given HTTP method.
+ def redirect(method_name, *args)
+ begin
+ terminus.send(method_name, *args)
+ rescue NoMethodError
+ raise ArgumentError, "Indirection category %s does not respond to REST method %s" % [indirection, method_name]
+ end
+ end
+
# Return the singleton terminus for this indirection.
- def terminus
- unless terminus = @termini[indirection.name]
- terminus = @termini[indirection.name] = make_terminus(indirection)
+ def terminus(name = nil)
+ @termini ||= {}
+ # Get the name of the terminus.
+ unless name
+ unless param_name = @indirection[:to]
+ raise ArgumentError, "You must specify an indirection terminus for indirection %s" % @indirection[:name]
+ end
+ name = Puppet[param_name]
+ name = name.intern if name.is_a?(String)
+ end
+
+ unless @termini[name]
+ @termini[name] = make_terminus(name)
end
- terminus
+ @termini[name]
end
end
diff --git a/lib/puppet/indirector/facts/yaml.rb b/lib/puppet/indirector/facts/yaml.rb
new file mode 100644
index 000000000..87860012f
--- /dev/null
+++ b/lib/puppet/indirector/facts/yaml.rb
@@ -0,0 +1,41 @@
+Puppet::Indirector.register_terminus :facts, :yaml do
+ desc "Store client facts as flat files, serialized using YAML."
+
+ # Get a client's facts.
+ def get(node)
+ file = path(node)
+
+ return nil unless FileTest.exists?(file)
+
+ begin
+ values = YAML::load(File.read(file))
+ rescue => detail
+ Puppet.err "Could not load facts for %s: %s" % [node, detail]
+ end
+
+ Puppet::Node::Facts.new(node, values)
+ end
+
+ def initialize
+ Puppet.config.use(:yamlfacts)
+ end
+
+ # Store the facts to disk.
+ def put(facts)
+ File.open(path(facts.name), "w", 0600) do |f|
+ begin
+ f.print YAML::dump(facts.values)
+ rescue => detail
+ Puppet.err "Could not write facts for %s: %s" % [facts.name, detail]
+ end
+ end
+ nil
+ end
+
+ private
+
+ # Return the path to a given node's file.
+ def path(name)
+ File.join(Puppet[:yamlfactdir], name + ".yaml")
+ end
+end
diff --git a/lib/puppet/indirector/node/external.rb b/lib/puppet/indirector/node/external.rb
new file mode 100644
index 000000000..70fc2505a
--- /dev/null
+++ b/lib/puppet/indirector/node/external.rb
@@ -0,0 +1,51 @@
+Puppet::Indirector.register_terminus :node, :external, :fact_merge => true do
+ desc "Call an external program to get node information."
+
+ include Puppet::Util
+ # Look for external node definitions.
+ def get(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 = newnode(name)
+ set = false
+ [:parameters, :classes].each do |param|
+ if value = result[param]
+ node.send(param.to_s + "=", value)
+ set = true
+ end
+ end
+
+ if set
+ return node
+ else
+ return nil
+ end
+ end
+end
diff --git a/lib/puppet/indirector/node/ldap.rb b/lib/puppet/indirector/node/ldap.rb
new file mode 100644
index 000000000..75a912568
--- /dev/null
+++ b/lib/puppet/indirector/node/ldap.rb
@@ -0,0 +1,138 @@
+Puppet::Indirector.register_terminus :node, :ldap, :fact_merge => true do
+ desc "Search in LDAP for node configuration information."
+
+ # Look for our node in ldap.
+ def get(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 newnode(node, :classes => classes, :source => "ldap", :parameters => parameters)
+ 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
new file mode 100644
index 000000000..ce188add5
--- /dev/null
+++ b/lib/puppet/indirector/node/none.rb
@@ -0,0 +1,10 @@
+Puppet::Network::Handler::Node.newnode_source(:none, :fact_merge => true) 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 nodesearch(name)
+ newnode(name)
+ end
+end
diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb
index 2d3ac712e..f04de91c5 100644
--- a/lib/puppet/node.rb
+++ b/lib/puppet/node.rb
@@ -1,10 +1,129 @@
# A simplistic class for managing the node information itself.
class Puppet::Node
+ # Set up indirection, so that nodes can be looked for in
+ # the node sources.
+ require 'puppet/indirector'
+ extend Puppet::Indirector
+
+ # Use the node source as the indirection terminus.
+ indirects :node, :to => :node_source
+
+ # Retrieve a node from the node source, with some additional munging
+ # thrown in for kicks.
+ # LAK:FIXME Crap. This won't work, because we might have two copies of this class,
+ # one remote and one local, and we won't know which one should do all of the
+ # extra crap.
+ def self.get(key)
+ return nil unless key
+ if node = cached?(key)
+ return node
+ end
+ facts = node_facts(key)
+ node = nil
+ names = node_names(key, facts)
+ names.each do |name|
+ name = name.to_s if name.is_a?(Symbol)
+ if node = nodesearch(name) and @source != "none"
+ Puppet.info "Found %s in %s" % [name, @source]
+ break
+ end
+ end
+
+ # If they made it this far, we haven't found anything, so look for a
+ # default node.
+ unless node or names.include?("default")
+ if node = nodesearch("default")
+ Puppet.notice "Using default node for %s" % key
+ end
+ end
+
+ if node
+ node.source = @source
+ node.names = names
+
+ # Merge the facts into the parameters.
+ if fact_merge?
+ node.fact_merge(facts)
+ end
+
+ cache(node)
+
+ return node
+ else
+ return nil
+ end
+ end
+
+ private
+
+ # Store the node to make things a bit faster.
+ def self.cache(node)
+ @node_cache[node.name] = node
+ end
+
+ # If the node is cached, return it.
+ def self.cached?(name)
+ # Don't use cache when the filetimeout is set to 0
+ return false if [0, "0"].include?(Puppet[:filetimeout])
+
+ if node = @node_cache[name] and Time.now - node.time < Puppet[:filetimeout]
+ return node
+ else
+ return false
+ end
+ end
+
+ # Look up the node facts from our fact handler.
+ def self.node_facts(key)
+ if facts = Puppet::Node::Facts.get(key)
+ facts.values
+ else
+ {}
+ end
+ end
+
+ # Calculate the list of node names we should use for looking
+ # up our node.
+ def self.node_names(key, facts = nil)
+ facts ||= node_facts(key)
+ names = []
+
+ if hostname = facts["hostname"]
+ unless hostname == key
+ names << hostname
+ end
+ else
+ hostname = key
+ end
+
+ if fqdn = facts["fqdn"]
+ hostname = fqdn
+ names << fqdn
+ end
+
+ # Make sure both the fqdn and the short name of the
+ # host can be used in the manifest
+ if hostname =~ /\./
+ names << hostname.sub(/\..+/,'')
+ elsif domain = facts['domain']
+ names << hostname + "." + domain
+ end
+
+ # Sort the names inversely by name length.
+ names.sort! { |a,b| b.length <=> a.length }
+
+ # And make sure the key is first, since that's the most
+ # likely usage.
+ ([key] + names).uniq
+ end
+
+ public
+
attr_accessor :name, :classes, :parameters, :source, :ipaddress, :names
attr_reader :time
attr_writer :environment
- # Do not return environments tha are empty string, and use
+ # Do not return environments that are the empty string, and use
# explicitly set environments, then facts, then a central env
# value.
def environment
diff --git a/lib/puppet/node/facts.rb b/lib/puppet/node/facts.rb
new file mode 100755
index 000000000..eddf44def
--- /dev/null
+++ b/lib/puppet/node/facts.rb
@@ -0,0 +1,36 @@
+# Manage a given node's facts. This either accepts facts and stores them, or
+# returns facts for a given node.
+class Puppet::Node::Facts
+ # Set up indirection, so that nodes can be looked for in
+ # the node sources.
+ require 'puppet/indirector'
+ extend Puppet::Indirector
+
+ # Use the node source as the indirection terminus.
+ indirects :facts, :to => :fact_store
+
+ attr_accessor :name, :values
+
+ def initialize(name, values = {})
+ @name = name
+ @values = values
+ end
+
+ private
+
+ # FIXME These methods are currently unused.
+
+ # Add internal data to the facts for storage.
+ def add_internal(facts)
+ facts = facts.dup
+ facts[:_puppet_timestamp] = Time.now
+ facts
+ end
+
+ # Strip out that internal data.
+ def strip_internal(facts)
+ facts = facts.dup
+ facts.find_all { |name, value| name.to_s =~ /^_puppet_/ }.each { |name, value| facts.delete(name) }
+ facts
+ end
+end
diff --git a/lib/puppet/util/instance_loader.rb b/lib/puppet/util/instance_loader.rb
index 1a64c9c69..bc0567866 100755
--- a/lib/puppet/util/instance_loader.rb
+++ b/lib/puppet/util/instance_loader.rb
@@ -70,5 +70,3 @@ module Puppet::Util::InstanceLoader
instances[name]
end
end
-
-# $Id$
diff --git a/spec/unit/indirector/facts/yaml.rb b/spec/unit/indirector/facts/yaml.rb
new file mode 100755
index 000000000..45c079a69
--- /dev/null
+++ b/spec/unit/indirector/facts/yaml.rb
@@ -0,0 +1,62 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+require 'puppet/indirector'
+require 'puppet/node/facts'
+require 'puppettest'
+
+describe Puppet::Indirector.terminus(:facts, :yaml), " when managing facts" do
+ # For cleanup mechanisms.
+ include PuppetTest
+
+ # LAK:FIXME It seems like I really do have to hit the filesystem
+ # here, since, like, that's what I'm testing. Is there another/better
+ # way to do this?
+ before do
+ @store = Puppet::Indirector.terminus(:facts, :yaml).new
+ setup # Grr, stupid rspec
+ Puppet[:yamlfactdir] = tempfile
+ Dir.mkdir(Puppet[:yamlfactdir])
+ end
+
+ it "should store facts in YAML in the yamlfactdir" do
+ values = {"one" => "two", "three" => "four"}
+ facts = Puppet::Node::Facts.new("node", values)
+ @store.put(facts)
+
+ # Make sure the file exists
+ path = File.join(Puppet[:yamlfactdir], facts.name) + ".yaml"
+ File.exists?(path).should be_true
+
+ # And make sure it's right
+ newvals = YAML.load(File.read(path))
+
+ # We iterate over them, because the store might add extra values.
+ values.each do |name, value|
+ newvals[name].should == value
+ end
+ end
+
+ it "should retrieve values from disk" do
+ values = {"one" => "two", "three" => "four"}
+
+ # Create the file.
+ path = File.join(Puppet[:yamlfactdir], "node") + ".yaml"
+ File.open(path, "w") do |f|
+ f.print values.to_yaml
+ end
+
+ facts = Puppet::Node::Facts.get('node')
+ facts.should be_instance_of(Puppet::Node::Facts)
+
+ # We iterate over them, because the store might add extra values.
+ values.each do |name, value|
+ facts.values[name].should == value
+ end
+ end
+
+ after do
+ teardown
+ end
+end
diff --git a/spec/unit/indirector/indirector.rb b/spec/unit/indirector/indirector.rb
new file mode 100755
index 000000000..c4221febb
--- /dev/null
+++ b/spec/unit/indirector/indirector.rb
@@ -0,0 +1,79 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../spec_helper'
+
+require 'puppet/defaults'
+require 'puppet/indirector'
+
+describe Puppet::Indirector, " when managing indirections" do
+ before do
+ @indirector = Object.new
+ @indirector.send(:extend, Puppet::Indirector)
+ end
+
+ # LAK:FIXME This seems like multiple tests, but I don't really know how to test one at a time.
+ it "should accept specification of an indirection terminus via a configuration parameter" do
+ @indirector.indirects :test, :to => :node_source
+ Puppet[:node_source] = "test_source"
+ klass = mock 'terminus_class'
+ terminus = mock 'terminus'
+ klass.expects(:new).returns terminus
+ Puppet::Indirector.expects(:terminus).with(:test, :test_source).returns(klass)
+ @indirector.send(:terminus).should equal(terminus)
+ end
+
+ it "should not allow more than one indirection in the same object" do
+ @indirector.indirects :test
+ proc { @indirector.indirects :else }.should raise_error(ArgumentError)
+ end
+
+ it "should allow multiple classes to use the same indirection" do
+ @indirector.indirects :test
+ other = Object.new
+ other.send(:extend, Puppet::Indirector)
+ proc { other.indirects :test }.should_not raise_error
+ end
+end
+
+describe Puppet::Indirector, " when managing termini" do
+ before do
+ @indirector = Object.new
+ @indirector.send(:extend, Puppet::Indirector)
+ end
+
+ it "should should autoload termini from disk" do
+ Puppet::Indirector.expects(:instance_load).with(:test, "puppet/indirector/test")
+ @indirector.indirects :test
+ end
+end
+
+describe Puppet::Indirector, " when performing indirections" do
+ before do
+ @indirector = Object.new
+ @indirector.send(:extend, Puppet::Indirector)
+ @indirector.indirects :test, :to => :node_source
+
+ # Set up a fake terminus class that will just be used to spit out
+ # mock terminus objects.
+ @terminus_class = mock 'terminus_class'
+ Puppet::Indirector.stubs(:terminus).with(:test, :test_source).returns(@terminus_class)
+ Puppet[:node_source] = "test_source"
+ end
+
+ it "should redirect http methods to the default terminus" do
+ terminus = mock 'terminus'
+ terminus.expects(:put).with("myargument")
+ @terminus_class.expects(:new).returns(terminus)
+ @indirector.put("myargument")
+ end
+
+ # Make sure it caches the terminus.
+ it "should use the same terminus for all indirections" do
+ terminus = mock 'terminus'
+ terminus.expects(:put).with("myargument")
+ terminus.expects(:get).with("other_argument")
+ @terminus_class.expects(:new).returns(terminus)
+ @indirector.put("myargument")
+ @indirector.get("other_argument")
+ end
+end
diff --git a/spec/unit/node/facts.rb b/spec/unit/node/facts.rb
new file mode 100755
index 000000000..c7fc65f38
--- /dev/null
+++ b/spec/unit/node/facts.rb
@@ -0,0 +1,25 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../spec_helper'
+
+require 'puppet/node/facts'
+
+describe Puppet::Node::Facts, " when indirecting" do
+ before do
+ Puppet[:fact_store] = "test_store"
+ @terminus_class = mock 'terminus_class'
+ @terminus = mock 'terminus'
+ @terminus_class.expects(:new).returns(@terminus)
+ Puppet::Indirector.expects(:terminus).with(:facts, :test_store).returns(@terminus_class)
+ end
+
+ it "should redirect to the specified fact store for retrieval" do
+ @terminus.expects(:get).with(:my_facts)
+ Puppet::Node::Facts.get(:my_facts)
+ end
+
+ it "should redirect to the specified fact store for storage" do
+ @terminus.expects(:put).with(:my_facts)
+ Puppet::Node::Facts.put(:my_facts)
+ end
+end
diff --git a/spec/unit/node/node.rb b/spec/unit/node/node.rb
new file mode 100755
index 000000000..a6cc1e301
--- /dev/null
+++ b/spec/unit/node/node.rb
@@ -0,0 +1,116 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../spec_helper'
+
+describe Puppet::Node, " when initializing" do
+ before do
+ @node = Puppet::Node.new("testnode")
+ end
+
+ it "should set the node name" do
+ @node.name.should == "testnode"
+ end
+
+ it "should default to an empty parameter hash" do
+ @node.parameters.should == {}
+ end
+
+ it "should default to an empty class array" do
+ @node.classes.should == []
+ end
+
+ it "should note its creation time" do
+ @node.time.should be_instance_of(Time)
+ end
+
+ it "should accept parameters passed in during initialization" do
+ params = {"a" => "b"}
+ @node = Puppet::Node.new("testing", :parameters => params)
+ @node.parameters.should == params
+ end
+
+ it "should accept classes passed in during initialization" do
+ classes = %w{one two}
+ @node = Puppet::Node.new("testing", :classes => classes)
+ @node.classes.should == classes
+ end
+
+ it "should always return classes as an array" do
+ @node = Puppet::Node.new("testing", :classes => "myclass")
+ @node.classes.should == ["myclass"]
+ end
+
+ it "should accept the environment during initialization" do
+ @node = Puppet::Node.new("testing", :environment => "myenv")
+ @node.environment.should == "myenv"
+ end
+
+ it "should accept names passed in" do
+ @node = Puppet::Node.new("testing", :names => ["myenv"])
+ @node.names.should == ["myenv"]
+ end
+end
+
+describe Puppet::Node, " when returning the environment" do
+ before do
+ @node = Puppet::Node.new("testnode")
+ end
+
+ it "should return the 'environment' fact if present and there is no explicit environment" do
+ @node.parameters = {"environment" => "myenv"}
+ @node.environment.should == "myenv"
+ end
+
+ it "should return the central environment if there is no environment fact nor explicit environment" do
+ Puppet.config.expects(:[]).with(:environment).returns(:centralenv)
+ @node.environment.should == :centralenv
+ end
+
+ it "should not use an explicit environment that is an empty string" do
+ @node.environment == ""
+ @node.environment.should be_nil
+ end
+
+ it "should not use an environment fact that is an empty string" do
+ @node.parameters = {"environment" => ""}
+ @node.environment.should be_nil
+ end
+
+ it "should not use an explicit environment that is an empty string" do
+ Puppet.config.expects(:[]).with(:environment).returns(nil)
+ @node.environment.should be_nil
+ end
+end
+
+describe Puppet::Node, " when merging facts" do
+ before do
+ @node = Puppet::Node.new("testnode")
+ end
+
+ it "should prefer parameters already set on the node over facts from the node" do
+ @node.parameters = {"one" => "a"}
+ @node.fact_merge("one" => "c")
+ @node.parameters["one"].should == "a"
+ end
+
+ it "should add passed parameters to the parameter list" do
+ @node.parameters = {"one" => "a"}
+ @node.fact_merge("two" => "b")
+ @node.parameters["two"].should == "b"
+ end
+end
+
+describe Puppet::Node, " when indirecting" do
+ before do
+ Puppet[:node_source] = :test_source
+ @terminus_class = mock 'terminus_class'
+ @terminus = mock 'terminus'
+ @terminus_class.expects(:new).returns(@terminus)
+ Puppet::Indirector.expects(:terminus).with(:node, :test_source).returns(@terminus_class)
+ end
+
+ it "should redirect to the specified node source" do
+ @terminus.expects(:get).with(:my_node)
+ Puppet::Node.get(:my_node)
+ end
+end
diff --git a/test/lib/puppettest.rb b/test/lib/puppettest.rb
index b56bc563e..45c5b2ed9 100755
--- a/test/lib/puppettest.rb
+++ b/test/lib/puppettest.rb
@@ -5,7 +5,12 @@ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../../lib
require 'puppet'
require 'mocha'
-require 'test/unit'
+
+# Only load the test/unit class if we're not in the spec directory.
+# Else we get the bogus 'no tests, no failures' message.
+unless Dir.getwd =~ /spec/
+ require 'test/unit'
+end
# Yay; hackish but it works
if ARGV.include?("-d")
@@ -141,7 +146,7 @@ module PuppetTest
end
@configpath = File.join(tmpdir,
- self.class.to_s + "configdir" + @@testcount.to_s + "/"
+ "configdir" + @@testcount.to_s + "/"
)
unless defined? $user and $group
@@ -197,8 +202,7 @@ module PuppetTest
@@tmpfilenum = 1
end
- f = File.join(self.tmpdir(), self.class.to_s + "_" + @method_name.to_s +
- @@tmpfilenum.to_s)
+ f = File.join(self.tmpdir(), "tempfile_" + @@tmpfilenum.to_s)
@@tmpfiles << f
return f
end
@@ -223,11 +227,11 @@ module PuppetTest
when "Darwin": "/private/tmp"
when "SunOS": "/var/tmp"
else
- "/tmp"
+ "/tmp"
end
- @tmpdir = File.join(@tmpdir, "puppettesting")
+ @tmpdir = File.join(@tmpdir, "puppettesting" + Process.pid.to_s)
unless File.exists?(@tmpdir)
FileUtils.mkdir_p(@tmpdir)