diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb
index b8690d7d5..704039abc 100644
--- a/lib/puppet/indirector.rb
+++ b/lib/puppet/indirector.rb
@@ -1,137 +1,118 @@
# 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 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
+
+ class << self
+ attr_accessor :name, :indirection
+ end
+ def name
+ self.class.name
+ end
+ def indirection
+ self.class.indirection
+ end
end
+ require 'puppet/indirector/indirection'
+
# 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'
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.
# 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,
+ klass = genclass(terminus,
:prefix => indirection.to_s.capitalize,
:hash => instance_hash(indirection),
:attributes => options,
:block => block,
:parent => options[:parent] || Terminus
)
+ klass.indirection = indirection
+ klass.name = terminus
end
# Retrieve a terminus class by indirection and name.
def self.terminus(indirection, terminus)
loaded_instance(indirection, terminus)
end
# Declare that the including class indirects its methods to
# this terminus. The terminus name must be the name of a Puppet
# 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.
# 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]
+ raise ArgumentError, "Already performing an indirection of %s; cannot redirect %s" % [@indirection.name, indirection]
end
- options[:name] = indirection
- @indirection = options
+ @indirection = Indirection.new(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.
Puppet::Indirector.register_indirection indirection
+
+ return @indirection
end
# Define methods for each of the HTTP methods. These just point to the
# termini, with consistent error-handling. Each method is called with
# the first argument being the indirection type and the rest of the
# arguments passed directly on to the indirection terminus. There is
# currently no attempt to standardize around what the rest of the arguments
# should allow or include or whatever.
# There is also no attempt to pre-validate that a given indirection supports
# the method in question. We should probably require that indirections
# declare supported methods, and then verify that termini implement all of
# those methods.
[:get, :post, :put, :delete].each do |method_name|
define_method(method_name) do |*args|
redirect(method_name, *args)
end
end
private
-
- # Create a new terminus instance.
- def make_terminus(name)
- # Load our terminus class.
- 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(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]
+ @indirection.terminus.send(method_name, *args)
+ rescue NoMethodError => detail
+ if Puppet[:trace]
+ puts detail.backtrace
end
- name = Puppet[param_name]
- name = name.intern if name.is_a?(String)
- end
-
- unless @termini[name]
- @termini[name] = make_terminus(name)
+ raise ArgumentError, "The %s terminus of the %s indirection failed to respond to %s: %s" %
+ [@indirection.terminus.name, @indirection.name, method_name, detail]
end
- @termini[name]
end
end
diff --git a/lib/puppet/indirector/facts/yaml.rb b/lib/puppet/indirector/facts/yaml.rb
index 87860012f..f29ea8ebc 100644
--- a/lib/puppet/indirector/facts/yaml.rb
+++ b/lib/puppet/indirector/facts/yaml.rb
@@ -1,41 +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)
+ def post(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/indirection.rb b/lib/puppet/indirector/indirection.rb
new file mode 100644
index 000000000..7a4c4bd55
--- /dev/null
+++ b/lib/puppet/indirector/indirection.rb
@@ -0,0 +1,74 @@
+# An actual indirection.
+class Puppet::Indirector::Indirection
+ @@indirections = []
+
+ # Clear all cached termini from all indirections.
+ def self.clear_cache
+ @@indirections.each { |ind| ind.clear_cache }
+ end
+
+ attr_accessor :name, :termini
+ attr_reader :to
+
+ # Clear our cached list of termini.
+ # This is only used for testing.
+ def clear_cache
+ @termini.clear
+ end
+
+ # This is only used for testing.
+ def delete
+ @@indirections.delete(self) if @@indirections.include?(self)
+ end
+
+ def initialize(name, options = {})
+ @name = name
+ options.each do |name, value|
+ begin
+ send(name.to_s + "=", value)
+ rescue NoMethodError
+ raise ArgumentError, "%s is not a valid Indirection parameter" % name
+ end
+ end
+ @termini = {}
+ @@indirections << self
+ end
+
+ # Return the singleton terminus for this indirection.
+ def terminus(name = nil)
+ # Get the name of the terminus.
+ unless name
+ unless param_name = self.to
+ raise ArgumentError, "You must specify an indirection terminus for indirection %s" % self.name
+ end
+ name = Puppet[param_name]
+ name = name.intern if name.is_a?(String)
+ end
+
+ unless @termini[name]
+ @termini[name] = make_terminus(name)
+ end
+ @termini[name]
+ end
+
+ # Validate the parameter. This requires that indirecting
+ # classes require 'puppet/defaults', because of ordering issues,
+ # but it makes problems much easier to debug.
+ def to=(param_name)
+ unless Puppet.config.valid?(param_name)
+ raise ArgumentError, "Configuration parameter '%s' for indirection '%s' does not exist'" % [param_name, self.name]
+ end
+ @to = param_name
+ end
+
+ private
+
+ # Create a new terminus instance.
+ def make_terminus(name)
+ # Load our terminus class.
+ unless klass = Puppet::Indirector.terminus(self.name, name)
+ raise ArgumentError, "Could not find terminus %s for indirection %s" % [name, self.name]
+ end
+ return klass.new
+ end
+end
diff --git a/lib/puppet/indirector/node/external.rb b/lib/puppet/indirector/node/external.rb
index ed2a8893e..13cd265fb 100644
--- a/lib/puppet/indirector/node/external.rb
+++ b/lib/puppet/indirector/node/external.rb
@@ -1,55 +1,89 @@
+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 get(name)
- return nil unless Puppet[:external_nodes] != "none"
+ 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 = Puppet::Util.execute(external_node_command)
+ 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
- result = YAML.load(output).inject({}) { |hash, data| hash[symbolize(data[0])] = data[1]; hash }
+ 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 = Puppe::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 facts = Puppet::Node.facts(name)
- node.fact_merge(facts)
- 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
index 77be04126..fb60cad31 100644
--- a/lib/puppet/indirector/node/ldap.rb
+++ b/lib/puppet/indirector/node/ldap.rb
@@ -1,142 +1,140 @@
Puppet::Indirector.register_terminus :node, :ldap do
desc "Search in LDAP for node configuration information."
# Look for our node in ldap.
def get(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 = Puppe::Node.new(name, :classes => classes, :source => "ldap", :parameters => parameters)
- if facts = Puppet::Node.facts(name)
- node.fact_merge(facts)
- 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
index 7143033d9..2b326968e 100644
--- a/lib/puppet/indirector/node/none.rb
+++ b/lib/puppet/indirector/node/none.rb
@@ -1,14 +1,14 @@
+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 get(name)
node = Puppet::Node.new(name)
- if facts = Puppet::Node.facts(name)
- node.fact_merge(facts)
- end
+ node.fact_merge
node
end
end
diff --git a/lib/puppet/network/handler/configuration.rb b/lib/puppet/network/handler/configuration.rb
index 372e80325..2df1b3ab4 100644
--- a/lib/puppet/network/handler/configuration.rb
+++ b/lib/puppet/network/handler/configuration.rb
@@ -1,212 +1,212 @@
require 'openssl'
require 'puppet'
require 'puppet/parser/interpreter'
require 'puppet/sslcertificates'
require 'xmlrpc/server'
require 'yaml'
class Puppet::Network::Handler
class Configuration < Handler
desc "Puppet's configuration compilation interface. Passed a node name
or other key, retrieves information about the node (using the ``node_source``)
and returns a compiled configuration."
include Puppet::Util
attr_accessor :local
@interface = XMLRPC::Service::Interface.new("configuration") { |iface|
iface.add_method("string configuration(string)")
iface.add_method("string version()")
}
# Compile a node's configuration.
def configuration(key, client = nil, clientip = nil)
# If we want to use the cert name as our key
if Puppet[:node_name] == 'cert' and client
key = client
end
# Note that this is reasonable, because either their node source should actually
# know about the node, or they should be using the ``none`` node source, which
# will always return data.
unless node = Puppet::Node.search(key)
raise Puppet::Error, "Could not find node '%s'" % key
end
# Add any external data to the node.
add_node_data(node)
configuration = compile(node)
return translate(configuration)
end
def initialize(options = {})
if options[:Local]
@local = options[:Local]
else
@local = false
end
# Just store the options, rather than creating the interpreter
# immediately. Mostly, this is so we can create the interpreter
# on-demand, which is easier for testing.
@options = options
set_server_facts
end
# Are we running locally, or are our clients networked?
def local?
self.local
end
# Return the configuration version.
def version(client = nil, clientip = nil)
if client and node = Puppet::Node.search(client)
update_node_check(node)
return interpreter.configuration_version(node)
else
# Just return something that will always result in a recompile, because
# this is local.
return (Time.now + 1000).to_i
end
end
private
# Add any extra data necessary to the node.
def add_node_data(node)
# Merge in our server-side facts, so they can be used during compilation.
- node.fact_merge(@server_facts)
+ node.merge(@server_facts)
# Add any specified classes to the node's class list.
if classes = @options[:Classes]
classes.each do |klass|
node.classes << klass
end
end
end
# Compile the actual configuration.
def compile(node)
# Pick the benchmark level.
if local?
level = :none
else
level = :notice
end
# Ask the interpreter to compile the configuration.
str = "Compiled configuration for %s" % node.name
if node.environment
str += " in environment %s" % node.environment
end
config = nil
benchmark(level, "Compiled configuration for %s" % node.name) do
begin
config = interpreter.compile(node)
rescue Puppet::Error => detail
if Puppet[:trace]
puts detail.backtrace
end
unless local?
Puppet.err detail.to_s
end
raise XMLRPC::FaultException.new(
1, detail.to_s
)
end
end
return config
end
# Create our interpreter object.
def create_interpreter(options)
args = {}
# Allow specification of a code snippet or of a file
if code = options[:Code]
args[:Code] = code
elsif options[:Manifest]
args[:Manifest] = options[:Manifest]
end
args[:Local] = local?
if options.include?(:UseNodes)
args[:UseNodes] = options[:UseNodes]
elsif @local
args[:UseNodes] = false
end
# This is only used by the cfengine module, or if --loadclasses was
# specified in +puppet+.
if options.include?(:Classes)
args[:Classes] = options[:Classes]
end
return Puppet::Parser::Interpreter.new(args)
end
# Create/return our interpreter.
def interpreter
unless defined?(@interpreter) and @interpreter
@interpreter = create_interpreter(@options)
end
@interpreter
end
# Initialize our server fact hash; we add these to each client, and they
# won't change while we're running, so it's safe to cache the values.
def set_server_facts
@server_facts = {}
# Add our server version to the fact list
@server_facts["serverversion"] = Puppet.version.to_s
# And then add the server name and IP
{"servername" => "fqdn",
"serverip" => "ipaddress"
}.each do |var, fact|
if value = Facter.value(fact)
@server_facts[var] = value
else
Puppet.warning "Could not retrieve fact %s" % fact
end
end
if @server_facts["servername"].nil?
host = Facter.value(:hostname)
if domain = Facter.value(:domain)
@server_facts["servername"] = [host, domain].join(".")
else
@server_facts["servername"] = host
end
end
end
# Translate our configuration appropriately for sending back to a client.
def translate(config)
if local?
config
else
CGI.escape(config.to_yaml(:UseBlock => true))
end
end
# Mark that the node has checked in. FIXME this needs to be moved into
# the Node class, or somewhere that's got abstract backends.
def update_node_check(node)
if Puppet.features.rails? and Puppet[:storeconfigs]
Puppet::Rails.connect
host = Puppet::Rails::Host.find_or_create_by_name(node.name)
host.last_freshcheck = Time.now
host.save
end
end
end
end
diff --git a/lib/puppet/network/handler/facts.rb b/lib/puppet/network/handler/facts.rb
deleted file mode 100755
index 4767e8be4..000000000
--- a/lib/puppet/network/handler/facts.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-require 'yaml'
-require 'puppet/util/fact_store'
-
-class Puppet::Network::Handler
- # Receive logs from remote hosts.
- class Facts < Handler
- desc "An interface for storing and retrieving client facts. Currently only
- used internally by Puppet."
-
- @interface = XMLRPC::Service::Interface.new("facts") { |iface|
- iface.add_method("void set(string, string)")
- iface.add_method("string get(string)")
- iface.add_method("integer store_date(string)")
- }
-
- def initialize(hash = {})
- super
-
- backend = Puppet[:factstore]
-
- unless klass = Puppet::Util::FactStore.store(backend)
- raise Puppet::Error, "Could not find fact store %s" % backend
- end
-
- @backend = klass.new
- end
-
- # Get the facts from our back end.
- def get(node)
- if facts = @backend.get(node)
- return strip_internal(facts)
- else
- return nil
- end
- end
-
- # Set the facts in the backend.
- def set(node, facts)
- @backend.set(node, add_internal(facts))
- nil
- end
-
- # Retrieve a client's storage date.
- def store_date(node)
- if facts = get(node)
- facts[:_puppet_timestamp].to_i
- else
- nil
- end
- end
-
- private
-
- # 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
-end
diff --git a/lib/puppet/network/handler/fileserver.rb b/lib/puppet/network/handler/fileserver.rb
index a429412d2..993e9d51a 100755
--- a/lib/puppet/network/handler/fileserver.rb
+++ b/lib/puppet/network/handler/fileserver.rb
@@ -1,684 +1,676 @@
require 'puppet'
require 'puppet/network/authstore'
require 'webrick/httpstatus'
require 'cgi'
require 'delegate'
require 'sync'
class Puppet::Network::Handler
AuthStoreError = Puppet::AuthStoreError
class FileServerError < Puppet::Error; end
class FileServer < Handler
desc "The interface to Puppet's fileserving abilities."
attr_accessor :local
CHECKPARAMS = [:mode, :type, :owner, :group, :checksum]
# Special filserver module for puppet's module system
MODULES = "modules"
@interface = XMLRPC::Service::Interface.new("fileserver") { |iface|
iface.add_method("string describe(string, string)")
iface.add_method("string list(string, string, boolean, array)")
iface.add_method("string retrieve(string, string)")
}
def self.params
CHECKPARAMS.dup
end
# Describe a given file. This returns all of the manageable aspects
# of that file.
def describe(url, links = :ignore, client = nil, clientip = nil)
links = links.intern if links.is_a? String
if links == :manage
raise Puppet::Network::Handler::FileServerError, "Cannot currently copy links"
end
mount, path = convert(url, client, clientip)
if client
mount.debug "Describing %s for %s" % [url, client]
end
obj = nil
unless obj = mount.getfileobject(path, links)
return ""
end
currentvalues = mount.check(obj)
desc = []
CHECKPARAMS.each { |check|
if value = currentvalues[check]
desc << value
else
if check == "checksum" and currentvalues[:type] == "file"
mount.notice "File %s does not have data for %s" %
[obj.name, check]
end
desc << nil
end
}
return desc.join("\t")
end
# Create a new fileserving module.
def initialize(hash = {})
@mounts = {}
@files = {}
if hash[:Local]
@local = hash[:Local]
else
@local = false
end
if hash[:Config] == false
@noreadconfig = true
else
@config = Puppet::Util::LoadedFile.new(
hash[:Config] || Puppet[:fileserverconfig]
)
@noreadconfig = false
end
if hash.include?(:Mount)
@passedconfig = true
unless hash[:Mount].is_a?(Hash)
raise Puppet::DevError, "Invalid mount hash %s" %
hash[:Mount].inspect
end
hash[:Mount].each { |dir, name|
if FileTest.exists?(dir)
self.mount(dir, name)
end
}
self.mount(nil, MODULES)
else
@passedconfig = false
readconfig(false) # don't check the file the first time.
end
end
# List a specific directory's contents.
def list(url, links = :ignore, recurse = false, ignore = false, client = nil, clientip = nil)
mount, path = convert(url, client, clientip)
if client
mount.debug "Listing %s for %s" % [url, client]
end
obj = nil
unless FileTest.exists?(path)
return ""
end
# We pass two paths here, but reclist internally changes one
# of the arguments when called internally.
desc = reclist(mount, path, path, recurse, ignore)
if desc.length == 0
mount.notice "Got no information on //%s/%s" %
[mount, path]
return ""
end
desc.collect { |sub|
sub.join("\t")
}.join("\n")
end
def local?
self.local
end
# Mount a new directory with a name.
def mount(path, name)
if @mounts.include?(name)
if @mounts[name] != path
raise FileServerError, "%s is already mounted at %s" %
[@mounts[name].path, name]
else
# it's already mounted; no problem
return
end
end
# Let the mounts do their own error-checking.
@mounts[name] = Mount.new(name, path)
@mounts[name].info "Mounted %s" % path
return @mounts[name]
end
# Retrieve a file from the local disk and pass it to the remote
# client.
def retrieve(url, links = :ignore, client = nil, clientip = nil)
links = links.intern if links.is_a? String
mount, path = convert(url, client, clientip)
if client
mount.info "Sending %s to %s" % [url, client]
end
unless FileTest.exists?(path)
return ""
end
links = links.intern if links.is_a? String
if links == :ignore and FileTest.symlink?(path)
return ""
end
str = nil
if links == :manage
raise Puppet::Error, "Cannot copy links yet."
else
str = File.read(path)
end
if @local
return str
else
return CGI.escape(str)
end
end
def umount(name)
@mounts.delete(name) if @mounts.include? name
end
private
def authcheck(file, mount, client, clientip)
# If we're local, don't bother passing in information.
if local?
client = nil
clientip = nil
end
unless mount.allowed?(client, clientip)
mount.warning "%s cannot access %s" %
[client, file]
raise Puppet::AuthorizationError, "Cannot access %s" % mount
end
end
def convert(url, client, clientip)
readconfig
url = URI.unescape(url)
mount, stub = splitpath(url, client)
authcheck(url, mount, client, clientip)
path = nil
unless path = mount.subdir(stub, client)
mount.notice "Could not find subdirectory %s" %
"//%s/%s" % [mount, stub]
return ""
end
return mount, path
end
# Deal with ignore parameters.
def handleignore(children, path, ignore)
ignore.each { |ignore|
Dir.glob(File.join(path,ignore), File::FNM_DOTMATCH) { |match|
children.delete(File.basename(match))
}
}
return children
end
# Return the mount for the Puppet modules; allows file copying from
# the modules.
def modules_mount(module_name, client)
# Find our environment, if we have one.
- if node = node_handler.details(client || Facter.value("hostname"))
+ if node = Puppet::Node.get(client || Facter.value("hostname"))
env = node.environment
else
env = nil
end
# And use the environment to look up the module.
mod = Puppet::Module::find(module_name, env)
if mod
return @mounts[MODULES].copy(mod.name, mod.files)
else
return nil
end
end
- # Create a node handler instance for looking up our nodes.
- def node_handler
- unless defined?(@node_handler)
- @node_handler = Puppet::Network::Handler.handler(:node).create
- end
- @node_handler
- end
-
# Read the configuration file.
def readconfig(check = true)
return if @noreadconfig
if check and ! @config.changed?
return
end
newmounts = {}
begin
File.open(@config.file) { |f|
mount = nil
count = 1
f.each { |line|
case line
when /^\s*#/: next # skip comments
when /^\s*$/: next # skip blank lines
when /\[([-\w]+)\]/:
name = $1
if newmounts.include?(name)
raise FileServerError, "%s is already mounted at %s" %
[newmounts[name], name], count, @config.file
end
mount = Mount.new(name)
newmounts[name] = mount
when /^\s*(\w+)\s+(.+)$/:
var = $1
value = $2
case var
when "path":
if mount.name == MODULES
Puppet.warning "The '#{MODULES}' module can not have a path. Ignoring attempt to set it"
else
begin
mount.path = value
rescue FileServerError => detail
Puppet.err "Removing mount %s: %s" %
[mount.name, detail]
newmounts.delete(mount.name)
end
end
when "allow":
value.split(/\s*,\s*/).each { |val|
begin
mount.info "allowing %s access" % val
mount.allow(val)
rescue AuthStoreError => detail
raise FileServerError.new(detail.to_s,
count, @config.file)
end
}
when "deny":
value.split(/\s*,\s*/).each { |val|
begin
mount.info "denying %s access" % val
mount.deny(val)
rescue AuthStoreError => detail
raise FileServerError.new(detail.to_s,
count, @config.file)
end
}
else
raise FileServerError.new("Invalid argument '%s'" % var,
count, @config.file)
end
else
raise FileServerError.new("Invalid line '%s'" % line.chomp,
count, @config.file)
end
count += 1
}
}
rescue Errno::EACCES => detail
Puppet.err "FileServer error: Cannot read %s; cannot serve" % @config
#raise Puppet::Error, "Cannot read %s" % @config
rescue Errno::ENOENT => detail
Puppet.err "FileServer error: '%s' does not exist; cannot serve" %
@config
#raise Puppet::Error, "%s does not exit" % @config
#rescue FileServerError => detail
# Puppet.err "FileServer error: %s" % detail
end
unless newmounts[MODULES]
mount = Mount.new(MODULES)
mount.allow("*")
newmounts[MODULES] = mount
end
# Verify each of the mounts are valid.
# We let the check raise an error, so that it can raise an error
# pointing to the specific problem.
newmounts.each { |name, mount|
unless mount.valid?
raise FileServerError, "No path specified for mount %s" %
name
end
}
@mounts = newmounts
end
# Recursively list the directory. FIXME This should be using
# puppet objects, not directly listing.
def reclist(mount, root, path, recurse, ignore)
# Take out the root of the path.
name = path.sub(root, '')
if name == ""
name = "/"
end
if name == path
raise FileServerError, "Could not match %s in %s" %
[root, path]
end
desc = [name]
ftype = File.stat(path).ftype
desc << ftype
if recurse.is_a?(Integer)
recurse -= 1
end
ary = [desc]
if recurse == true or (recurse.is_a?(Integer) and recurse > -1)
if ftype == "directory"
children = Dir.entries(path)
if ignore
children = handleignore(children, path, ignore)
end
children.each { |child|
next if child =~ /^\.\.?$/
reclist(mount, root, File.join(path, child), recurse, ignore).each { |cobj|
ary << cobj
}
}
end
end
return ary.reject { |c| c.nil? }
end
# Split the path into the separate mount point and path.
def splitpath(dir, client)
# the dir is based on one of the mounts
# so first retrieve the mount path
mount = nil
path = nil
if dir =~ %r{/([-\w]+)/?}
# Strip off the mount name.
mount_name, path = dir.sub(%r{^/}, '').split(File::Separator, 2)
unless mount = modules_mount(mount_name, client)
unless mount = @mounts[mount_name]
raise FileServerError, "Fileserver module '%s' not mounted" % mount_name
end
end
else
raise FileServerError, "Fileserver error: Invalid path '%s'" % dir
end
if path == ""
path = nil
elsif path
# Remove any double slashes that might have occurred
path = URI.unescape(path.gsub(/\/\//, "/"))
end
return mount, path
end
def to_s
"fileserver"
end
# A simple class for wrapping mount points. Instances of this class
# don't know about the enclosing object; they're mainly just used for
# authorization.
class Mount < Puppet::Network::AuthStore
attr_reader :name
@@syncs = {}
@@files = {}
Puppet::Util.logmethods(self, true)
def getfileobject(dir, links)
unless FileTest.exists?(dir)
self.notice "File source %s does not exist" % dir
return nil
end
return fileobj(dir, links)
end
# Run 'retrieve' on a file. This gets the actual parameters, so
# we can pass them to the client.
def check(obj)
# Retrieval is enough here, because we don't want to cache
# any information in the state file, and we don't want to generate
# any state changes or anything. We don't even need to sync
# the checksum, because we're always going to hit the disk
# directly.
# We're now caching file data, using the LoadedFile to check the
# disk no more frequently than the :filetimeout.
path = obj[:path]
sync = sync(path)
unless data = @@files[path]
data = {}
sync.synchronize(Sync::EX) do
@@files[path] = data
data[:loaded_obj] = Puppet::Util::LoadedFile.new(path)
data[:values] = properties(obj)
return data[:values]
end
end
changed = nil
sync.synchronize(Sync::SH) do
changed = data[:loaded_obj].changed?
end
if changed
sync.synchronize(Sync::EX) do
data[:values] = properties(obj)
return data[:values]
end
else
sync.synchronize(Sync::SH) do
return data[:values]
end
end
end
# Create a map for a specific client.
def clientmap(client)
{
"h" => client.sub(/\..*$/, ""),
"H" => client,
"d" => client.sub(/[^.]+\./, "") # domain name
}
end
# Replace % patterns as appropriate.
def expand(path, client = nil)
# This map should probably be moved into a method.
map = nil
if client
map = clientmap(client)
else
Puppet.notice "No client; expanding '%s' with local host" %
path
# Else, use the local information
map = localmap()
end
path.gsub(/%(.)/) do |v|
key = $1
if key == "%"
"%"
else
map[key] || v
end
end
end
# Do we have any patterns in our path, yo?
def expandable?
if defined? @expandable
@expandable
else
false
end
end
# Create out object. It must have a name.
def initialize(name, path = nil)
unless name =~ %r{^[-\w]+$}
raise FileServerError, "Invalid name format '%s'" % name
end
@name = name
if path
self.path = path
else
@path = nil
end
super()
end
def fileobj(path, links)
obj = nil
if obj = Puppet.type(:file)[path]
# This can only happen in local fileserving, but it's an
# important one. It'd be nice if we didn't just set
# the check params every time, but I'm not sure it's worth
# the effort.
obj[:check] = CHECKPARAMS
else
obj = Puppet.type(:file).create(
:name => path,
:check => CHECKPARAMS
)
end
if links == :manage
links = :follow
end
# This, ah, might be completely redundant
unless obj[:links] == links
obj[:links] = links
end
return obj
end
# Cache this manufactured map, since if it's used it's likely
# to get used a lot.
def localmap
unless defined? @@localmap
@@localmap = {
"h" => Facter.value("hostname"),
"H" => [Facter.value("hostname"),
Facter.value("domain")].join("."),
"d" => Facter.value("domain")
}
end
@@localmap
end
# Return the path as appropriate, expanding as necessary.
def path(client = nil)
if expandable?
return expand(@path, client)
else
return @path
end
end
# Set the path.
def path=(path)
# FIXME: For now, just don't validate paths with replacement
# patterns in them.
if path =~ /%./
# Mark that we're expandable.
@expandable = true
else
unless FileTest.exists?(path)
raise FileServerError, "%s does not exist" % path
end
unless FileTest.directory?(path)
raise FileServerError, "%s is not a directory" % path
end
unless FileTest.readable?(path)
raise FileServerError, "%s is not readable" % path
end
@expandable = false
end
@path = path
end
# Return the current values for the object.
def properties(obj)
obj.retrieve.inject({}) { |props, ary| props[ary[0].name] = ary[1]; props }
end
# Retrieve a specific directory relative to a mount point.
# If they pass in a client, then expand as necessary.
def subdir(dir = nil, client = nil)
basedir = self.path(client)
dirname = if dir
File.join(basedir, dir.split("/").join(File::SEPARATOR))
else
basedir
end
dirname
end
def sync(path)
@@syncs[path] ||= Sync.new
@@syncs[path]
end
def to_s
"mount[%s]" % @name
end
# Verify our configuration is valid. This should really check to
# make sure at least someone will be allowed, but, eh.
def valid?
if name == MODULES
return @path.nil?
else
return ! @path.nil?
end
end
# Return a new mount with the same properties as +self+, except
# with a different name and path.
def copy(name, path)
result = self.clone
result.path = path
result.instance_variable_set(:@name, name)
return result
end
end
end
end
# $Id$
diff --git a/lib/puppet/network/handler/master.rb b/lib/puppet/network/handler/master.rb
index c8db277ba..9550dd550 100644
--- a/lib/puppet/network/handler/master.rb
+++ b/lib/puppet/network/handler/master.rb
@@ -1,153 +1,146 @@
require 'openssl'
require 'puppet'
require 'puppet/parser/interpreter'
require 'puppet/sslcertificates'
require 'xmlrpc/server'
require 'yaml'
class Puppet::Network::Handler
class MasterError < Puppet::Error; end
class Master < Handler
desc "Puppet's configuration interface. Used for all interactions related to
generating client configurations."
include Puppet::Util
attr_accessor :ast
attr_reader :ca
@interface = XMLRPC::Service::Interface.new("puppetmaster") { |iface|
iface.add_method("string getconfig(string)")
iface.add_method("int freshness()")
}
# Tell a client whether there's a fresh config for it
def freshness(client = nil, clientip = nil)
client ||= Facter.value("hostname")
config_handler.version(client, clientip)
end
def initialize(hash = {})
args = {}
# Allow specification of a code snippet or of a file
if code = hash[:Code]
args[:Code] = code
elsif man = hash[:Manifest]
args[:Manifest] = man
end
if hash[:Local]
@local = hash[:Local]
else
@local = false
end
args[:Local] = true
if hash.include?(:CA) and hash[:CA]
@ca = Puppet::SSLCertificates::CA.new()
else
@ca = nil
end
Puppet.debug("Creating interpreter")
if hash.include?(:UseNodes)
args[:UseNodes] = hash[:UseNodes]
elsif @local
args[:UseNodes] = false
end
# This is only used by the cfengine module, or if --loadclasses was
# specified in +puppet+.
if hash.include?(:Classes)
args[:Classes] = hash[:Classes]
end
@config_handler = Puppet::Network::Handler.handler(:configuration).new(args)
end
# Call our various handlers; this handler is getting deprecated.
def getconfig(facts, format = "marshal", client = nil, clientip = nil)
facts = decode_facts(facts)
client, clientip = clientname(client, clientip, facts)
# Pass the facts to the fact handler
- fact_handler.set(client, facts)
+ Puppet::Node::Facts.post(Puppet::Node::Facts.new(client, facts))
# And get the configuration from the config handler
begin
config = config_handler.configuration(client)
rescue => detail
puts detail.backtrace
raise
end
return translate(config.extract)
end
private
# Manipulate the client name as appropriate.
def clientname(name, ip, facts)
# Always use the hostname from Facter.
client = facts["hostname"]
clientip = facts["ipaddress"]
if Puppet[:node_name] == 'cert'
if name
client = name
end
if ip
clientip = ip
end
end
return client, clientip
end
def config_handler
unless defined? @config_handler
@config_handler = Puppet::Network::Handler.handler(:config).new :local => local?
end
@config_handler
end
#
def decode_facts(facts)
if @local
# we don't need to do anything, since we should already
# have raw objects
Puppet.debug "Our client is local"
else
Puppet.debug "Our client is remote"
begin
facts = YAML.load(CGI.unescape(facts))
rescue => detail
raise XMLRPC::FaultException.new(
1, "Could not rebuild facts"
)
end
end
return facts
end
- def fact_handler
- unless defined? @fact_handler
- @fact_handler = Puppet::Network::Handler.handler(:facts).new :local => local?
- end
- @fact_handler
- end
-
# Translate our configuration appropriately for sending back to a client.
def translate(config)
if local?
config
else
CGI.escape(config.to_yaml(:UseBlock => true))
end
end
end
end
diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb
index 9c5d2d397..7ad7bc3b3 100644
--- a/lib/puppet/node.rb
+++ b/lib/puppet/node.rb
@@ -1,75 +1,84 @@
+require 'puppet/indirector'
+
# A simplistic class for managing the node information itself.
class Puppet::Node
+ require '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 :node, :to => :node_source
# Add the node-searching methods. This is what people will actually
# interact with that will find the node with the list of names or
# will search for a default node.
require 'puppet/node/searching'
extend Puppet::Node::Searching
attr_accessor :name, :classes, :parameters, :source, :ipaddress, :names
attr_reader :time
attr_writer :environment
# Do not return environments that are the empty string, and use
# explicitly set environments, then facts, then a central env
# value.
def environment
unless @environment and @environment != ""
if env = parameters["environment"] and env != ""
@environment = env
elsif env = Puppet[:environment] and env != ""
@environment = env
else
@environment = nil
end
end
@environment
end
def initialize(name, options = {})
@name = name
# Provide a default value.
if names = options[:names]
if names.is_a?(String)
@names = [names]
else
@names = names
end
else
@names = [name]
end
if classes = options[:classes]
if classes.is_a?(String)
@classes = [classes]
else
@classes = classes
end
else
@classes = []
end
@parameters = options[:parameters] || {}
@environment = options[:environment]
@time = Time.now
end
# Merge the node facts with parameters from the node source.
- # This is only called if the node source has 'fact_merge' set to true.
- def fact_merge(facts)
- facts.each do |name, value|
+ def fact_merge
+ if facts = Puppet::Node::Facts.get(name)
+ merge(facts.values)
+ end
+ end
+
+ # Merge any random parameters into our parameter list.
+ def merge(params)
+ params.each do |name, value|
@parameters[name] = value unless @parameters.include?(name)
end
end
end
diff --git a/lib/puppet/node/facts.rb b/lib/puppet/node/facts.rb
index eddf44def..e5774127b 100755
--- a/lib/puppet/node/facts.rb
+++ b/lib/puppet/node/facts.rb
@@ -1,36 +1,36 @@
+require 'puppet/node'
+require 'puppet/indirector'
+
# 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
+
+ add_internal
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
+ def add_internal
+ self.values[:_timestamp] = Time.now
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
+ def strip_internal
+ newvals = values.dup
+ newvals.find_all { |name, value| name.to_s =~ /^_/ }.each { |name, value| newvals.delete(name) }
+ newvals
end
end
diff --git a/spec/Rakefile b/spec/Rakefile
index 40d107312..bb2a75de5 100644
--- a/spec/Rakefile
+++ b/spec/Rakefile
@@ -1,10 +1,16 @@
require File.join(File.dirname(__FILE__), "spec_helper.rb")
require 'rake'
require 'spec/rake/spectask'
+basedir = File.dirname(__FILE__)
+puppetlibdir = File.join(basedir, "../lib")
+puppettestlibdir = File.join(basedir, "../test/lib")
+speclibdir = File.join(basedir, "lib")
+
desc "Run all spec unit tests"
Spec::Rake::SpecTask.new('unit') do |t|
t.spec_files = FileList['unit/**/*.rb']
+ t.libs = [puppetlibdir, puppettestlibdir, speclibdir]
end
task :default => [:unit]
diff --git a/spec/lib/spec/dsl/behaviour.rb b/spec/lib/spec/dsl/behaviour.rb
index 5158bb673..93a357f19 100644
--- a/spec/lib/spec/dsl/behaviour.rb
+++ b/spec/lib/spec/dsl/behaviour.rb
@@ -1,220 +1,224 @@
+require 'puppettest/runnable_test'
+
module Spec
module DSL
- class EvalModule < Module; end
+ class EvalModule < Module;
+ include PuppetTest::RunnableTest
+ end
class Behaviour
extend BehaviourCallbacks
class << self
def add_shared_behaviour(behaviour)
return if behaviour.equal?(found_behaviour = find_shared_behaviour(behaviour.description))
return if found_behaviour and File.expand_path(behaviour.description[:spec_path]) == File.expand_path(found_behaviour.description[:spec_path])
raise ArgumentError.new("Shared Behaviour '#{behaviour.description}' already exists") if found_behaviour
shared_behaviours << behaviour
end
def find_shared_behaviour(behaviour_description)
shared_behaviours.find { |b| b.description == behaviour_description }
end
def shared_behaviours
# TODO - this needs to be global, or at least accessible from
# from subclasses of Behaviour in a centralized place. I'm not loving
# this as a solution, but it works for now.
$shared_behaviours ||= []
end
end
def initialize(*args, &behaviour_block)
init_description(*args)
init_eval_module
before_eval
eval_behaviour(&behaviour_block)
end
private
def init_description(*args)
unless self.class == Behaviour
args << {} unless Hash === args.last
args.last[:behaviour_class] = self.class
end
@description = Description.new(*args)
end
def init_eval_module
@eval_module = EvalModule.new
@eval_module.extend BehaviourEval::ModuleMethods
@eval_module.include BehaviourEval::InstanceMethods
@eval_module.include described_type if described_type.class == Module
@eval_module.behaviour = self
@eval_module.description = @description
end
def eval_behaviour(&behaviour_block)
@eval_module.class_eval(&behaviour_block)
end
protected
def before_eval
end
public
def run(reporter, dry_run=false, reverse=false, timeout=nil)
raise "shared behaviours should never run" if shared?
# TODO - change add_behaviour to add_description ??????
reporter.add_behaviour(@description)
prepare_execution_context_class
before_all_errors = run_before_all(reporter, dry_run)
exs = reverse ? examples.reverse : examples
example_execution_context = nil
if before_all_errors.empty?
exs.each do |example|
example_execution_context = execution_context(example)
example_execution_context.copy_instance_variables_from(@before_and_after_all_context_instance) unless before_all_proc(behaviour_type).nil?
befores = before_each_proc(behaviour_type) {|e| raise e}
afters = after_each_proc(behaviour_type)
example.run(reporter, befores, afters, dry_run, example_execution_context, timeout)
end
end
@before_and_after_all_context_instance.copy_instance_variables_from(example_execution_context) unless after_all_proc(behaviour_type).nil?
run_after_all(reporter, dry_run)
end
def number_of_examples
examples.length
end
def matches?(specified_examples)
matcher ||= ExampleMatcher.new(description)
examples.each do |example|
return true if example.matches?(matcher, specified_examples)
end
return false
end
def shared?
@description[:shared]
end
def retain_examples_matching!(specified_examples)
return if specified_examples.index(description)
matcher = ExampleMatcher.new(description)
examples.reject! do |example|
!example.matches?(matcher, specified_examples)
end
end
def methods
my_methods = super
my_methods |= @eval_module.methods
my_methods
end
# Includes modules in the Behaviour (the describe block).
def include(*args)
@eval_module.include(*args)
end
def behaviour_type #:nodoc:
@description[:behaviour_type]
end
# Sets the #number on each Example and returns the next number
def set_sequence_numbers(number, reverse) #:nodoc:
exs = reverse ? examples.reverse : examples
exs.each do |example|
example.number = number
number += 1
end
number
end
protected
# Messages that this class does not understand
# are passed directly to the @eval_module.
def method_missing(sym, *args, &block)
@eval_module.send(sym, *args, &block)
end
def prepare_execution_context_class
plugin_mock_framework
weave_in_included_modules
define_predicate_matchers #this is in behaviour_eval
execution_context_class
end
def weave_in_included_modules
mods = [@eval_module]
mods << included_modules.dup
mods << Spec::Runner.configuration.modules_for(behaviour_type)
execution_context_class.class_eval do
# WARNING - the following can be executed in the context of any
# class, and should never pass more than one module to include
# even though we redefine include in this class. This is NOT
# tested anywhere, hence this comment.
mods.flatten.each {|mod| include mod}
end
end
def execution_context(example)
execution_context_class.new(example)
end
def run_before_all(reporter, dry_run)
errors = []
unless dry_run
begin
@before_and_after_all_context_instance = execution_context(nil)
@before_and_after_all_context_instance.instance_eval(&before_all_proc(behaviour_type))
rescue Exception => e
errors << e
location = "before(:all)"
# The easiest is to report this as an example failure. We don't have an Example
# at this point, so we'll just create a placeholder.
reporter.example_finished(Example.new(location), e, location) if reporter
end
end
errors
end
def run_after_all(reporter, dry_run)
unless dry_run
begin
@before_and_after_all_context_instance ||= execution_context(nil)
@before_and_after_all_context_instance.instance_eval(&after_all_proc(behaviour_type))
rescue Exception => e
location = "after(:all)"
reporter.example_finished(Example.new(location), e, location) if reporter
end
end
end
def plugin_mock_framework
case mock_framework = Spec::Runner.configuration.mock_framework
when Module
include mock_framework
else
require Spec::Runner.configuration.mock_framework
include Spec::Plugins::MockFramework
end
end
def description
@description.to_s
end
def described_type
@description.described_type
end
end
end
end
diff --git a/spec/lib/spec/runner/behaviour_runner.rb b/spec/lib/spec/runner/behaviour_runner.rb
index 1ac891f3c..078490e92 100644
--- a/spec/lib/spec/runner/behaviour_runner.rb
+++ b/spec/lib/spec/runner/behaviour_runner.rb
@@ -1,123 +1,125 @@
module Spec
module Runner
class BehaviourRunner
def initialize(options, arg=nil)
@behaviours = []
@options = options
end
def add_behaviour(behaviour)
if !specified_examples.nil? && !specified_examples.empty?
behaviour.retain_examples_matching!(specified_examples)
end
@behaviours << behaviour if behaviour.number_of_examples != 0 && !behaviour.shared?
end
# Runs all behaviours and returns the number of failures.
def run(paths, exit_when_done)
prepare!(paths)
begin
run_behaviours
rescue Interrupt
ensure
report_end
end
failure_count = report_dump
heckle if(failure_count == 0 && !@options.heckle_runner.nil?)
if(exit_when_done)
exit_code = (failure_count == 0) ? 0 : 1
exit(exit_code)
end
failure_count
end
def report_end
@options.reporter.end
end
def report_dump
@options.reporter.dump
end
def prepare!(paths)
unless paths.nil? # It's nil when running single specs with ruby
paths = find_paths(paths)
sorted_paths = sort_paths(paths)
load_specs(sorted_paths) # This will populate @behaviours via callbacks to add_behaviour
end
@options.reporter.start(number_of_examples)
@behaviours.reverse! if @options.reverse
set_sequence_numbers
end
def run_behaviours
@behaviours.each do |behaviour|
+ # LAK:NOTE: this 'runnable' test is Puppet-specific.
+ next unless behaviour.runnable?
behaviour.run(@options.reporter, @options.dry_run, @options.reverse, @options.timeout)
end
end
def number_of_examples
@behaviours.inject(0) {|sum, behaviour| sum + behaviour.number_of_examples}
end
FILE_SORTERS = {
'mtime' => lambda {|file_a, file_b| File.mtime(file_b) <=> File.mtime(file_a)}
}
def sorter(paths)
FILE_SORTERS[@options.loadby]
end
def sort_paths(paths)
sorter = sorter(paths)
paths = paths.sort(&sorter) unless sorter.nil?
paths
end
private
# Sets the #number on each Example
def set_sequence_numbers
number = 0
@behaviours.each do |behaviour|
number = behaviour.set_sequence_numbers(number, @options.reverse)
end
end
def find_paths(paths)
result = []
paths.each do |path|
if File.directory?(path)
result += Dir["#{path}/**/*.rb"]
elsif File.file?(path)
result << path
else
raise "File or directory not found: #{path}"
end
end
result
end
def load_specs(paths)
paths.each do |path|
load path
end
end
def specified_examples
@options.examples
end
def heckle
heckle_runner = @options.heckle_runner
@options.heckle_runner = nil
behaviour_runner = self.class.new(@options)
behaviour_runner.instance_variable_set(:@behaviours, @behaviours)
heckle_runner.heckle_with(behaviour_runner)
end
end
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index a4171bb07..477842495 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,20 +1,20 @@
dir = File.dirname(__FILE__)
$:.unshift("#{dir}/lib").unshift("#{dir}/../lib")
# Add the old test dir, so that we can still find mocha and spec
$:.unshift("#{dir}/../test/lib")
require 'mocha'
-require 'spec'
require 'puppettest'
+require 'spec'
Spec::Runner.configure do |config|
config.mock_with :mocha
config.prepend_before :each do
setup() if respond_to? :setup
end
config.prepend_after :each do
teardown() if respond_to? :teardown
end
end
diff --git a/spec/unit/indirector/facts/yaml.rb b/spec/unit/indirector/facts/yaml.rb
index 45c079a69..176a47f04 100755
--- a/spec/unit/indirector/facts/yaml.rb
+++ b/spec/unit/indirector/facts/yaml.rb
@@ -1,62 +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)
+ @store.post(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/indirection.rb b/spec/unit/indirector/indirection.rb
new file mode 100755
index 000000000..e9b10c8c7
--- /dev/null
+++ b/spec/unit/indirector/indirection.rb
@@ -0,0 +1,57 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../spec_helper'
+
+require 'puppet/indirector'
+
+describe Puppet::Indirector::Indirection, " when initializing" do
+ it "should set the name" do
+ @indirection = Puppet::Indirector::Indirection.new(:myind)
+ @indirection.name.should == :myind
+ end
+
+ it "should set any passed options" do
+ @indirection = Puppet::Indirector::Indirection.new(:myind, :to => :node_source)
+ @indirection.to.should == :node_source
+ end
+
+ it "should only allow valid configuration parameters to be specified as :to targets" do
+ proc { Puppet::Indirector::Indirection.new(:myind, :to => :no_such_variable) }.should raise_error(ArgumentError)
+ end
+
+ after do
+ if defined? @indirection
+ @indirection.delete
+ end
+ end
+end
+
+describe Puppet::Indirector, " when managing termini" do
+ before do
+ @indirection = Puppet::Indirector::Indirection.new(:node, :to => :node_source)
+ end
+
+ it "should allow the clearance of cached termini" do
+ terminus1 = mock 'terminus1'
+ terminus2 = mock 'terminus2'
+ Puppet::Indirector.terminus(:node, Puppet[:node_source]).stubs(:new).returns(terminus1, terminus2, ArgumentError)
+ @indirection.terminus.should equal(terminus1)
+ @indirection.class.clear_cache
+ @indirection.terminus.should equal(terminus2)
+ end
+
+ # Make sure it caches the terminus.
+ it "should return the same terminus each time" do
+ @indirection = Puppet::Indirector::Indirection.new(:node, :to => :node_source)
+ @terminus = mock 'new'
+ Puppet::Indirector.terminus(:node, Puppet[:node_source]).expects(:new).returns(@terminus)
+
+ @indirection.terminus.should equal(@terminus)
+ @indirection.terminus.should equal(@terminus)
+ end
+
+ after do
+ @indirection.delete
+ Puppet::Indirector::Indirection.clear_cache
+ end
+end
diff --git a/spec/unit/indirector/indirector.rb b/spec/unit/indirector/indirector.rb
index c4221febb..312c60951 100755
--- a/spec/unit/indirector/indirector.rb
+++ b/spec/unit/indirector/indirector.rb
@@ -1,79 +1,61 @@
#!/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)
+ it "should create an indirection" do
+ indirection = @indirector.indirects :test, :to => :node_source
+ indirection.name.should == :test
+ indirection.to.should == :node_source
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
+
+ after do
+ Puppet.config.clear
+ 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/indirector/node/external.rb b/spec/unit/indirector/node/external.rb
new file mode 100755
index 000000000..30b2f74c2
--- /dev/null
+++ b/spec/unit/indirector/node/external.rb
@@ -0,0 +1,119 @@
+#!/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.get("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.get("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.get("yay")
+ end
+
+ it "should return a node object" do
+ @searcher.get("apple").should be_instance_of(Puppet::Node)
+ end
+
+ it "should set the node's name" do
+ @searcher.get("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.get("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.get("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.get("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(:get).with("apple").returns(facts)
+ node = @searcher.get("apple")
+ node.parameters["three"].should == "four"
+ end
+
+ it "should return nil when it cannot find the node" do
+ @searcher.get("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
new file mode 100755
index 000000000..c6eb45ffc
--- /dev/null
+++ b/spec/unit/indirector/node/ldap.rb
@@ -0,0 +1,243 @@
+#!/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.get("foo").should be_nil
+ end
+
+ it "should return Puppet::Node instances" do
+ @nodetable["foo"] = [nil, %w{}, {}]
+ @searcher.get("foo").should be_instance_of(Puppet::Node)
+ end
+
+ it "should set the node name" do
+ @nodetable["foo"] = [nil, %w{}, {}]
+ @searcher.get("foo").name.should == "foo"
+ end
+
+ it "should set the classes" do
+ @nodetable["foo"] = [nil, %w{one two}, {}]
+ @searcher.get("foo").classes.should == %w{one two}
+ end
+
+ it "should set the parameters" do
+ @nodetable["foo"] = [nil, %w{}, {"one" => "two"}]
+ @searcher.get("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.get("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.get("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.get("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
new file mode 100755
index 000000000..d52d7ca83
--- /dev/null
+++ b/spec/unit/indirector/node/none.rb
@@ -0,0 +1,32 @@
+#!/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.get("yay").should be_instance_of(Puppet::Node)
+ end
+
+ it "should create a new node with the correct name" do
+ @searcher.get("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(:get).with("yay").returns(facts)
+ node = @searcher.get("yay")
+ node.parameters["one"].should == "two"
+ node.parameters["three"].should == "four"
+ end
+
+ after do
+ Puppet.config.clear
+ end
+end
diff --git a/spec/unit/node/facts.rb b/spec/unit/node/facts.rb
index c7fc65f38..c677c1d2e 100755
--- a/spec/unit/node/facts.rb
+++ b/spec/unit/node/facts.rb
@@ -1,25 +1,38 @@
#!/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)
+ Puppet::Indirector.terminus(:facts, Puppet[:fact_store].intern).stubs(:new).returns(@terminus)
+
+ # We have to clear the cache so that the facts ask for our terminus stub,
+ # instead of anything that might be cached.
+ Puppet::Indirector::Indirection.clear_cache
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)
+ @terminus.expects(:post).with(:my_facts)
+ Puppet::Node::Facts.post(:my_facts)
+ end
+
+ after do
+ mocha_verify
+ Puppet::Indirector::Indirection.clear_cache
+ end
+end
+
+describe Puppet::Node::Facts, " when storing and retrieving" do
+ it "should add metadata to the facts" do
+ facts = Puppet::Node::Facts.new("me", "one" => "two", "three" => "four")
+ facts.values[:_timestamp].should be_instance_of(Time)
end
end
diff --git a/spec/unit/node/node.rb b/spec/unit/node/node.rb
index a6cc1e301..9342dc5ce 100755
--- a/spec/unit/node/node.rb
+++ b/spec/unit/node/node.rb
@@ -1,116 +1,124 @@
#!/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")
+ Puppet::Node::Facts.stubs(:get).with(@node.name).returns(Puppet::Node::Facts.new(@node.name, "one" => "c", "two" => "b"))
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.fact_merge
@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.fact_merge
@node.parameters["two"].should == "b"
end
+
+ it "should accept arbitrary parameters to merge into its parameters" do
+ @node.parameters = {"one" => "a"}
+ @node.merge "two" => "three"
+ @node.parameters["two"].should == "three"
+ 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)
+ Puppet::Indirector.terminus(:node, Puppet[:node_source]).stubs(:new).returns(@terminus)
end
it "should redirect to the specified node source" do
@terminus.expects(:get).with(:my_node)
Puppet::Node.get(:my_node)
end
+
+ after do
+ Puppet::Indirector::Indirection.clear_cache
+ end
end
diff --git a/spec/unit/other/node.rb b/spec/unit/other/node.rb
deleted file mode 100755
index 66d5ba9d7..000000000
--- a/spec/unit/other/node.rb
+++ /dev/null
@@ -1,101 +0,0 @@
-#!/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
diff --git a/spec/unit/util/config.rb b/spec/unit/util/config.rb
index 348a54893..28ccb04d7 100755
--- a/spec/unit/util/config.rb
+++ b/spec/unit/util/config.rb
@@ -1,408 +1,426 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
describe Puppet::Util::Config, " when specifying defaults" do
before do
@config = Puppet::Util::Config.new
end
it "should start with no defined parameters" do
@config.params.length.should == 0
end
it "should allow specification of default values associated with a section as an array" do
@config.setdefaults(:section, :myvalue => ["defaultval", "my description"])
end
it "should not allow duplicate parameter specifications" do
@config.setdefaults(:section, :myvalue => ["a", "b"])
lambda { @config.setdefaults(:section, :myvalue => ["c", "d"]) }.should raise_error(ArgumentError)
end
it "should allow specification of default values associated with a section as a hash" do
@config.setdefaults(:section, :myvalue => {:default => "defaultval", :desc => "my description"})
end
it "should consider defined parameters to be valid" do
@config.setdefaults(:section, :myvalue => ["defaultval", "my description"])
@config.valid?(:myvalue).should be_true
end
it "should require a description when defaults are specified with an array" do
lambda { @config.setdefaults(:section, :myvalue => ["a value"]) }.should raise_error(ArgumentError)
end
it "should require a description when defaults are specified with a hash" do
lambda { @config.setdefaults(:section, :myvalue => {:default => "a value"}) }.should raise_error(ArgumentError)
end
it "should support specifying owner, group, and mode when specifying files" do
@config.setdefaults(:section, :myvalue => {:default => "/some/file", :owner => "blah", :mode => "boo", :group => "yay", :desc => "whatever"})
end
it "should support specifying a short name" do
@config.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"})
end
it "should fail when short names conflict" do
@config.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"})
lambda { @config.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) }.should raise_error(ArgumentError)
end
end
describe Puppet::Util::Config, " when setting values" do
before do
@config = Puppet::Util::Config.new
@config.setdefaults :main, :myval => ["val", "desc"]
@config.setdefaults :main, :bool => [true, "desc"]
end
it "should provide a method for setting values from other objects" do
@config[:myval] = "something else"
@config[:myval].should == "something else"
end
it "should support a getopt-specific mechanism for setting values" do
@config.handlearg("--myval", "newval")
@config[:myval].should == "newval"
end
it "should support a getopt-specific mechanism for turning booleans off" do
@config.handlearg("--no-bool")
@config[:bool].should == false
end
it "should support a getopt-specific mechanism for turning booleans on" do
# Turn it off first
@config[:bool] = false
@config.handlearg("--bool")
@config[:bool].should == true
end
it "should clear the cache when setting getopt-specific values" do
@config.setdefaults :mysection, :one => ["whah", "yay"], :two => ["$one yay", "bah"]
@config[:two].should == "whah yay"
@config.handlearg("--one", "else")
@config[:two].should == "else yay"
end
it "should not clear other values when setting getopt-specific values" do
@config[:myval] = "yay"
@config.handlearg("--no-bool")
@config[:myval].should == "yay"
end
it "should call passed blocks when values are set" do
values = []
@config.setdefaults(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }})
values.should == []
@config[:hooker] = "something"
values.should == %w{something}
end
it "should munge values using the element-specific methods" do
@config[:bool] = "false"
@config[:bool].should == false
end
it "should prefer cli values to values set in Ruby code" do
@config.handlearg("--myval", "cliarg")
@config[:myval] = "memarg"
@config[:myval].should == "cliarg"
end
end
describe Puppet::Util::Config, " when returning values" do
before do
@config = Puppet::Util::Config.new
@config.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"], :four => ["$two $three FOUR", "d"]
end
it "should provide a mechanism for returning set values" do
@config[:one] = "other"
@config[:one].should == "other"
end
it "should interpolate default values for other parameters into returned parameter values" do
@config[:one].should == "ONE"
@config[:two].should == "ONE TWO"
@config[:three].should == "ONE ONE TWO THREE"
end
it "should interpolate default values that themselves need to be interpolated" do
@config[:four].should == "ONE TWO ONE ONE TWO THREE FOUR"
end
it "should interpolate set values for other parameters into returned parameter values" do
@config[:one] = "on3"
@config[:two] = "$one tw0"
@config[:three] = "$one $two thr33"
@config[:four] = "$one $two $three f0ur"
@config[:one].should == "on3"
@config[:two].should == "on3 tw0"
@config[:three].should == "on3 on3 tw0 thr33"
@config[:four].should == "on3 on3 tw0 on3 on3 tw0 thr33 f0ur"
end
it "should not cache interpolated values such that stale information is returned" do
@config[:two].should == "ONE TWO"
@config[:one] = "one"
@config[:two].should == "one TWO"
end
it "should not cache values such that information from one environment is returned for another environment" do
text = "[env1]\none = oneval\n[env2]\none = twoval\n"
file = mock 'file'
file.stubs(:changed?).returns(true)
file.stubs(:file).returns("/whatever")
@config.stubs(:read_file).with(file).returns(text)
@config.parse(file)
@config.value(:one, "env1").should == "oneval"
@config.value(:one, "env2").should == "twoval"
end
it "should have a name determined by the 'name' parameter" do
@config.setdefaults(:whatever, :name => ["something", "yayness"])
@config.name.should == :something
@config[:name] = :other
@config.name.should == :other
end
end
describe Puppet::Util::Config, " when choosing which value to return" do
before do
@config = Puppet::Util::Config.new
@config.setdefaults :section,
:one => ["ONE", "a"],
:name => ["myname", "w"]
end
it "should return default values if no values have been set" do
@config[:one].should == "ONE"
end
it "should return values set on the cli before values set in the configuration file" do
text = "[main]\none = fileval\n"
file = mock 'file'
file.stubs(:changed?).returns(true)
file.stubs(:file).returns("/whatever")
@config.stubs(:parse_file).returns(text)
@config.handlearg("--one", "clival")
@config.parse(file)
@config[:one].should == "clival"
end
it "should return values set on the cli before values set in Ruby" do
@config[:one] = "rubyval"
@config.handlearg("--one", "clival")
@config[:one].should == "clival"
end
it "should return values set in the executable-specific section before values set in the main section" do
text = "[main]\none = mainval\n[myname]\none = nameval\n"
file = mock 'file'
file.stubs(:changed?).returns(true)
file.stubs(:file).returns("/whatever")
@config.stubs(:read_file).with(file).returns(text)
@config.parse(file)
@config[:one].should == "nameval"
end
it "should not return values outside of its search path" do
text = "[other]\none = oval\n"
file = "/some/file"
file = mock 'file'
file.stubs(:changed?).returns(true)
file.stubs(:file).returns("/whatever")
@config.stubs(:read_file).with(file).returns(text)
@config.parse(file)
@config[:one].should == "ONE"
end
it "should return values in a specified environment" do
text = "[env]\none = envval\n"
file = "/some/file"
file = mock 'file'
file.stubs(:changed?).returns(true)
file.stubs(:file).returns("/whatever")
@config.stubs(:read_file).with(file).returns(text)
@config.parse(file)
@config.value(:one, "env").should == "envval"
end
it "should return values in a specified environment before values in the main or name sections" do
text = "[env]\none = envval\n[main]\none = mainval\n[myname]\none = nameval\n"
file = "/some/file"
file = mock 'file'
file.stubs(:changed?).returns(true)
file.stubs(:file).returns("/whatever")
@config.stubs(:read_file).with(file).returns(text)
@config.parse(file)
@config.value(:one, "env").should == "envval"
end
end
describe Puppet::Util::Config, " when parsing its configuration" do
before do
@config = Puppet::Util::Config.new
@config.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"]
end
it "should return values set in the configuration file" do
text = "[main]
one = fileval
"
file = "/some/file"
@config.expects(:read_file).with(file).returns(text)
@config.parse(file)
@config[:one].should == "fileval"
end
#484 - this should probably be in the regression area
it "should not throw an exception on unknown parameters" do
text = "[main]\nnosuchparam = mval\n"
file = "/some/file"
@config.expects(:read_file).with(file).returns(text)
lambda { @config.parse(file) }.should_not raise_error
end
it "should support an old parse method when per-executable configuration files still exist" do
# I'm not going to bother testing this method.
@config.should respond_to(:old_parse)
end
it "should convert booleans in the configuration file into Ruby booleans" do
text = "[main]
one = true
two = false
"
file = "/some/file"
@config.expects(:read_file).with(file).returns(text)
@config.parse(file)
@config[:one].should == true
@config[:two].should == false
end
it "should convert integers in the configuration file into Ruby Integers" do
text = "[main]
one = 65
"
file = "/some/file"
@config.expects(:read_file).with(file).returns(text)
@config.parse(file)
@config[:one].should == 65
end
it "should support specifying file all metadata (owner, group, mode) in the configuration file" do
@config.setdefaults :section, :myfile => ["/my/file", "a"]
text = "[main]
myfile = /other/file {owner = luke, group = luke, mode = 644}
"
file = "/some/file"
@config.expects(:read_file).with(file).returns(text)
@config.parse(file)
@config[:myfile].should == "/other/file"
@config.metadata(:myfile).should == {:owner => "luke", :group => "luke", :mode => "644"}
end
it "should support specifying file a single piece of metadata (owner, group, or mode) in the configuration file" do
@config.setdefaults :section, :myfile => ["/my/file", "a"]
text = "[main]
myfile = /other/file {owner = luke}
"
file = "/some/file"
@config.expects(:read_file).with(file).returns(text)
@config.parse(file)
@config[:myfile].should == "/other/file"
@config.metadata(:myfile).should == {:owner => "luke"}
end
end
describe Puppet::Util::Config, " when reparsing its configuration" do
before do
@config = Puppet::Util::Config.new
@config.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"]
end
it "should replace in-memory values with on-file values" do
# Init the value
text = "[main]\none = disk-init\n"
file = mock 'file'
file.stubs(:changed?).returns(true)
file.stubs(:file).returns("/test/file")
@config[:one] = "init"
@config.file = file
# Now replace the value
text = "[main]\none = disk-replace\n"
# This is kinda ridiculous - the reason it parses twice is that
# it goes to parse again when we ask for the value, because the
# mock always says it should get reparsed.
@config.expects(:read_file).with(file).returns(text).times(2)
@config.reparse
@config[:one].should == "disk-replace"
end
it "should retain parameters set by cli when configuration files are reparsed" do
@config.handlearg("--one", "clival")
text = "[main]\none = on-disk\n"
file = mock 'file'
file.stubs(:file).returns("/test/file")
@config.stubs(:read_file).with(file).returns(text)
@config.parse(file)
@config[:one].should == "clival"
end
it "should remove in-memory values that are no longer set in the file" do
# Init the value
text = "[main]\none = disk-init\n"
file = mock 'file'
file.stubs(:changed?).returns(true)
file.stubs(:file).returns("/test/file")
@config.expects(:read_file).with(file).returns(text)
@config.parse(file)
@config[:one].should == "disk-init"
# Now replace the value
text = "[main]\ntwo = disk-replace\n"
@config.expects(:read_file).with(file).returns(text)
@config.parse(file)
#@config.reparse
# The originally-overridden value should be replaced with the default
@config[:one].should == "ONE"
# and we should now have the new value in memory
@config[:two].should == "disk-replace"
end
end
-#describe Puppet::Util::Config, " when being used to manage the host machine" do
-# it "should provide a method that writes files with the correct modes"
-#
-# it "should provide a method that creates directories with the correct modes"
-#
-# it "should provide a method to declare what directories should exist"
-#
-# it "should provide a method to trigger enforcing of file modes on existing files and directories"
-#
-# it "should provide a method to convert the file mode enforcement into a Puppet manifest"
-#
-# it "should provide an option to create needed users and groups"
-#
-# it "should provide a method to print out the current configuration"
-#
-# it "should be able to provide all of its parameters in a format compatible with GetOpt::Long"
-#
-# it "should not attempt to manage files within /dev"
-#end
+describe Puppet::Util::Config, " when being used to manage the host machine" do
+ it "should provide a method that writes files with the correct modes" do
+ pending "Not converted from test/unit yet"
+ end
+
+ it "should provide a method that creates directories with the correct modes" do
+ pending "Not converted from test/unit yet"
+ end
+
+ it "should provide a method to declare what directories should exist" do
+ pending "Not converted from test/unit yet"
+ end
+
+ it "should provide a method to trigger enforcing of file modes on existing files and directories" do
+ pending "Not converted from test/unit yet"
+ end
+
+ it "should provide a method to convert the file mode enforcement into a Puppet manifest" do
+ pending "Not converted from test/unit yet"
+ end
+
+ it "should provide an option to create needed users and groups" do
+ pending "Not converted from test/unit yet"
+ end
+
+ it "should provide a method to print out the current configuration" do
+ pending "Not converted from test/unit yet"
+ end
+
+ it "should be able to provide all of its parameters in a format compatible with GetOpt::Long" do
+ pending "Not converted from test/unit yet"
+ end
+
+ it "should not attempt to manage files within /dev" do
+ pending "Not converted from test/unit yet"
+ end
+end
diff --git a/test/language/snippets.rb b/test/language/snippets.rb
index 2c74543e7..ff8a09881 100755
--- a/test/language/snippets.rb
+++ b/test/language/snippets.rb
@@ -1,519 +1,520 @@
#!/usr/bin/env ruby
$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/
require 'puppet'
require 'puppet/parser/interpreter'
require 'puppet/parser/parser'
require 'puppet/network/client'
require 'puppet/network/handler'
require 'puppettest'
class TestSnippets < Test::Unit::TestCase
include PuppetTest
def setup
super
@file = Puppet::Type.type(:file)
end
def self.snippetdir
PuppetTest.datadir "snippets"
end
def assert_file(path, msg = nil)
unless file = @file[path]
msg ||= "Could not find file %s" % path
raise msg
end
end
def assert_mode_equal(mode, path)
unless file = @file[path]
raise "Could not find file %s" % path
end
unless mode == file.should(:mode)
raise "Mode for %s is incorrect: %o vs %o" % [path, mode, file.should(:mode)]
end
end
def snippet(name)
File.join(self.class.snippetdir, name)
end
def file2ast(file)
parser = Puppet::Parser::Parser.new()
parser.file = file
ast = parser.parse
return ast
end
def snippet2ast(text)
parser = Puppet::Parser::Parser.new()
parser.string = text
ast = parser.parse
return ast
end
def client
args = {
:Listen => false
}
Puppet::Network::Client.new(args)
end
def ast2scope(ast)
interp = Puppet::Parser::Interpreter.new(
:ast => ast,
:client => client()
)
scope = Puppet::Parser::Scope.new()
ast.evaluate(scope)
return scope
end
def scope2objs(scope)
objs = scope.to_trans
end
def snippet2scope(snippet)
ast = snippet2ast(snippet)
scope = ast2scope(ast)
end
def snippet2objs(snippet)
ast = snippet2ast(snippet)
scope = ast2scope(ast)
objs = scope2objs(scope)
end
def properties(type)
properties = type.validproperties
end
def metaparams(type)
mparams = []
Puppet::Type.eachmetaparam { |param|
mparams.push param
}
mparams
end
def params(type)
params = []
type.parameters.each { |name,property|
params.push name
}
params
end
def randthing(thing,type)
list = self.send(thing,type)
list[rand(list.length)]
end
def randeach(type)
[:properties, :metaparams, :params].collect { |thing|
randthing(thing,type)
}
end
@@snippets = {
true => [
%{File { mode => 755 }}
],
}
def disabled_test_defaults
Puppet::Type.eachtype { |type|
next if type.name == :puppet or type.name == :component
rands = randeach(type)
name = type.name.to_s.capitalize
[0..1, 0..2].each { |range|
params = rands[range]
paramstr = params.collect { |param|
"%s => fake" % param
}.join(", ")
str = "%s { %s }" % [name, paramstr]
scope = nil
assert_nothing_raised {
scope = snippet2scope(str)
}
defaults = nil
assert_nothing_raised {
defaults = scope.lookupdefaults(name)
}
p defaults
params.each { |param|
puts "%s => '%s'" % [name,param]
assert(defaults.include?(param))
}
}
}
end
# this is here in case no tests get defined; otherwise we get a warning
def test_nothing
end
def snippet_filecreate
%w{a b c d}.each { |letter|
path = "/tmp/create%stest" % letter
assert_file(path)
if %w{a b}.include?(letter)
assert_mode_equal(0755, path)
end
}
end
def snippet_simpledefaults
path = "/tmp/defaulttest"
assert_file(path)
assert_mode_equal(0755, path)
end
def snippet_simpleselector
files = %w{a b c d}.collect { |letter|
path = "/tmp/snippetselect%stest" % letter
assert_file(path)
assert_mode_equal(0755, path)
}
end
def snippet_classpathtest
path = "/tmp/classtest"
file = @file[path]
assert(file, "did not create file %s" % path)
assert_nothing_raised {
assert_equal(
"//testing/component[componentname]/File[/tmp/classtest]",
file.path)
}
end
def snippet_argumentdefaults
path1 = "/tmp/argumenttest1"
path2 = "/tmp/argumenttest2"
file1 = @file[path1]
file2 = @file[path2]
assert_file(path1)
assert_mode_equal(0755, path1)
assert_file(path2)
assert_mode_equal(0644, path2)
end
def snippet_casestatement
paths = %w{
/tmp/existsfile
/tmp/existsfile2
/tmp/existsfile3
/tmp/existsfile4
/tmp/existsfile5
}
paths.each { |path|
file = @file[path]
assert(file, "File %s is missing" % path)
assert_mode_equal(0755, path)
}
end
def snippet_implicititeration
paths = %w{a b c d e f g h}.collect { |l| "/tmp/iteration%stest" % l }
paths.each { |path|
file = @file[path]
assert_file(path)
assert_mode_equal(0755, path)
}
end
def snippet_multipleinstances
paths = %w{a b c}.collect { |l| "/tmp/multipleinstances%s" % l }
paths.each { |path|
assert_file(path)
assert_mode_equal(0755, path)
}
end
def snippet_namevartest
file = "/tmp/testfiletest"
dir = "/tmp/testdirtest"
assert_file(file)
assert_file(dir)
assert_equal(:directory, @file[dir].should(:ensure), "Directory is not set to be a directory")
end
def snippet_scopetest
file = "/tmp/scopetest"
assert_file(file)
assert_mode_equal(0755, file)
end
def snippet_failmissingexecpath
file = "/tmp/exectesting1"
execfile = "/tmp/execdisttesting"
assert_file(file)
assert_nil(Puppet::Type.type(:exec)["exectest"], "invalid exec was created")
end
def snippet_selectorvalues
nums = %w{1 2 3 4 5}
files = nums.collect { |n|
"/tmp/selectorvalues%s" % n
}
files.each { |f|
assert_file(f)
assert_mode_equal(0755, f)
}
end
def snippet_singleselector
nums = %w{1 2 3}
files = nums.collect { |n|
"/tmp/singleselector%s" % n
}
files.each { |f|
assert_file(f)
assert_mode_equal(0755, f)
}
end
def snippet_falsevalues
file = "/tmp/falsevaluesfalse"
assert_file(file)
end
def disabled_snippet_classargtest
[1,2].each { |num|
file = "/tmp/classargtest%s" % num
assert_file(file)
assert_mode_equal(0755, file)
}
end
def snippet_classheirarchy
[1,2,3].each { |num|
file = "/tmp/classheir%s" % num
assert_file(file)
assert_mode_equal(0755, file)
}
end
def snippet_singleary
[1,2,3,4].each { |num|
file = "/tmp/singleary%s" % num
assert_file(file)
}
end
def snippet_classincludes
[1,2,3].each { |num|
file = "/tmp/classincludes%s" % num
assert_file(file)
assert_mode_equal(0755, file)
}
end
def snippet_componentmetaparams
["/tmp/component1", "/tmp/component2"].each { |file|
assert_file(file)
}
end
def snippet_aliastest
%w{/tmp/aliastest /tmp/aliastest2 /tmp/aliastest3}.each { |file|
assert_file(file)
}
end
def snippet_singlequote
{ 1 => 'a $quote',
2 => 'some "\yayness\"'
}.each { |count, str|
path = "/tmp/singlequote%s" % count
assert_file(path)
assert_equal(str, @file[path].should(:content))
}
end
# There's no way to actually retrieve the list of classes from the
# transaction.
def snippet_tag
end
# Make sure that set tags are correctly in place, yo.
def snippet_tagged
tags = {"testing" => true, "yayness" => false,
"both" => false, "bothtrue" => true, "define" => true}
tags.each do |tag, retval|
assert_file("/tmp/tagged#{tag}#{retval.to_s}")
end
end
def snippet_defineoverrides
file = "/tmp/defineoverrides1"
assert_file(file)
assert_mode_equal(0755, file)
end
def snippet_deepclassheirarchy
5.times { |i|
i += 1
file = "/tmp/deepclassheir%s" % i
assert_file(file)
}
end
def snippet_emptyclass
# There's nothing to check other than that it works
end
def snippet_emptyexec
assert(Puppet::Type.type(:exec)["touch /tmp/emptyexectest"],
"Did not create exec")
end
def snippet_multisubs
path = "/tmp/multisubtest"
assert_file(path)
file = @file[path]
assert_equal("sub2", file.should(:content), "sub2 did not override content")
assert_mode_equal(0755, path)
end
def snippet_collection
assert_file("/tmp/colltest1")
assert_nil(@file["/tmp/colltest2"], "Incorrectly collected file")
end
def snippet_virtualresources
%w{1 2 3 4}.each do |num|
assert_file("/tmp/virtualtest#{num}")
end
end
def snippet_componentrequire
%w{1 2}.each do |num|
assert_file("/tmp/testing_component_requires#{num}",
"#{num} does not exist")
end
end
def snippet_realize_defined_types
assert_file("/tmp/realize_defined_test1")
assert_file("/tmp/realize_defined_test2")
end
def snippet_fqparents
assert_file("/tmp/fqparent1", "Did not make file from parent class")
assert_file("/tmp/fqparent2", "Did not make file from subclass")
end
def snippet_fqdefinition
assert_file("/tmp/fqdefinition",
"Did not make file from fully-qualified definition")
end
def snippet_subclass_name_duplication
assert_file("/tmp/subclass_name_duplication1",
"Did not make first file from duplicate subclass names")
assert_file("/tmp/subclass_name_duplication2",
"Did not make second file from duplicate subclass names")
end
# Iterate across each of the snippets and create a test.
Dir.entries(snippetdir).sort.each { |file|
next if file =~ /^\./
mname = "snippet_" + file.sub(/\.pp$/, '')
if self.method_defined?(mname)
#eval("alias %s %s" % [testname, mname])
testname = ("test_" + mname).intern
self.send(:define_method, testname) {
facts = {
"hostname" => "testhost",
"domain" => "domain.com",
"ipaddress" => "127.0.0.1",
"fqdn" => "testhost.domain.com"
}
Facter.stubs(:each)
facts.each do |name, value|
Facter.stubs(:value).with(name).returns(value)
end
# first parse the file
server = Puppet::Network::Handler.master.new(
:Manifest => snippet(file),
:Local => true
)
- server.send(:fact_handler).stubs(:set)
- server.send(:fact_handler).stubs(:get).returns(facts)
+ facts = Puppet::Node::Facts.new("testhost", facts)
+ Puppet::Node::Facts.stubs(:post)
+ Puppet::Node::Facts.stubs(:get).returns(facts)
client = Puppet::Network::Client.master.new(
:Master => server,
:Cache => false
)
- client.class.stubs(:facts).returns(facts)
+ client.class.stubs(:facts).returns(facts.values)
assert(client.local)
assert_nothing_raised {
client.getconfig()
}
client = Puppet::Network::Client.master.new(
:Master => server,
:Cache => false
)
assert(client.local)
# Now do it again
Puppet::Type.allclear
assert_nothing_raised {
client.getconfig()
}
Puppet::Type.eachtype { |type|
type.each { |obj|
# don't worry about this for now
#unless obj.name == "puppet[top]" or
# obj.is_a?(Puppet.type(:schedule))
# assert(obj.parent, "%s has no parent" % obj.name)
#end
assert(obj.name)
}
}
assert_nothing_raised {
self.send(mname)
}
client.clear
}
mname = mname.intern
end
}
end
diff --git a/test/lib/puppettest.rb b/test/lib/puppettest.rb
index 45c5b2ed9..06b85f147 100755
--- a/test/lib/puppettest.rb
+++ b/test/lib/puppettest.rb
@@ -1,313 +1,314 @@
# Add .../test/lib
$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
# Add .../lib
$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../../lib')))
require 'puppet'
require 'mocha'
# 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")
ARGV.delete("-d")
$console = true
end
module PuppetTest
# Munge cli arguments, so we can enable debugging if we want
# and so we can run just specific methods.
def self.munge_argv
require 'getoptlong'
result = GetoptLong.new(
[ "--debug", "-d", GetoptLong::NO_ARGUMENT ],
[ "--resolve", "-r", GetoptLong::REQUIRED_ARGUMENT ],
[ "-n", GetoptLong::REQUIRED_ARGUMENT ],
[ "--help", "-h", GetoptLong::NO_ARGUMENT ]
)
usage = "USAGE: TESTOPTS='[-n -n ...] [-d]' rake [target] [target] ..."
opts = []
dir = method = nil
result.each { |opt,arg|
case opt
when "--resolve"
dir, method = arg.split(",")
when "--debug"
$puppet_debug = true
Puppet::Util::Log.level = :debug
Puppet::Util::Log.newdestination(:console)
when "--help"
puts usage
exit
else
opts << opt << arg
end
}
suites = nil
args = ARGV.dup
# Reset the options, so the test suite can deal with them (this is
# what makes things like '-n' work).
opts.each { |o| ARGV << o }
return args
end
# Find the root of the Puppet tree; this is not the test directory, but
# the parent of that dir.
def basedir(*list)
unless defined? @@basedir
Dir.chdir(File.dirname(__FILE__)) do
@@basedir = File.dirname(File.dirname(Dir.getwd))
end
end
if list.empty?
@@basedir
else
File.join(@@basedir, *list)
end
end
def cleanup(&block)
@@cleaners << block
end
def datadir(*list)
File.join(basedir, "test", "data", *list)
end
def exampledir(*args)
unless defined? @@exampledir
@@exampledir = File.join(basedir, "examples")
end
if args.empty?
return @@exampledir
else
return File.join(@@exampledir, *args)
end
end
module_function :basedir, :datadir, :exampledir
# Rails clobbers RUBYLIB, thanks
def libsetup
curlibs = ENV["RUBYLIB"].split(":")
$:.reject do |dir| dir =~ /^\/usr/ end.each do |dir|
unless curlibs.include?(dir)
curlibs << dir
end
end
ENV["RUBYLIB"] = curlibs.join(":")
end
def logcollector
collector = []
Puppet::Util::Log.newdestination(collector)
cleanup do
Puppet::Util::Log.close(collector)
end
collector
end
def rake?
$0 =~ /test_loader/
end
# Redirect stdout and stderr
def redirect
@stderr = tempfile
@stdout = tempfile
$stderr = File.open(@stderr, "w")
$stdout = File.open(@stdout, "w")
cleanup do
$stderr = STDERR
$stdout = STDOUT
end
end
def setup
@memoryatstart = Puppet::Util.memory
if defined? @@testcount
@@testcount += 1
else
@@testcount = 0
end
@configpath = File.join(tmpdir,
"configdir" + @@testcount.to_s + "/"
)
unless defined? $user and $group
$user = nonrootuser().uid.to_s
$group = nonrootgroup().gid.to_s
end
Puppet.config.clear
Puppet[:user] = $user
Puppet[:group] = $group
Puppet[:confdir] = @configpath
Puppet[:vardir] = @configpath
unless File.exists?(@configpath)
Dir.mkdir(@configpath)
end
@@tmpfiles = [@configpath, tmpdir()]
@@tmppids = []
@@cleaners = []
@logs = []
# If we're running under rake, then disable debugging and such.
#if rake? or ! Puppet[:debug]
if defined?($puppet_debug) or ! rake?
if textmate?
Puppet[:color] = false
end
Puppet::Util::Log.newdestination(@logs)
if defined? $console
Puppet.info @method_name
Puppet::Util::Log.newdestination(:console)
Puppet[:trace] = true
end
Puppet::Util::Log.level = :debug
#$VERBOSE = 1
else
Puppet::Util::Log.close
Puppet::Util::Log.newdestination(@logs)
Puppet[:httplog] = tempfile()
end
Puppet[:ignoreschedules] = true
end
def tempfile
if defined? @@tmpfilenum
@@tmpfilenum += 1
else
@@tmpfilenum = 1
end
f = File.join(self.tmpdir(), "tempfile_" + @@tmpfilenum.to_s)
@@tmpfiles << f
return f
end
def textmate?
if ENV["TM_FILENAME"]
return true
else
return false
end
end
def tstdir
dir = tempfile()
Dir.mkdir(dir)
return dir
end
def tmpdir
unless defined? @tmpdir and @tmpdir
@tmpdir = case Facter["operatingsystem"].value
when "Darwin": "/private/tmp"
when "SunOS": "/var/tmp"
else
"/tmp"
end
@tmpdir = File.join(@tmpdir, "puppettesting" + Process.pid.to_s)
unless File.exists?(@tmpdir)
FileUtils.mkdir_p(@tmpdir)
File.chmod(01777, @tmpdir)
end
end
@tmpdir
end
def teardown
@@cleaners.each { |cleaner| cleaner.call() }
@@tmpfiles.each { |file|
unless file =~ /tmp/
puts "Not deleting tmpfile %s" % file
next
end
if FileTest.exists?(file)
system("chmod -R 755 %s" % file)
system("rm -rf %s" % file)
end
}
@@tmpfiles.clear
@@tmppids.each { |pid|
%x{kill -INT #{pid} 2>/dev/null}
}
@@tmppids.clear
Puppet::Type.allclear
Puppet::Util::Storage.clear
Puppet.clear
+ Puppet.config.clear
@memoryatend = Puppet::Util.memory
diff = @memoryatend - @memoryatstart
if diff > 1000
Puppet.info "%s#%s memory growth (%s to %s): %s" %
[self.class, @method_name, @memoryatstart, @memoryatend, diff]
end
# reset all of the logs
Puppet::Util::Log.close
@logs.clear
# Just in case there are processes waiting to die...
require 'timeout'
begin
Timeout::timeout(5) do
Process.waitall
end
rescue Timeout::Error
# just move on
end
mocha_verify
if File.stat("/dev/null").mode & 007777 != 0666
File.open("/tmp/nullfailure", "w") { |f|
f.puts self.class
}
exit(74)
end
end
def logstore
@logs = []
Puppet::Util::Log.newdestination(@logs)
end
end
require 'puppettest/support'
require 'puppettest/filetesting'
require 'puppettest/fakes'
require 'puppettest/exetest'
require 'puppettest/parsertesting'
require 'puppettest/servertest'
require 'puppettest/testcase'
# $Id$
diff --git a/test/lib/puppettest/parsertesting.rb b/test/lib/puppettest/parsertesting.rb
index eef0cd8bc..c4bd7dc2b 100644
--- a/test/lib/puppettest/parsertesting.rb
+++ b/test/lib/puppettest/parsertesting.rb
@@ -1,408 +1,407 @@
require 'puppettest'
require 'puppet/rails'
module PuppetTest::ParserTesting
include PuppetTest
AST = Puppet::Parser::AST
Compile = Puppet::Parser::Compile
# A fake class that we can use for testing evaluation.
class FakeAST
attr_writer :evaluate
def evaluated?
defined? @evaluated and @evaluated
end
def evaluate(*args)
@evaluated = true
return @evaluate
end
def initialize(val = nil)
if val
@evaluate = val
end
end
def reset
@evaluated = nil
end
def safeevaluate(*args)
evaluate()
end
end
def astarray(*args)
AST::ASTArray.new(
:children => args
)
end
def mkcompile(parser = nil)
- require 'puppet/network/handler/node'
parser ||= mkparser
node = mknode
return Compile.new(node, parser)
end
def mknode(name = nil)
require 'puppet/node'
name ||= "nodename"
Puppet::Network::Handler.handler(:node)
Puppet::Node.new(name)
end
def mkinterp(args = {})
args[:Code] ||= "" unless args.include?(:Manifest)
args[:Local] ||= true
Puppet::Parser::Interpreter.new(args)
end
def mkparser
Puppet::Parser::Parser.new()
end
def mkscope(hash = {})
hash[:parser] ||= mkparser
compile ||= mkcompile(hash[:parser])
compile.topscope.source = (hash[:parser].findclass("", "") || hash[:parser].newclass(""))
unless compile.topscope.source
raise "Could not find source for scope"
end
# Make the 'main' stuff
compile.send(:evaluate_main)
compile.topscope
end
def classobj(name, hash = {})
hash[:file] ||= __FILE__
hash[:line] ||= __LINE__
hash[:type] ||= name
AST::HostClass.new(hash)
end
def tagobj(*names)
args = {}
newnames = names.collect do |name|
if name.is_a? AST
name
else
nameobj(name)
end
end
args[:type] = astarray(*newnames)
assert_nothing_raised("Could not create tag %s" % names.inspect) {
return AST::Tag.new(args)
}
end
def resourcedef(type, title, params)
unless title.is_a?(AST)
title = stringobj(title)
end
assert_nothing_raised("Could not create %s %s" % [type, title]) {
return AST::Resource.new(
:file => __FILE__,
:line => __LINE__,
:title => title,
:type => type,
:params => resourceinst(params)
)
}
end
def virt_resourcedef(*args)
res = resourcedef(*args)
res.virtual = true
res
end
def resourceoverride(type, title, params)
assert_nothing_raised("Could not create %s %s" % [type, name]) {
return AST::ResourceOverride.new(
:file => __FILE__,
:line => __LINE__,
:object => resourceref(type, title),
:type => type,
:params => resourceinst(params)
)
}
end
def resourceref(type, title)
assert_nothing_raised("Could not create %s %s" % [type, title]) {
return AST::ResourceReference.new(
:file => __FILE__,
:line => __LINE__,
:type => type,
:title => stringobj(title)
)
}
end
def fileobj(path, hash = {"owner" => "root"})
assert_nothing_raised("Could not create file %s" % path) {
return resourcedef("file", path, hash)
}
end
def nameobj(name)
assert_nothing_raised("Could not create name %s" % name) {
return AST::Name.new(
:file => tempfile(),
:line => rand(100),
:value => name
)
}
end
def typeobj(name)
assert_nothing_raised("Could not create type %s" % name) {
return AST::Type.new(
:file => tempfile(),
:line => rand(100),
:value => name
)
}
end
def nodedef(name)
assert_nothing_raised("Could not create node %s" % name) {
return AST::NodeDef.new(
:file => tempfile(),
:line => rand(100),
:names => nameobj(name),
:code => AST::ASTArray.new(
:children => [
varobj("%svar" % name, "%svalue" % name),
fileobj("/%s" % name)
]
)
)
}
end
def resourceinst(hash)
assert_nothing_raised("Could not create resource instance") {
params = hash.collect { |param, value|
resourceparam(param, value)
}
return AST::ResourceInstance.new(
:file => tempfile(),
:line => rand(100),
:children => params
)
}
end
def resourceparam(param, value)
# Allow them to pass non-strings in
if value.is_a?(String)
value = stringobj(value)
end
assert_nothing_raised("Could not create param %s" % param) {
return AST::ResourceParam.new(
:file => tempfile(),
:line => rand(100),
:param => param,
:value => value
)
}
end
def stringobj(value)
AST::String.new(
:file => tempfile(),
:line => rand(100),
:value => value
)
end
def varobj(name, value)
unless value.is_a? AST
value = stringobj(value)
end
assert_nothing_raised("Could not create %s code" % name) {
return AST::VarDef.new(
:file => tempfile(),
:line => rand(100),
:name => nameobj(name),
:value => value
)
}
end
def varref(name)
assert_nothing_raised("Could not create %s variable" % name) {
return AST::Variable.new(
:file => __FILE__,
:line => __LINE__,
:value => name
)
}
end
def argobj(name, value)
assert_nothing_raised("Could not create %s compargument" % name) {
return AST::CompArgument.new(
:children => [nameobj(name), stringobj(value)]
)
}
end
def defaultobj(type, params)
pary = []
params.each { |p,v|
pary << AST::ResourceParam.new(
:file => __FILE__,
:line => __LINE__,
:param => p,
:value => stringobj(v)
)
}
past = AST::ASTArray.new(
:file => __FILE__,
:line => __LINE__,
:children => pary
)
assert_nothing_raised("Could not create defaults for %s" % type) {
return AST::ResourceDefaults.new(
:file => __FILE__,
:line => __LINE__,
:type => type,
:params => past
)
}
end
def taggedobj(name, ftype = :statement)
functionobj("tagged", name, ftype)
end
def functionobj(function, name, ftype = :statement)
func = nil
assert_nothing_raised do
func = Puppet::Parser::AST::Function.new(
:name => function,
:ftype => ftype,
:arguments => AST::ASTArray.new(
:children => [nameobj(name)]
)
)
end
return func
end
# This assumes no nodes
def assert_creates(manifest, *files)
interp = nil
assert_nothing_raised {
interp = Puppet::Parser::Interpreter.new(
:Manifest => manifest,
:UseNodes => false
)
}
config = nil
assert_nothing_raised {
config = interp.compile(mknode)
}
comp = nil
assert_nothing_raised {
comp = config.extract.to_type
}
assert_apply(comp)
files.each do |file|
assert(FileTest.exists?(file), "Did not create %s" % file)
end
end
def mk_transobject(file = "/etc/passwd")
obj = nil
assert_nothing_raised {
obj = Puppet::TransObject.new("file", file)
obj["owner"] = "root"
obj["mode"] = "644"
}
return obj
end
def mk_transbucket(*resources)
bucket = nil
assert_nothing_raised {
bucket = Puppet::TransBucket.new
bucket.name = "yayname"
bucket.type = "yaytype"
}
resources.each { |o| bucket << o }
return bucket
end
# Make a tree of resources, yielding if desired
def mk_transtree(depth = 4, width = 2)
top = nil
assert_nothing_raised {
top = Puppet::TransBucket.new
top.name = "top"
top.type = "bucket"
}
bucket = top
file = tempfile()
depth.times do |i|
resources = []
width.times do |j|
path = tempfile + i.to_s
obj = Puppet::TransObject.new("file", path)
obj["owner"] = "root"
obj["mode"] = "644"
# Yield, if they want
if block_given?
yield(obj, i, j)
end
resources << obj
end
newbucket = mk_transbucket(*resources)
bucket.push newbucket
bucket = newbucket
end
return top
end
# Take a list of AST resources, evaluate them, and return the results
def assert_evaluate(children)
top = nil
assert_nothing_raised("Could not create top object") {
top = AST::ASTArray.new(
:children => children
)
}
trans = nil
scope = nil
assert_nothing_raised {
scope = Puppet::Parser::Scope.new()
trans = scope.evaluate(:ast => top)
}
return trans
end
end
diff --git a/test/lib/puppettest/runnable_test.rb b/test/lib/puppettest/runnable_test.rb
new file mode 100644
index 000000000..e4b0f9033
--- /dev/null
+++ b/test/lib/puppettest/runnable_test.rb
@@ -0,0 +1,30 @@
+# Manage whether a test is runnable.
+module PuppetTest
+ module RunnableTest
+ # Confine this test based on specified criteria. The keys of the
+ # hash should be the message to use if the test is not suitable,
+ # and the values should be either 'true' or 'false'; true values
+ # mean the test is suitable.
+ def confine(hash)
+ @confines ||= {}
+ hash.each do |message, result|
+ @confines[message] = result
+ end
+ end
+
+ # Evaluate all of our tests to see if any of them are false
+ # and thus whether this test is considered not runnable.
+ def runnable?
+ @messages ||= []
+ return false unless @messages.empty?
+ return true unless defined? @confines
+ @confines.find_all do |message, result|
+ ! result
+ end.each do |message, result|
+ @messages << message
+ end
+
+ return @messages.empty?
+ end
+ end
+end
diff --git a/test/lib/puppettest/testcase.rb b/test/lib/puppettest/testcase.rb
index cfedeee26..15c835854 100644
--- a/test/lib/puppettest/testcase.rb
+++ b/test/lib/puppettest/testcase.rb
@@ -1,48 +1,29 @@
#!/usr/bin/env ruby
#
# Created by Luke A. Kanies on 2007-03-05.
# Copyright (c) 2007. All rights reserved.
require 'puppettest'
+require 'puppettest/runnable_test'
class PuppetTest::TestCase < Test::Unit::TestCase
include PuppetTest
- def self.confine(hash)
- @confines ||= {}
- hash.each do |message, result|
- @confines[message] = result
- end
- end
-
- def self.runnable?
- @messages ||= []
- return false unless @messages.empty?
- return true unless defined? @confines
- @confines.find_all do |message, result|
- ! result
- end.each do |message, result|
- @messages << message
- end
-
- return @messages.empty?
- end
+ extend PuppetTest::RunnableTest
def self.suite
# Always skip this parent class. It'd be nice if there were a
# "supported" way to do this.
if self == PuppetTest::TestCase
suite = Test::Unit::TestSuite.new(name)
return suite
elsif self.runnable?
return super
else
if defined? $console
puts "Skipping %s: %s" % [name, @messages.join(", ")]
end
suite = Test::Unit::TestSuite.new(name)
return suite
end
end
end
-
-# $Id$
diff --git a/test/network/handler/configuration.rb b/test/network/handler/configuration.rb
index 0964a4c5e..29a393769 100755
--- a/test/network/handler/configuration.rb
+++ b/test/network/handler/configuration.rb
@@ -1,173 +1,173 @@
#!/usr/bin/env ruby
$:.unshift("../../lib") if __FILE__ =~ /\.rb$/
require 'puppettest'
require 'puppet/network/handler/configuration'
class TestHandlerConfiguration < Test::Unit::TestCase
include PuppetTest
Config = Puppet::Network::Handler.handler(:configuration)
# Check all of the setup stuff.
def test_initialize
config = nil
assert_nothing_raised("Could not create local config") do
config = Config.new(:Local => true)
end
assert(config.local?, "Config is not considered local after being started that way")
end
# Test creation/returning of the interpreter
def test_interpreter
config = Config.new
# First test the defaults
args = {}
config.instance_variable_set("@options", args)
config.expects(:create_interpreter).with(args).returns(:interp)
assert_equal(:interp, config.send(:interpreter), "Did not return the interpreter")
# Now run it again and make sure we get the same thing
assert_equal(:interp, config.send(:interpreter), "Did not cache the interpreter")
end
def test_create_interpreter
config = Config.new(:Local => false)
args = {}
# Try it first with defaults.
Puppet::Parser::Interpreter.expects(:new).with(:Local => config.local?).returns(:interp)
assert_equal(:interp, config.send(:create_interpreter, args), "Did not return the interpreter")
# Now reset it and make sure a specified manifest passes through
file = tempfile
args[:Manifest] = file
Puppet::Parser::Interpreter.expects(:new).with(:Local => config.local?, :Manifest => file).returns(:interp)
assert_equal(:interp, config.send(:create_interpreter, args), "Did not return the interpreter")
# And make sure the code does, too
args.delete(:Manifest)
args[:Code] = "yay"
Puppet::Parser::Interpreter.expects(:new).with(:Local => config.local?, :Code => "yay").returns(:interp)
assert_equal(:interp, config.send(:create_interpreter, args), "Did not return the interpreter")
end
# Make sure node objects get appropriate data added to them.
def test_add_node_data
# First with no classes
config = Config.new
fakenode = Object.new
# Set the server facts to something
config.instance_variable_set("@server_facts", :facts)
- fakenode.expects(:fact_merge).with(:facts)
+ fakenode.expects(:merge).with(:facts)
config.send(:add_node_data, fakenode)
# Now try it with classes.
config.instance_variable_set("@options", {:Classes => %w{a b}})
list = []
fakenode = Object.new
- fakenode.expects(:fact_merge).with(:facts)
+ fakenode.expects(:merge).with(:facts)
fakenode.expects(:classes).returns(list).times(2)
config.send(:add_node_data, fakenode)
assert_equal(%w{a b}, list, "Did not add classes to node")
end
def test_compile
config = Config.new
# First do a local
node = mock 'node'
node.stubs(:name).returns(:mynode)
node.stubs(:environment).returns(:myenv)
interp = mock 'interpreter'
interp.stubs(:environment)
interp.expects(:compile).with(node).returns(:config)
config.expects(:interpreter).returns(interp)
Puppet.expects(:notice) # The log message from benchmarking
assert_equal(:config, config.send(:compile, node), "Did not return config")
# Now try it non-local
node = mock 'node'
node.stubs(:name).returns(:mynode)
node.stubs(:environment).returns(:myenv)
interp = mock 'interpreter'
interp.stubs(:environment)
interp.expects(:compile).with(node).returns(:config)
config = Config.new(:Local => true)
config.expects(:interpreter).returns(interp)
assert_equal(:config, config.send(:compile, node), "Did not return config")
end
def test_set_server_facts
config = Config.new
assert_nothing_raised("Could not call :set_server_facts") do
config.send(:set_server_facts)
end
facts = config.instance_variable_get("@server_facts")
%w{servername serverversion serverip}.each do |fact|
assert(facts.include?(fact), "Config did not set %s fact" % fact)
end
end
def test_translate
# First do a local config
config = Config.new(:Local => true)
assert_equal(:plain, config.send(:translate, :plain), "Attempted to translate local config")
# Now a non-local
config = Config.new(:Local => false)
obj = Object.new
yamld = Object.new
obj.expects(:to_yaml).with(:UseBlock => true).returns(yamld)
CGI.expects(:escape).with(yamld).returns(:translated)
assert_equal(:translated, config.send(:translate, obj), "Did not return translated config")
end
# Check that we're storing the node freshness into the rails db. Hackilicious.
def test_update_node_check
# This is stupid.
config = Config.new
node = Object.new
node.expects(:name).returns(:hostname)
now = Object.new
Time.expects(:now).returns(now)
host = Object.new
host.expects(:last_freshcheck=).with(now)
host.expects(:save)
# Only test the case where rails is there
Puppet[:storeconfigs] = true
Puppet.features.expects(:rails?).returns(true)
Puppet::Rails.expects(:connect)
Puppet::Rails::Host.expects(:find_or_create_by_name).with(:hostname).returns(host)
config.send(:update_node_check, node)
end
def test_version
# First try the case where we can't look up the node
config = Config.new
node = Object.new
Puppet::Node.stubs(:search).with(:client).returns(false, node)
interp = Object.new
assert_instance_of(Bignum, config.version(:client), "Did not return configuration version")
# And then when we find the node.
config = Config.new
config.expects(:update_node_check).with(node)
interp = Object.new
interp.expects(:configuration_version).returns(:version)
config.expects(:interpreter).returns(interp)
assert_equal(:version, config.version(:client), "Did not return configuration version")
end
end
diff --git a/test/network/handler/facts.rb b/test/network/handler/facts.rb
deleted file mode 100755
index 03327b8c4..000000000
--- a/test/network/handler/facts.rb
+++ /dev/null
@@ -1,112 +0,0 @@
-#!/usr/bin/env ruby
-
-$:.unshift("../../lib") if __FILE__ =~ /\.rb$/
-
-require 'puppettest'
-require 'mocha'
-require 'puppet/network/handler/facts'
-
-class TestFactsHandler < Test::Unit::TestCase
- include PuppetTest::ServerTest
-
- def setup
- super
-
- @class = Puppet::Network::Handler.handler(:facts)
-
- @@client_facts = {}
-
- unless Puppet::Util::FactStore.store(:testing)
- Puppet::Util::FactStore.newstore(:testing) do
- def get(node)
- @@client_facts[node]
- end
-
- def set(node, facts)
- @@client_facts[node] = facts
- end
- end
- end
-
- Puppet[:factstore] = :testing
-
- @handler = @class.new
-
- @facts = {:a => :b, :c => :d}
- @name = "foo"
-
- @backend = @handler.instance_variable_get("@backend")
- end
-
- def teardown
- @@client_facts.clear
- end
-
- def test_strip_internal
- @facts[:_puppet_one] = "yay"
- @facts[:_puppet_two] = "boo"
- @facts[:_puppetthree] = "foo"
-
- newfacts = nil
- assert_nothing_raised("Could not call strip_internal") do
- newfacts = @handler.send(:strip_internal, @facts)
- end
-
- [:_puppet_one, :_puppet_two, :_puppetthree].each do |name|
- assert(@facts.include?(name), "%s was removed in strip_internal from original hash" % name)
- end
- [:_puppet_one, :_puppet_two].each do |name|
- assert(! newfacts.include?(name), "%s was not removed in strip_internal" % name)
- end
- assert_equal("foo", newfacts[:_puppetthree], "_puppetthree was removed in strip_internal")
- end
-
- def test_add_internal
- newfacts = nil
- assert_nothing_raised("Could not call strip_internal") do
- newfacts = @handler.send(:add_internal, @facts)
- end
-
- assert_instance_of(Time, newfacts[:_puppet_timestamp], "Did not set timestamp in add_internal")
- assert(! @facts.include?(:_puppet_timestamp), "Modified original hash in add_internal")
- end
-
- def test_set
- newfacts = @facts.dup
- newfacts[:_puppet_timestamp] = Time.now
- @handler.expects(:add_internal).with(@facts).returns(newfacts)
- @backend.expects(:set).with(@name, newfacts).returns(nil)
-
- assert_nothing_raised("Could not set facts") do
- assert_nil(@handler.set(@name, @facts), "handler.set did not return nil")
- end
- end
-
- def test_get
- prefacts = @facts.dup
- prefacts[:_puppet_timestamp] = Time.now
- @@client_facts[@name] = prefacts
- @handler.expects(:strip_internal).with(prefacts).returns(@facts)
- @backend.expects(:get).with(@name).returns(prefacts)
-
- assert_nothing_raised("Could not retrieve facts") do
- assert_equal(@facts, @handler.get(@name), "did not get correct answer from handler.get")
- end
-
- @handler = @class.new
- assert_nothing_raised("Failed to call 'get' with no stored facts") do
- @handler.get("nosuchname")
- end
- end
-
- def test_store_date
- time = Time.now
- @facts[:_puppet_timestamp] = time
-
- @handler.expects(:get).with(@name).returns(@facts)
-
- assert_equal(time.to_i, @handler.store_date(@name), "Did not retrieve timestamp correctly")
- end
-end
-
-# $Id$
diff --git a/test/network/handler/master.rb b/test/network/handler/master.rb
index a976726ef..9749c7bdf 100755
--- a/test/network/handler/master.rb
+++ b/test/network/handler/master.rb
@@ -1,156 +1,161 @@
#!/usr/bin/env ruby
$:.unshift("../../lib") if __FILE__ =~ /\.rb$/
require 'puppettest'
require 'puppet/network/handler/master'
class TestMaster < Test::Unit::TestCase
include PuppetTest::ServerTest
+ def teardown
+ super
+ Puppet::Indirector::Indirection.clear_cache
+ end
+
def test_defaultmanifest
textfiles { |file|
Puppet[:manifest] = file
client = nil
master = nil
assert_nothing_raised() {
# this is the default server setup
master = Puppet::Network::Handler.master.new(
:Manifest => file,
:UseNodes => false,
:Local => true
)
}
assert_nothing_raised() {
client = Puppet::Network::Client.master.new(
:Master => master
)
}
# pull our configuration
assert_nothing_raised() {
client.getconfig
stopservices
Puppet::Type.allclear
}
break
}
end
def test_filereread
# Start with a normal setting
Puppet[:filetimeout] = 15
manifest = mktestmanifest()
facts = Puppet::Network::Client.master.facts
# Store them, so we don't determine frshness based on facts.
Puppet::Util::Storage.cache(:configuration)[:facts] = facts
file2 = @createdfile + "2"
@@tmpfiles << file2
client = master = nil
assert_nothing_raised() {
# this is the default server setup
master = Puppet::Network::Handler.master.new(
:Manifest => manifest,
:UseNodes => false,
:Local => true
)
}
assert_nothing_raised() {
client = Puppet::Network::Client.master.new(
:Master => master
)
}
assert(client, "did not create master client")
# The client doesn't have a config, so it can't be up to date
assert(! client.fresh?(facts),
"Client is incorrectly up to date")
Puppet.config.use(:main)
assert_nothing_raised {
client.getconfig
client.apply
}
# Now it should be up to date
assert(client.fresh?(facts), "Client is not up to date")
# Cache this value for later
parse1 = master.freshness("mynode")
# Verify the config got applied
assert(FileTest.exists?(@createdfile),
"Created file %s does not exist" % @createdfile)
Puppet::Type.allclear
sleep 1.5
# Create a new manifest
File.open(manifest, "w") { |f|
f.puts "file { \"%s\": ensure => file }\n" % file2
}
# Verify that the master doesn't immediately reparse the file; we
# want to wait through the timeout
assert_equal(parse1, master.freshness("mynode"), "Master did not wait through timeout")
assert(client.fresh?(facts), "Client is not up to date")
# Then eliminate it
Puppet[:filetimeout] = 0
# Now make sure the master does reparse
#Puppet.notice "%s vs %s" % [parse1, master.freshness]
assert(parse1 != master.freshness("mynode"), "Master did not reparse file")
assert(! client.fresh?(facts), "Client is incorrectly up to date")
# Retrieve and apply the new config
assert_nothing_raised {
client.getconfig
client.apply
}
assert(client.fresh?(facts), "Client is not up to date")
assert(FileTest.exists?(file2), "Second file %s does not exist" % file2)
end
# Make sure we're correctly doing clientname manipulations.
# Testing to make sure we always get a hostname and IP address.
def test_clientname
# create our master
master = Puppet::Network::Handler.master.new(
:Manifest => tempfile,
:UseNodes => true,
:Local => true
)
# First check that 'cert' works
Puppet[:node_name] = "cert"
# Make sure we get the fact data back when nothing is set
facts = {"hostname" => "fact_hostname", "ipaddress" => "fact_ip"}
certname = "cert_hostname"
certip = "cert_ip"
resname, resip = master.send(:clientname, nil, nil, facts)
assert_equal(facts["hostname"], resname, "Did not use fact hostname when no certname was present")
assert_equal(facts["ipaddress"], resip, "Did not use fact ip when no certname was present")
# Now try it with the cert stuff present
resname, resip = master.send(:clientname, certname, certip, facts)
assert_equal(certname, resname, "Did not use cert hostname when certname was present")
assert_equal(certip, resip, "Did not use cert ip when certname was present")
# And reset the node_name stuff and make sure we use it.
Puppet[:node_name] = :facter
resname, resip = master.send(:clientname, certname, certip, facts)
assert_equal(facts["hostname"], resname, "Did not use fact hostname when nodename was set to facter")
assert_equal(facts["ipaddress"], resip, "Did not use fact ip when nodename was set to facter")
end
end
# $Id$