diff options
167 files changed, 7296 insertions, 3339 deletions
@@ -1,3 +1,8 @@ + Added a boolean 'crl' default value. Now we have a location for + the CA CRL and the host CRL, and then a setting for configuring + whether we should even use a CRL. This way we aren't trying to + set file paths to 'false' to disable the CRL. + Moving all confine code out of the Provider class, and fixing #1197. Created a Confiner module for the Provider class methods, enhanced the interface between it and the Confine class to make sure binary @@ -198,6 +203,8 @@ permission denied exceptions caught, thus forbidding them from being replaced with 'nil'. + The environment is now available as a variable in the manifests. + Fixed #1043 -- autoloading now searches the plugins directory in each module, in addition to the lib directory. The 'lib' directory is also deprecated, but supported for now to give diff --git a/bin/puppetca b/bin/puppetca index 759b602ac..75988432f 100755 --- a/bin/puppetca +++ b/bin/puppetca @@ -97,7 +97,7 @@ # Licensed under the GNU Public License require 'puppet' -require 'puppet/sslcertificates' +require 'puppet/ssl/certificate_authority' require 'getoptlong' options = [ @@ -120,22 +120,20 @@ Puppet.settings.addargs(options) result = GetoptLong.new(*options) -mode = nil -all = false -generate = nil +modes = Puppet::SSL::CertificateAuthority::Interface::INTERFACE_METHODS -modes = [:clean, :list, :revoke, :generate, :sign, :print, :verify] +all = false +mode = nil begin result.each { |opt,arg| case opt + when "--clean" + mode = :destroy when "--all" all = true when "--debug" Puppet::Util::Log.level = :debug - when "--generate" - generate = arg - mode = :generate when "--help" if Puppet.features.usage? RDoc::usage && exit @@ -143,12 +141,6 @@ begin puts "No help available unless you have RDoc::usage installed" exit end - when "--list" - mode = :list - when "--revoke" - mode = :revoke - when "--sign" - mode = :sign when "--version" puts "%s" % Puppet.version exit @@ -174,12 +166,14 @@ Puppet.parse_config Puppet.genconfig Puppet.genmanifest +Puppet::Util::Log.newdestination :console + +Puppet::SSL::Host.ca_location = :local + begin - ca = Puppet::SSLCertificates::CA.new() + ca = Puppet::SSL::CertificateAuthority.new rescue => detail - if Puppet[:debug] - puts detail.backtrace - end + puts detail.backtrace if Puppet[:trace] puts detail.to_s exit(23) end @@ -189,172 +183,16 @@ unless mode exit(12) end -if [:verify, :print, :generate, :clean, :revoke, :list].include?(mode) +if all + hosts = :all +else hosts = ARGV.collect { |h| h.downcase } end -if [:sign, :list].include?(mode) - waiting = ca.list - unless waiting.length > 0 or (mode == :list and all) - puts "No certificates to sign" - if ARGV.length > 0 - exit(17) - else - exit(0) - end - end -end - -case mode -when :list - waiting = ca.list - if waiting.length > 0 - puts waiting.join("\n") - end - if all - puts ca.list_signed.collect { |cert | cert.sub(/^/,"+ ") }.join("\n") - end -when :clean - if hosts.empty? and all == false - $stderr.puts "You must specify one or more hosts to clean or --all to clean all host certificates" - exit(24) - end - - cleaned = false - - if all - certs = ca.list - if certs.empty? - $stderr.puts "No certificates to clean" - exit(24) - end - certs.each do |c| - ca.clean(c) - end - cleaned = true - else - hosts.each do |host| - cert = ca.getclientcert(host)[0] - if cert.nil? - $stderr.puts "Could not find client certificate for %s" % host - next - end - ca.clean(host) - cleaned = true - end - end - - unless cleaned - exit(27) - end -when :sign - to_sign = ARGV.collect { |h| h.downcase } - unless to_sign.length > 0 or all - $stderr.puts( - "You must specify one or more hosts to sign certificates for or --all to sign all certificates" - ) - exit(24) - end - - unless all - to_sign.each { |host| - unless waiting.include?(host) - $stderr.puts "No waiting request for %s" % host - end - } - waiting = waiting.find_all { |host| - to_sign.include?(host) - } - end - - waiting.each { |host| - begin - csr = ca.getclientcsr(host) - rescue => detail - $stderr.puts "Could not retrieve request for %s: %s" % [host, detail] - end - - begin - ca.sign(csr) - $stderr.puts "Signed %s" % host - rescue => detail - $stderr.puts "Could not sign request for %s: %s" % [host, detail] - end - - begin - ca.removeclientcsr(host) - rescue => detail - $stderr.puts "Could not remove request for %s: %s" % [host, detail] - end - } -when :generate - # we need to generate a certificate for a host - hosts.each { |host| - puts "Generating certificate for %s" % host - cert = Puppet::SSLCertificates::Certificate.new( - :name => host - ) - cert.mkcsr - signedcert, cacert = ca.sign(cert.csr) - - cert.cert = signedcert - cert.cacert = cacert - cert.write - } -when :print - hosts.each { |h| - cert = ca.getclientcert(h)[0] - puts cert.to_text - } -when :revoke - hosts.each { |h| - serial = nil - if h =~ /^0x[0-9a-f]+$/ - serial = h.to_i(16) - elsif h =~ /^[0-9]+$/ - serial = h.to_i - else - cert = ca.getclientcert(h)[0] - if cert.nil? - $stderr.puts "Could not find client certificate for %s" % h - else - serial = cert.serial - end - end - unless serial.nil? - ca.revoke(serial) - puts "Revoked certificate with serial #{serial}" - end - } -when :verify - unless ssl = %x{which openssl}.chomp - raise "Can't verify certificates without the openssl binary and could not find one" - end - success = true - - cacert = Puppet[:localcacert] - - hosts.each do |host| - print "%s: " % host - file = ca.host2certfile(host) - unless FileTest.exist?(file) - puts "no certificate found" - success = false - next - end - - - command = %{#{ssl} verify -CAfile #{cacert} #{file}} - output = %x{#{command}} - if $? == 0 - puts "valid" - else - puts output - success = false - end - end -else - $stderr.puts "Invalid mode %s" % mode - exit(42) +begin + ca.apply(mode, :to => hosts) +rescue => detail + puts detail.backtrace if Puppet[:trace] + puts detail.to_s + exit(24) end - diff --git a/bin/puppetd b/bin/puppetd index 96d0e5ee8..d408af7d3 100755 --- a/bin/puppetd +++ b/bin/puppetd @@ -321,6 +321,11 @@ if options[:centrallogs] Puppet::Util::Log.newdestination(logdest) end +# We need to specify a ca location for things to work, but +# until the REST cert transfers are working, it needs to +# be local. +Puppet::SSL::Host.ca_location = :local + # We need tomake the client either way, we just don't start it # if --no-client is set. client = Puppet::Network::Client.master.new(args) @@ -342,10 +347,9 @@ if Puppet[:daemonize] client.daemonize end -unless Puppet::Network::HttpPool.read_cert - # If we don't already have the certificate, then create a client to - # request one. Use the special ca stuff, don't use the normal server and port. - caclient = Puppet::Network::Client.ca.new() +caclient = Puppet::Network::Client.ca.new() + +unless caclient.read_cert if options[:waitforcert] > 0 begin while ! caclient.request_cert do @@ -364,7 +368,7 @@ unless Puppet::Network::HttpPool.read_cert end # Now read the new cert in. - if Puppet::Network::HttpPool.read_cert + if caclient.read_cert # If we read it in, then get rid of our existing http connection. client.recycle_connection Puppet.notice "Got signed certificate" diff --git a/bin/puppetmasterd b/bin/puppetmasterd index b4733e604..625b75d52 100755 --- a/bin/puppetmasterd +++ b/bin/puppetmasterd @@ -8,8 +8,7 @@ # = Usage # # puppetmasterd [-D|--daemonize|--no-daemonize] [-d|--debug] [-h|--help] -# [-l|--logdest <file>|console|syslog] [--nobucket] [--nonodes] -# [-v|--verbose] [-V|--version] +# [-l|--logdest <file>|console|syslog] [-v|--verbose] [-V|--version] # # = Description # @@ -22,7 +21,7 @@ # parameter, so you can specify '--ssldir <directory>' as an argument. # # See the configuration file documentation at -# http://reductivelabs.com/projects/puppet/reference/configref.html for +# http://reductivelabs.com/trac/puppet/wiki/ConfigurationReference for # the full list of acceptable parameters. A commented list of all # configuration options can also be generated by running puppetmasterdd with # '--genconfig'. @@ -44,16 +43,6 @@ # Defaults to sending messages to syslog, or the console # if debugging or verbosity is enabled. # -# nobucket:: -# Do not function as a file bucket. -# -# nonodes:: -# Do not use individual node designations; each node will receive the result -# of evaluating the entire configuration. -# -# noreports:: -# Do not start the reports server. -# # verbose:: # Enable verbosity. # @@ -81,16 +70,12 @@ end require 'getoptlong' require 'puppet' -require 'puppet/network/handler' -require 'puppet/sslcertificates' +require 'puppet/network/server' options = [ [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], [ "--help", "-h", GetoptLong::NO_ARGUMENT ], [ "--logdest", "-l", GetoptLong::REQUIRED_ARGUMENT ], - [ "--nobucket", GetoptLong::NO_ARGUMENT ], - [ "--noreports", GetoptLong::NO_ARGUMENT ], - [ "--nonodes", GetoptLong::NO_ARGUMENT ], [ "--verbose", "-v", GetoptLong::NO_ARGUMENT ], [ "--version", "-V", GetoptLong::NO_ARGUMENT ] ] @@ -100,15 +85,7 @@ Puppet.settings.addargs(options) result = GetoptLong.new(*options) -master = {} -ca = {} -report = {} -bucket = {} - options = { - :havereport => true, - :havebucket => true, - :havemaster => true, :setdest => false, :verbose => false, :debug => false @@ -128,14 +105,6 @@ begin puts "No help available unless you have RDoc::usage installed" exit end - when "--noreports" - options[:havereport] = false - when "--nomaster" - options[:havemaster] = false - when "--nobucket" - options[:havebucket] = false - when "--nonodes" - master[:UseNodes] = false when "--logdest" begin Puppet::Util::Log.newdestination(arg) @@ -191,87 +160,54 @@ Puppet::Node::Facts.terminus_class = :yaml # Cache our nodes in yaml. Currently not configurable. Puppet::Node.cache_class = :yaml -require 'etc' - -handlers = { - :Status => {}, - :FileServer => {} -} - -if options[:havemaster] - handlers[:Master] = master +# Configure all of the SSL stuff. +if Puppet::SSL::CertificateAuthority.ca? + Puppet::SSL::Host.ca_location = :local + Puppet.settings.use :main, :ssl, :ca + Puppet::SSL::CertificateAuthority.instance +else + Puppet::SSL::Host.ca_location = :none end -if options[:havereport] - handlers[:Report] = report -end - -if Puppet[:ca] - handlers[:CA] = ca -end - -if options[:havebucket] - handlers[:FileBucket] = bucket -end +require 'etc' if Puppet[:parseonly] begin - Puppet::Network::Handler.master.new(master) + Puppet::Parser::Interpreter.new.parser(Puppet[:environment]) rescue => detail - if Puppet[:trace] - puts detail.backtrace - end - $stderr.puts detail - exit(32) + Puppet.err detail + exit 1 end - # we would have already exited if the file weren't syntactically correct exit(0) end -webserver = server = nil -begin - case Puppet[:servertype] - when "webrick" - # use the default, um, everything - require 'puppet/network/http_server/webrick' - webserver = server = Puppet::Network::HTTPServer::WEBrick.new(:Handlers => handlers) - when "mongrel": - require 'puppet/network/http_server/mongrel' - server = Puppet::Network::HTTPServer::Mongrel.new(handlers) - addr = Puppet[:bindaddress] - if addr == "" - addr = "127.0.0.1" - end - webserver = Mongrel::HttpServer.new(addr, Puppet[:masterport]) - webserver.register("/", server) - else - Puppet.err "Invalid server type %s" % Puppet[:servertype] - exit(45) - end -rescue => detail - if Puppet[:trace] - puts detail.backtrace - end - $stderr.puts detail - exit(1) +require 'puppet/file_serving/content' +require 'puppet/file_serving/metadata' +require 'puppet/checksum' + +xmlrpc_handlers = [:Status, :FileServer, :Master, :Report, :Filebucket] +rest_handlers = [:file_content, :file_metadata, :certificate, :facts, :catalog, :report, :checksum] + +if Puppet[:ca] + xmlrpc_handlers << :CA end +server = Puppet::Network::Server.new(:handlers => rest_handlers, :xmlrpc_handlers => xmlrpc_handlers) + if Process.uid == 0 begin Puppet::Util.chuser rescue => detail - if Puppet[:debug] - puts detail.backtrace - end + puts detail.backtrace if Puppet[:trace] $stderr.puts "Could not change user to %s: %s" % [Puppet[:user], detail] exit(39) end end -# Mongrel doesn't shut down like webrick; we really need to write plugins for it. -if Puppet[:servertype] == "webrick" - Puppet.newservice(server) -end +# Tell Puppet to manage this service for us, which has it starting and stopping +# as appropriate. +Puppet.newservice(server) + Puppet.settraps if Puppet[:daemonize] @@ -279,10 +215,5 @@ if Puppet[:daemonize] end Puppet.notice "Starting Puppet server version %s" % [Puppet.version] -case Puppet[:servertype] -when "webrick" - Puppet.start -when "mongrel": - webserver.run.join -end +Puppet.start diff --git a/install.rb b/install.rb index 207f74a0b..c32c24245 100755 --- a/install.rb +++ b/install.rb @@ -51,10 +51,10 @@ begin if $haverdoc rst2man = %x{which rst2man.py} $haveman = true - else + else $haveman = false end -rescue +rescue puts "Missing rst2man; skipping man page creation" $haveman = false end @@ -151,6 +151,15 @@ def prepare_installation InstallOptions.tests = true + if $haveman + InstallOptions.man = true + if RUBY_PLATFORM == "i386-mswin32" + InstallOptions.man = false + end + else + InstallOptions.man = false + end + ARGV.options do |opts| opts.banner = "Usage: #{File.basename($0)} [options]" opts.separator "" @@ -173,6 +182,7 @@ def prepare_installation end opts.on('--full', 'Performs a full installation. All', 'optional installation steps are run.') do |full| InstallOptions.rdoc = true + InstallOptions.man = true InstallOptions.ri = true InstallOptions.tests = true end @@ -277,13 +287,13 @@ def build_man(bins) File.unlink("./puppet.conf.rst") # Create binary man pages - bins.each do |bin| + bins.each do |bin| b = bin.gsub( "bin/", "") %x{#{bin} --help > ./#{b}.rst} %x{#{rst2man} ./#{b}.rst ./man/man8/#{b}.8} File.unlink("./#{b}.rst") end - rescue SystemCallError + rescue SystemCallError $stderr.puts "Couldn't build man pages: " + $! $stderr.puts "Continuing with install..." end diff --git a/lib/puppet.rb b/lib/puppet.rb index 66a52f9e3..cde25721e 100644 --- a/lib/puppet.rb +++ b/lib/puppet.rb @@ -205,16 +205,7 @@ module Puppet end # Parse the config file for this process. - def self.parse_config(oldconfig = nil) - # First look for the old configuration file. - oldconfig ||= File.join(Puppet[:confdir], Puppet[:name].to_s + ".conf") - if FileTest.exists?(oldconfig) and Puppet[:name] != "puppet" - Puppet.warning "Individual config files are deprecated; remove %s and use puppet.conf" % oldconfig - Puppet.settings.old_parse(oldconfig) - return - end - - # Now check for the normal config. + def self.parse_config if Puppet[:config] and File.exists? Puppet[:config] Puppet.debug "Parsing %s" % Puppet[:config] Puppet.settings.parse(Puppet[:config]) diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index 57299b7e7..d77ec0486 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -60,12 +60,6 @@ module Puppet this directory can be removed without causing harm (although it might result in spurious service restarts)." }, - :ssldir => { - :default => "$confdir/ssl", - :mode => 0771, - :owner => "root", - :desc => "Where SSL certificates are kept." - }, :rundir => { :default => rundir, :mode => 01777, @@ -148,7 +142,20 @@ module Puppet but then ship with tools that do not know how to handle signed ints, so the UIDs show up as huge numbers that can then not be fed back into the system. This is a hackish way to fail in a slightly more useful way when that happens."], - :node_terminus => ["plain", "Where to find information about nodes."] + :node_terminus => ["plain", "Where to find information about nodes."], + :httplog => { :default => "$logdir/http.log", + :owner => "root", + :mode => 0640, + :desc => "Where the puppetd web server logs." + }, + :http_proxy_host => ["none", + "The HTTP proxy host to use for outgoing connections. Note: You + may need to use a FQDN for the server hostname when using a proxy."], + :http_proxy_port => [3128, + "The HTTP proxy port to use for outgoing connections"], + :http_enable_post_connection_check => [true, + "Boolean; wheter or not puppetd should validate the server + SSL certificate against the request hostname."] ) hostname = Facter["hostname"].value @@ -159,14 +166,21 @@ module Puppet fqdn = hostname end - Puppet.setdefaults(:ssl, + Puppet.setdefaults(:main, :certname => [fqdn, "The name to use when handling certificates. Defaults to the fully qualified domain name."], :certdnsnames => ['', "The DNS names on the Server certificate as a colon-separated list. If it's anything other than an empty string, it will be used as an alias in the created certificate. By default, only the server gets an alias set up, and only for 'puppet'."], :certdir => ["$ssldir/certs", "The certificate directory."], + :ssldir => { + :default => "$confdir/ssl", + :mode => 0771, + :owner => "root", + :desc => "Where SSL certificates are kept." + }, :publickeydir => ["$ssldir/public_keys", "The public key directory."], + :requestdir => ["$ssldir/certificate_requests", "Where host certificate requests are stored."], :privatekeydir => { :default => "$ssldir/private_keys", :mode => 0750, :desc => "The private key directory." @@ -182,7 +196,7 @@ module Puppet }, :hostcsr => { :default => "$ssldir/csr_$certname.pem", :mode => 0644, - :desc => "Where individual hosts store and look for their certificates." + :desc => "Where individual hosts store and look for their certificate requests." }, :hostcert => { :default => "$certdir/$certname.pem", :mode => 0644, @@ -199,6 +213,11 @@ module Puppet :localcacert => { :default => "$certdir/ca.pem", :mode => 0644, :desc => "Where each client stores the CA certificate." + }, + :hostcrl => { :default => "$ssldir/crl.pem", + :mode => 0644, + :desc => "Where the host's certificate revocation list can be found. + This is distinct from the certificate authority's CRL." } ) @@ -230,7 +249,12 @@ module Puppet :owner => "$user", :group => "$group", :mode => 0664, - :desc => "The certificate revocation list (CRL) for the CA. Set this to 'false' if you do not want to use a CRL." + :desc => "The certificate revocation list (CRL) for the CA. Will be used if present but otherwise ignored.", + :hook => proc do |value| + if value == 'false' + Puppet.warning "Setting the :cacrl to 'false' is deprecated; Puppet will just ignore the crl if yours is missing" + end + end }, :caprivatedir => { :default => "$cadir/private", :owner => "$user", @@ -258,7 +282,7 @@ module Puppet :serial => { :default => "$cadir/serial", :owner => "$user", :group => "$group", - :mode => 0600, + :mode => 0644, :desc => "Where the serial number for certificates is stored." }, :autosign => { :default => "$confdir/autosign.conf", @@ -291,7 +315,7 @@ module Puppet self.setdefaults(self.settings[:name], :config => ["$confdir/puppet.conf", "The configuration file for #{Puppet[:name]}."], - :pidfile => ["", "The pid file"], + :pidfile => ["$rundir/$name.pid", "The pid file"], :bindaddress => ["", "The address to bind to. Mongrel servers default to 127.0.0.1 and WEBrick defaults to 0.0.0.0."], :servertype => ["webrick", "The type of server to use. Currently supported @@ -382,19 +406,6 @@ module Puppet :mode => 0640, :desc => "The log file for puppetd. This is generally not used." }, - :httplog => { :default => "$logdir/http.log", - :owner => "root", - :mode => 0640, - :desc => "Where the puppetd web server logs." - }, - :http_proxy_host => ["none", - "The HTTP proxy host to use for outgoing connections. Note: You - may need to use a FQDN for the server hostname when using a proxy."], - :http_proxy_port => [3128, - "The HTTP proxy port to use for outgoing connections"], - :http_enable_post_connection_check => [true, - "Boolean; wheter or not puppetd should validate the server - SSL certificate against the request hostname."], :server => ["puppet", "The server to which server puppetd should connect"], :ignoreschedules => [false, diff --git a/lib/puppet/file_serving/configuration.rb b/lib/puppet/file_serving/configuration.rb index ccf0957d1..9c38aaa19 100644 --- a/lib/puppet/file_serving/configuration.rb +++ b/lib/puppet/file_serving/configuration.rb @@ -5,25 +5,20 @@ require 'puppet' require 'puppet/file_serving' require 'puppet/file_serving/mount' +require 'puppet/util/cacher' class Puppet::FileServing::Configuration require 'puppet/file_serving/configuration/parser' + extend Puppet::Util::Cacher + @config_fileuration = nil Mount = Puppet::FileServing::Mount - # Remove our singleton instance. - def self.clear_cache - @config_fileuration = nil - end - # Create our singleton configuration. def self.create - unless @config_fileuration - @config_fileuration = new() - end - @config_fileuration + attr_cache(:configuration) { new() } end private_class_method :new diff --git a/lib/puppet/file_serving/mount.rb b/lib/puppet/file_serving/mount.rb index 8e5bd03e8..c52cedbfb 100644 --- a/lib/puppet/file_serving/mount.rb +++ b/lib/puppet/file_serving/mount.rb @@ -4,6 +4,7 @@ require 'puppet/network/authstore' require 'puppet/util/logging' +require 'puppet/util/cacher' require 'puppet/file_serving' require 'puppet/file_serving/metadata' require 'puppet/file_serving/content' @@ -12,12 +13,16 @@ require 'puppet/file_serving/content' # or content objects. class Puppet::FileServing::Mount < Puppet::Network::AuthStore include Puppet::Util::Logging + extend Puppet::Util::Cacher - @@localmap = nil - - # Clear the cache. This is only ever used for testing. - def self.clear_cache - @@localmap = nil + def self.localmap + attr_cache(:localmap) { + { "h" => Facter.value("hostname"), + "H" => [Facter.value("hostname"), + Facter.value("domain")].join("."), + "d" => Facter.value("domain") + } + } end attr_reader :name @@ -173,14 +178,6 @@ class Puppet::FileServing::Mount < Puppet::Network::AuthStore # Cache this manufactured map, since if it's used it's likely # to get used a lot. def localmap - unless @@localmap - @@localmap = { - "h" => Facter.value("hostname"), - "H" => [Facter.value("hostname"), - Facter.value("domain")].join("."), - "d" => Facter.value("domain") - } - end - @@localmap + self.class.localmap end end diff --git a/lib/puppet/indirector/certificate/ca.rb b/lib/puppet/indirector/certificate/ca.rb new file mode 100644 index 000000000..b64080e16 --- /dev/null +++ b/lib/puppet/indirector/certificate/ca.rb @@ -0,0 +1,9 @@ +require 'puppet/indirector/ssl_file' +require 'puppet/ssl/certificate' + +class Puppet::SSL::Certificate::Ca < Puppet::Indirector::SslFile + desc "Manage the CA collection of signed SSL certificates on disk." + + store_in :signeddir + store_ca_at :cacert +end diff --git a/lib/puppet/indirector/certificate/file.rb b/lib/puppet/indirector/certificate/file.rb new file mode 100644 index 000000000..c19d001f4 --- /dev/null +++ b/lib/puppet/indirector/certificate/file.rb @@ -0,0 +1,9 @@ +require 'puppet/indirector/ssl_file' +require 'puppet/ssl/certificate' + +class Puppet::SSL::Certificate::File < Puppet::Indirector::SslFile + desc "Manage SSL certificates on disk." + + store_in :certdir + store_ca_at :localcacert +end diff --git a/lib/puppet/indirector/certificate/rest.rb b/lib/puppet/indirector/certificate/rest.rb new file mode 100644 index 000000000..f88d60d40 --- /dev/null +++ b/lib/puppet/indirector/certificate/rest.rb @@ -0,0 +1,6 @@ +require 'puppet/ssl/certificate' +require 'puppet/indirector/rest' + +class Puppet::SSL::Certificate::Rest < Puppet::Indirector::REST + desc "Find and save certificates over HTTP via REST." +end diff --git a/lib/puppet/indirector/certificate_request/ca.rb b/lib/puppet/indirector/certificate_request/ca.rb new file mode 100644 index 000000000..e90f43a03 --- /dev/null +++ b/lib/puppet/indirector/certificate_request/ca.rb @@ -0,0 +1,14 @@ +require 'puppet/indirector/ssl_file' +require 'puppet/ssl/certificate_request' + +class Puppet::SSL::CertificateRequest::Ca < Puppet::Indirector::SslFile + desc "Manage the CA collection of certificate requests on disk." + + store_in :csrdir + + def save(request) + result = super + Puppet.notice "%s has a waiting certificate request" % request.key + result + end +end diff --git a/lib/puppet/indirector/certificate_request/file.rb b/lib/puppet/indirector/certificate_request/file.rb new file mode 100644 index 000000000..274311e2c --- /dev/null +++ b/lib/puppet/indirector/certificate_request/file.rb @@ -0,0 +1,8 @@ +require 'puppet/indirector/ssl_file' +require 'puppet/ssl/certificate_request' + +class Puppet::SSL::CertificateRequest::File < Puppet::Indirector::SslFile + desc "Manage the collection of certificate requests on disk." + + store_in :requestdir +end diff --git a/lib/puppet/indirector/certificate_request/rest.rb b/lib/puppet/indirector/certificate_request/rest.rb new file mode 100644 index 000000000..6df014583 --- /dev/null +++ b/lib/puppet/indirector/certificate_request/rest.rb @@ -0,0 +1,6 @@ +require 'puppet/ssl/certificate_request' +require 'puppet/indirector/rest' + +class Puppet::SSL::CertificateRequest::Rest < Puppet::Indirector::REST + desc "Find and save certificate requests over HTTP via REST." +end diff --git a/lib/puppet/indirector/certificate_revocation_list/ca.rb b/lib/puppet/indirector/certificate_revocation_list/ca.rb new file mode 100644 index 000000000..66cc23e50 --- /dev/null +++ b/lib/puppet/indirector/certificate_revocation_list/ca.rb @@ -0,0 +1,8 @@ +require 'puppet/indirector/ssl_file' +require 'puppet/ssl/certificate_revocation_list' + +class Puppet::SSL::CertificateRevocationList::Ca < Puppet::Indirector::SslFile + desc "Manage the CA collection of certificate requests on disk." + + store_at :cacrl +end diff --git a/lib/puppet/indirector/certificate_revocation_list/file.rb b/lib/puppet/indirector/certificate_revocation_list/file.rb new file mode 100644 index 000000000..037aa6b8c --- /dev/null +++ b/lib/puppet/indirector/certificate_revocation_list/file.rb @@ -0,0 +1,8 @@ +require 'puppet/indirector/ssl_file' +require 'puppet/ssl/certificate_revocation_list' + +class Puppet::SSL::CertificateRevocationList::File < Puppet::Indirector::SslFile + desc "Manage the global certificate revocation list." + + store_at :hostcrl +end diff --git a/lib/puppet/indirector/certificate_revocation_list/rest.rb b/lib/puppet/indirector/certificate_revocation_list/rest.rb new file mode 100644 index 000000000..13cc95c87 --- /dev/null +++ b/lib/puppet/indirector/certificate_revocation_list/rest.rb @@ -0,0 +1,6 @@ +require 'puppet/ssl/certificate_revocation_list' +require 'puppet/indirector/rest' + +class Puppet::SSL::CertificateRevocationList::Rest < Puppet::Indirector::REST + desc "Find and save certificate revocation lists over HTTP via REST." +end diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb index 05464f8c9..c70259304 100644 --- a/lib/puppet/indirector/indirection.rb +++ b/lib/puppet/indirector/indirection.rb @@ -1,20 +1,17 @@ require 'puppet/util/docs' require 'puppet/indirector/envelope' require 'puppet/indirector/request' +require 'puppet/util/cacher' # The class that connects functional classes with their different collection # back-ends. Each indirection has a set of associated terminus classes, # each of which is a subclass of Puppet::Indirector::Terminus. class Puppet::Indirector::Indirection + include Puppet::Util::Cacher include Puppet::Util::Docs @@indirections = [] - # Clear all cached termini from all indirections. - def self.clear_cache - @@indirections.each { |ind| ind.clear_cache } - end - # Find an indirection by name. This is provided so that Terminus classes # can specifically hook up with the indirections they are associated with. def self.instance(name) @@ -54,13 +51,6 @@ class Puppet::Indirector::Indirection @cache_class = class_name end - # Clear our cached list of termini, and reset the cache name - # so it's looked up again. - # 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) @@ -104,7 +94,6 @@ class Puppet::Indirector::Indirection @model = model @name = name - @termini = {} @cache_class = nil @terminus_class = nil @@ -138,7 +127,7 @@ class Puppet::Indirector::Indirection raise Puppet::DevError, "No terminus specified for %s; cannot redirect" % self.name end - return @termini[terminus_name] ||= make_terminus(terminus_name) + return termini[terminus_name] ||= make_terminus(terminus_name) end # This can be used to select the terminus class. @@ -299,4 +288,9 @@ class Puppet::Indirector::Indirection end return klass.new end + + # Use cached termini. + def termini + attr_cache(:termini) { Hash.new } + end end diff --git a/lib/puppet/indirector/key/ca.rb b/lib/puppet/indirector/key/ca.rb new file mode 100644 index 000000000..3604de22b --- /dev/null +++ b/lib/puppet/indirector/key/ca.rb @@ -0,0 +1,20 @@ +require 'puppet/indirector/ssl_file' +require 'puppet/ssl/key' + +class Puppet::SSL::Key::Ca < Puppet::Indirector::SslFile + desc "Manage the CA's private on disk. This terminus *only* works + with the CA key, because that's the only key that the CA ever interacts + with." + + # This is just to pass the validation in the base class. Eh. + store_at :cakey + + store_ca_at :cakey + + def path(name) + unless ca?(name) + raise ArgumentError, "The :ca terminus can only handle the CA private key" + end + super + end +end diff --git a/lib/puppet/indirector/key/file.rb b/lib/puppet/indirector/key/file.rb new file mode 100644 index 000000000..4536f8aa7 --- /dev/null +++ b/lib/puppet/indirector/key/file.rb @@ -0,0 +1,42 @@ +require 'puppet/indirector/ssl_file' +require 'puppet/ssl/key' + +class Puppet::SSL::Key::File < Puppet::Indirector::SslFile + desc "Manage SSL private and public keys on disk." + + store_in :privatekeydir + store_ca_at :cakey + + # Where should we store the public key? + def public_key_path(name) + if ca?(name) + Puppet[:capub] + else + File.join(Puppet[:publickeydir], name.to_s + ".pem") + end + end + + # Remove the public key, in addition to the private key + def destroy(request) + super + + return unless FileTest.exist?(public_key_path(request.key)) + + begin + File.unlink(public_key_path(request.key)) + rescue => detail + raise Puppet::Error, "Could not remove %s public key: %s" % [request.key, detail] + end + end + + # Save the public key, in addition to the private key. + def save(request) + super + + begin + File.open(public_key_path(request.key), "w") { |f| f.print request.instance.content.public_key.to_pem } + rescue => detail + raise Puppet::Error, "Could not write %s: %s" % [key, detail] + end + end +end diff --git a/lib/puppet/indirector/rest.rb b/lib/puppet/indirector/rest.rb index d33150fc2..77dd0538b 100644 --- a/lib/puppet/indirector/rest.rb +++ b/lib/puppet/indirector/rest.rb @@ -3,21 +3,20 @@ require 'uri' # Access objects via REST class Puppet::Indirector::REST < Puppet::Indirector::Terminus - def rest_connection_details { :host => Puppet[:server], :port => Puppet[:masterport].to_i } end def network_fetch(path) - network {|conn| conn.get("/#{path}").body } + network.get("/#{path}").body end def network_delete(path) - network {|conn| conn.delete("/#{path}").body } + network.delete("/#{path}").body end def network_put(path, data) - network {|conn| conn.put("/#{path}", data).body } + network.put("/#{path}", data).body end def find(request) @@ -46,8 +45,8 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus private - def network(&block) - Net::HTTP.start(rest_connection_details[:host], rest_connection_details[:port]) {|conn| yield(conn) } + def network + Puppet::Network::HttpPool.http_instance(rest_connection_details[:host], rest_connection_details[:port]) end def exception?(yaml_string) diff --git a/lib/puppet/indirector/ssl_file.rb b/lib/puppet/indirector/ssl_file.rb new file mode 100644 index 000000000..44a66fab2 --- /dev/null +++ b/lib/puppet/indirector/ssl_file.rb @@ -0,0 +1,147 @@ +require 'puppet/ssl' + +class Puppet::Indirector::SslFile < Puppet::Indirector::Terminus + # Specify the directory in which multiple files are stored. + def self.store_in(setting) + @directory_setting = setting + end + + # Specify a single file location for storing just one file. + # This is used for things like the CRL. + def self.store_at(setting) + @file_setting = setting + end + + # Specify where a specific ca file should be stored. + def self.store_ca_at(setting) + @ca_setting = setting + end + + class << self + attr_reader :directory_setting, :file_setting, :ca_setting + end + + # The full path to where we should store our files. + def self.collection_directory + return nil unless directory_setting + Puppet.settings[directory_setting] + end + + # The full path to an individual file we would be managing. + def self.file_location + return nil unless file_setting + Puppet.settings[file_setting] + end + + # The full path to a ca file we would be managing. + def self.ca_location + return nil unless ca_setting + Puppet.settings[ca_setting] + end + + # We assume that all files named 'ca' are pointing to individual ca files, + # rather than normal host files. It's a bit hackish, but all the other + # solutions seemed even more hackish. + def ca?(name) + name == Puppet::SSL::Host.ca_name + end + + def initialize + Puppet.settings.use(:main, :ssl) + + (collection_directory || file_location) or raise Puppet::DevError, "No file or directory setting provided; terminus %s cannot function" % self.class.name + end + + # Use a setting to determine our path. + def path(name) + if ca?(name) and ca_location + ca_location + elsif collection_directory + File.join(collection_directory, name.to_s + ".pem") + else + file_location + end + end + + # Remove our file. + def destroy(request) + path = path(request.key) + return false unless FileTest.exist?(path) + + begin + File.unlink(path) + rescue => detail + raise Puppet::Error, "Could not remove %s: %s" % [request.key, detail] + end + end + + # Find the file on disk, returning an instance of the model. + def find(request) + path = path(request.key) + + return nil unless FileTest.exist?(path) + + result = model.new(request.key) + result.read(path) + result + end + + # Save our file to disk. + def save(request) + path = path(request.key) + dir = File.dirname(path) + + raise Puppet::Error.new("Cannot save %s; parent directory %s does not exist" % [request.key, dir]) unless FileTest.directory?(dir) + raise Puppet::Error.new("Cannot save %s; parent directory %s is not writable" % [request.key, dir]) unless FileTest.writable?(dir) + + write(request.key, path) { |f| f.print request.instance.to_s } + end + + # Search for more than one file. At this point, it just returns + # an instance for every file in the directory. + def search(request) + dir = collection_directory + Dir.entries(dir).reject { |file| file !~ /\.pem$/ }.collect do |file| + name = file.sub(/\.pem$/, '') + result = model.new(name) + result.read(File.join(dir, file)) + result + end + end + + private + + # Demeterish pointers to class info. + def collection_directory + self.class.collection_directory + end + + def file_location + self.class.file_location + end + + def ca_location + self.class.ca_location + end + + # Yield a filehandle set up appropriately, either with our settings doing + # the work or opening a filehandle manually. + def write(name, path) + if ca?(name) and ca_location + Puppet.settings.write(self.class.ca_setting) { |f| yield f } + elsif file_location + Puppet.settings.write(self.class.file_setting) { |f| yield f } + else + begin + File.open(path, "w") { |f| yield f } + rescue => detail + raise Puppet::Error, "Could not write %s: %s" % [path, detail] + end + end + end +end + +# LAK:NOTE This has to be at the end, because classes like SSL::Key use this +# class, and this require statement loads those, which results in a load loop +# and lots of failures. +require 'puppet/ssl/host' diff --git a/lib/puppet/indirector/ssl_rsa.rb b/lib/puppet/indirector/ssl_rsa.rb deleted file mode 100644 index 162d8200a..000000000 --- a/lib/puppet/indirector/ssl_rsa.rb +++ /dev/null @@ -1,5 +0,0 @@ -# This is a stub class - -class Puppet::Indirector::SslRsa #:nodoc: -end - diff --git a/lib/puppet/indirector/ssl_rsa/file.rb b/lib/puppet/indirector/ssl_rsa/file.rb deleted file mode 100644 index 435aa8f86..000000000 --- a/lib/puppet/indirector/ssl_rsa/file.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'puppet/indirector/file' -require 'puppet/indirector/ssl_rsa' - -class Puppet::Indirector::SslRsa::File < Puppet::Indirector::File - desc "Store SSL keys on disk." - - def initialize - Puppet.settings.use(:ssl) - end - - def path(name) - if name == :ca - File.join Puppet.settings[:cadir], "ca_key.pem" - else - File.join Puppet.settings[:publickeydir], name.to_s + ".pem" - end - end - - def save(key) - File.open(path(key.name), "w") { |f| f.print key.to_pem } - end - - def find(name) - return nil unless FileTest.exists?(path(name)) - OpenSSL::PKey::RSA.new(File.read(path(name))) - end - - def destroy(name) - return nil unless FileTest.exists?(path(name)) - File.unlink(path(name)) and true - end - -end diff --git a/lib/puppet/metatype/container.rb b/lib/puppet/metatype/container.rb index 2bbe3f546..dbc8a3dee 100644 --- a/lib/puppet/metatype/container.rb +++ b/lib/puppet/metatype/container.rb @@ -36,7 +36,6 @@ class Puppet::Type obj.remove end @parameters.clear - self.class.delete(self) @parent = nil diff --git a/lib/puppet/metatype/instances.rb b/lib/puppet/metatype/instances.rb index 3f44413f8..34d542c5d 100644 --- a/lib/puppet/metatype/instances.rb +++ b/lib/puppet/metatype/instances.rb @@ -8,11 +8,13 @@ class Puppet::Type # retrieve a named instance of the current type def self.[](name) + raise "Global resource access is deprecated" @objects[name] || @aliases[name] end # add an instance by name to the class list of instances def self.[]=(name,object) + raise "Global resource storage is deprecated" newobj = nil if object.is_a?(Puppet::Type) newobj = object @@ -44,6 +46,7 @@ class Puppet::Type # Create an alias. We keep these in a separate hash so that we don't encounter # the objects multiple times when iterating over them. def self.alias(name, obj) + raise "Global resource aliasing is deprecated" if @objects.include?(name) unless @objects[name] == obj raise Puppet::Error.new( @@ -67,6 +70,7 @@ class Puppet::Type # remove all of the instances of a single type def self.clear + raise "Global resource removal is deprecated" if defined? @objects @objects.each do |name, obj| obj.remove(true) @@ -126,24 +130,6 @@ class Puppet::Type # XXX This will have to change when transobjects change to using titles title = hash.name - # if the object already exists - if self.isomorphic? and retobj = self[title] - # if only one of our objects is implicit, then it's easy to see - # who wins -- the non-implicit one. - if retobj.implicit? and ! implicit - Puppet.notice "Removing implicit %s" % retobj.title - # Remove all of the objects, but do not remove their subscriptions. - retobj.remove(false) - - # now pass through and create the new object - elsif implicit - Puppet.debug "Ignoring implicit %s[%s]" % [self.name, title] - return nil - else - raise Puppet::Error, "%s is already being managed" % retobj.ref - end - end - # create it anew # if there's a failure, destroy the object if it got that far, but raise # the error. @@ -153,8 +139,6 @@ class Puppet::Type Puppet.err "Could not create %s: %s" % [title, detail.to_s] if obj obj.remove(true) - elsif obj = self[title] - obj.remove(true) end raise end @@ -163,14 +147,12 @@ class Puppet::Type obj.implicit = true end - # Store the object by title - self[obj.title] = obj - return obj end # remove a specified object def self.delete(resource) + raise "Global resource removal is deprecated" return unless defined? @objects if @objects.include?(resource.title) @objects.delete(resource.title) @@ -191,6 +173,7 @@ class Puppet::Type # iterate across each of the type's instances def self.each + raise "Global resource iteration is deprecated" return unless defined? @objects @objects.each { |name,instance| yield instance @@ -199,6 +182,7 @@ class Puppet::Type # does the type have an object with the given name? def self.has_key?(name) + raise "Global resource access is deprecated" return @objects.has_key?(name) end @@ -258,10 +242,6 @@ class Puppet::Type provider_instances = {} providers_by_source.collect do |provider| provider.instances.collect do |instance| - # First try to get the resource if it already exists - # Skip instances that map to a managed resource with a different provider - next if resource = self[instance.name] and resource.provider.class != instance.class - # We always want to use the "first" provider instance we find, unless the resource # is already managed and has a different provider set if other = provider_instances[instance.name] @@ -271,12 +251,7 @@ class Puppet::Type end provider_instances[instance.name] = instance - if resource - resource.provider = instance - resource - else - create(:name => instance.name, :provider => instance, :check => :all) - end + create(:name => instance.name, :provider => instance, :check => :all) end end.flatten.compact end diff --git a/lib/puppet/metatype/metaparams.rb b/lib/puppet/metatype/metaparams.rb index edd594114..bf64d3a93 100644 --- a/lib/puppet/metatype/metaparams.rb +++ b/lib/puppet/metatype/metaparams.rb @@ -207,9 +207,6 @@ class Puppet::Type next end - # LAK:FIXME Old-school, add the alias to the class. - @resource.class.alias(other, @resource) - # Newschool, add it to the catalog. @resource.catalog.alias(@resource, other) end @@ -277,6 +274,7 @@ class Puppet::Type # to an object... tname, name = value reference = Puppet::ResourceReference.new(tname, name) + reference.catalog = resource.catalog # Either of the two retrieval attempts could have returned # nil. diff --git a/lib/puppet/metatype/relationships.rb b/lib/puppet/metatype/relationships.rb index 4fb78ae56..0070c6efb 100644 --- a/lib/puppet/metatype/relationships.rb +++ b/lib/puppet/metatype/relationships.rb @@ -17,7 +17,10 @@ class Puppet::Type # Figure out of there are any objects we can automatically add as # dependencies. - def autorequire + def autorequire(rel_catalog = nil) + rel_catalog ||= catalog + raise(Puppet::DevError, "You cannot add relationships without a catalog") unless rel_catalog + reqs = [] self.class.eachautorequire { |type, block| # Ignore any types we can't find, although that would be a bit odd. @@ -35,7 +38,7 @@ class Puppet::Type # Support them passing objects directly, to save some effort. unless dep.is_a? Puppet::Type # Skip autorequires that we aren't managing - unless dep = typeobj[dep] + unless dep = rel_catalog.resource(type, dep) next end end diff --git a/lib/puppet/metatype/schedules.rb b/lib/puppet/metatype/schedules.rb index 96ebce0ab..b4782f852 100644 --- a/lib/puppet/metatype/schedules.rb +++ b/lib/puppet/metatype/schedules.rb @@ -3,9 +3,13 @@ class Puppet::Type # the instantiation phase, so that the schedule can be anywhere in the # file. def schedule + unless catalog + warning "Cannot schedule without a schedule-containing catalog" + return nil + end unless defined? @schedule if name = self[:schedule] - if sched = Puppet.type(:schedule)[name] + if sched = catalog.resource(:schedule, name) @schedule = sched else self.fail "Could not find schedule %s" % name diff --git a/lib/puppet/network/client/ca.rb b/lib/puppet/network/client/ca.rb index a2704e451..5fbdfe9e3 100644 --- a/lib/puppet/network/client/ca.rb +++ b/lib/puppet/network/client/ca.rb @@ -45,7 +45,7 @@ class Puppet::Network::Client::CA < Puppet::Network::Client end unless @cert.check_private_key(key) - raise InvalidCertificate, "Certificate does not match private key. Try 'puppetca --clean %s' on the server." % Facter.value(:fqdn) + raise InvalidCertificate, "Certificate does not match private key. Try 'puppetca --clean %s' on the server." % Puppet[:certname] end # Only write the cert out if it passes validating. diff --git a/lib/puppet/network/client/master.rb b/lib/puppet/network/client/master.rb index 22bb3fa4e..a2b6499bb 100644 --- a/lib/puppet/network/client/master.rb +++ b/lib/puppet/network/client/master.rb @@ -75,7 +75,6 @@ class Puppet::Network::Client::Master < Puppet::Network::Client def clear @catalog.clear(true) if @catalog - Puppet::Type.allclear @catalog = nil end diff --git a/lib/puppet/network/handler/fileserver.rb b/lib/puppet/network/handler/fileserver.rb index 3e62cdbd9..183979429 100755 --- a/lib/puppet/network/handler/fileserver.rb +++ b/lib/puppet/network/handler/fileserver.rb @@ -496,12 +496,14 @@ class Puppet::Network::Handler @path = nil end + @files = {} + super() end def fileobj(path, links, client) obj = nil - if obj = Puppet.type(:file)[file_path(path, client)] + if obj = @files[file_path(path, client)] # 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 @@ -512,6 +514,7 @@ class Puppet::Network::Handler :name => file_path(path, client), :check => CHECKPARAMS ) + @files[file_path(path, client)] = obj end if links == :manage @@ -528,7 +531,7 @@ class Puppet::Network::Handler # Read the contents of the file at the relative path given. def read_file(relpath, client) - File.read(file_path(relpath, client)) + File.read(file_path(relpath, client)) end # Cache this manufactured map, since if it's used it's likely diff --git a/lib/puppet/network/handler/resource.rb b/lib/puppet/network/handler/resource.rb index f2a339751..7ec27b4dc 100755 --- a/lib/puppet/network/handler/resource.rb +++ b/lib/puppet/network/handler/resource.rb @@ -68,15 +68,11 @@ class Puppet::Network::Handler retrieve ||= :all ignore ||= [] - if obj = typeklass[name] - obj[:check] = retrieve - else - begin - obj = typeklass.create(:name => name, :check => retrieve) - rescue Puppet::Error => detail - raise Puppet::Error, "%s[%s] could not be created: %s" % - [type, name, detail] - end + begin + obj = typeklass.create(:name => name, :check => retrieve) + rescue Puppet::Error => detail + raise Puppet::Error, "%s[%s] could not be created: %s" % + [type, name, detail] end unless obj diff --git a/lib/puppet/network/http/handler.rb b/lib/puppet/network/http/handler.rb index 7113c92d3..c05d4907f 100644 --- a/lib/puppet/network/http/handler.rb +++ b/lib/puppet/network/http/handler.rb @@ -20,7 +20,7 @@ module Puppet::Network::HTTP::Handler private def model - @model + @model end def do_find(request, response) @@ -53,7 +53,7 @@ module Puppet::Network::HTTP::Handler end def save_object(obj) - obj.save + obj.save end def do_exception(request, response, exception, status=404) @@ -85,8 +85,7 @@ module Puppet::Network::HTTP::Handler %r{/#{@handler.to_s}s$}.match(path(request)) end - # methods to be overridden by the including web server class - + # methods to be overridden by the including web server class def register_handler raise NotImplementedError end diff --git a/lib/puppet/network/http/mongrel.rb b/lib/puppet/network/http/mongrel.rb index 9a4531c7a..847781cf2 100644 --- a/lib/puppet/network/http/mongrel.rb +++ b/lib/puppet/network/http/mongrel.rb @@ -16,6 +16,7 @@ class Puppet::Network::HTTP::Mongrel @protocols = args[:protocols] @handlers = args[:handlers] + @xmlrpc_handlers = args[:xmlrpc_handlers] @server = Mongrel::HttpServer.new(args[:address], args[:port]) setup_handlers @@ -38,12 +39,22 @@ class Puppet::Network::HTTP::Mongrel def setup_handlers @protocols.each do |protocol| + next if protocol == :xmlrpc klass = class_for_protocol(protocol) @handlers.each do |handler| @server.register('/' + handler.to_s, klass.new(:server => @server, :handler => handler)) @server.register('/' + handler.to_s + 's', klass.new(:server => @server, :handler => handler)) end end + + if @protocols.include?(:xmlrpc) and ! @xmlrpc_handlers.empty? + setup_xmlrpc_handlers + end + end + + # Use our existing code to provide the xmlrpc backward compatibility. + def setup_xmlrpc_handlers + @server.register('/RPC2', Puppet::Network::HTTPServer::Mongrel.new(@xmlrpc_handlers)) end def class_for_protocol(protocol) diff --git a/lib/puppet/network/http/mongrel/rest.rb b/lib/puppet/network/http/mongrel/rest.rb index 2a3d4f143..a471a62bf 100644 --- a/lib/puppet/network/http/mongrel/rest.rb +++ b/lib/puppet/network/http/mongrel/rest.rb @@ -2,12 +2,12 @@ require 'puppet/network/http/handler' class Puppet::Network::HTTP::MongrelREST < Mongrel::HttpHandler - include Puppet::Network::HTTP::Handler + include Puppet::Network::HTTP::Handler - def initialize(args={}) - super() - initialize_for_puppet(args) - end + def initialize(args={}) + super() + initialize_for_puppet(args) + end private diff --git a/lib/puppet/network/http/webrick.rb b/lib/puppet/network/http/webrick.rb index 3a37e2071..eacf81ec2 100644 --- a/lib/puppet/network/http/webrick.rb +++ b/lib/puppet/network/http/webrick.rb @@ -1,8 +1,12 @@ require 'webrick' require 'webrick/https' require 'puppet/network/http/webrick/rest' +require 'puppet/network/xmlrpc/webrick_servlet' require 'thread' +require 'puppet/ssl/certificate' +require 'puppet/ssl/certificate_revocation_list' + class Puppet::Network::HTTP::WEBrick def initialize(args = {}) @listening = false @@ -22,7 +26,14 @@ class Puppet::Network::HTTP::WEBrick @protocols = args[:protocols] @handlers = args[:handlers] - @server = WEBrick::HTTPServer.new(:BindAddress => args[:address], :Port => args[:port]) + @xmlrpc_handlers = args[:xmlrpc_handlers] + + arguments = {:BindAddress => args[:address], :Port => args[:port]} + arguments.merge!(setup_logger) + arguments.merge!(setup_ssl) + + @server = WEBrick::HTTPServer.new(arguments) + setup_handlers @mutex.synchronize do @@ -48,15 +59,86 @@ class Puppet::Network::HTTP::WEBrick end end + # Configure our http log file. + def setup_logger + # Make sure the settings are all ready for us. + Puppet.settings.use(:main, :ssl, Puppet[:name]) + + if Puppet[:name] == "puppetmasterd" + file = Puppet[:masterhttplog] + else + file = Puppet[:httplog] + end + + # open the log manually to prevent file descriptor leak + file_io = ::File.open(file, "a+") + file_io.sync + file_io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) + + args = [file_io] + args << WEBrick::Log::DEBUG if Puppet::Util::Log.level == :debug + + logger = WEBrick::Log.new(*args) + return :Logger => logger, :AccessLog => [ + [logger, WEBrick::AccessLog::COMMON_LOG_FORMAT ], + [logger, WEBrick::AccessLog::REFERER_LOG_FORMAT ] + ] + end + + # Add all of the ssl cert information. + def setup_ssl + results = {} + + host = Puppet::SSL::Host.new + + host.generate unless host.certificate + + raise Puppet::Error, "Could not retrieve certificate for %s and not running on a valid certificate authority" % host.name unless host.certificate + + results[:SSLPrivateKey] = host.key.content + results[:SSLCertificate] = host.certificate.content + results[:SSLStartImmediately] = true + results[:SSLEnable] = true + + unless Puppet::SSL::Certificate.find("ca") + raise Puppet::Error, "Could not find CA certificate" + end + + results[:SSLCACertificateFile] = Puppet[:localcacert] + results[:SSLVerifyClient] = OpenSSL::SSL::VERIFY_PEER + + results[:SSLCertificateStore] = host.ssl_store + + results + end + private def setup_handlers + # Set up the new-style protocols. @protocols.each do |protocol| + next if protocol == :xmlrpc klass = self.class.class_for_protocol(protocol) @handlers.each do |handler| @server.mount('/' + handler.to_s, klass, handler) @server.mount('/' + handler.to_s + 's', klass, handler) end end + + # And then set up xmlrpc, if configured. + if @protocols.include?(:xmlrpc) and ! @xmlrpc_handlers.empty? + @server.mount("/RPC2", xmlrpc_servlet) + end + end + + # Create our xmlrpc servlet, which provides backward compatibility. + def xmlrpc_servlet + handlers = @xmlrpc_handlers.collect { |handler| + unless hclass = Puppet::Network::Handler.handler(handler) + raise "Invalid xmlrpc handler %s" % handler + end + hclass.new({}) + } + Puppet::Network::XMLRPC::WEBrickServlet.new handlers end end diff --git a/lib/puppet/network/http_pool.rb b/lib/puppet/network/http_pool.rb index 1227f78dc..78a35cc15 100644 --- a/lib/puppet/network/http_pool.rb +++ b/lib/puppet/network/http_pool.rb @@ -1,11 +1,13 @@ -require 'puppet/sslcertificates/support' +require 'puppet/ssl/host' require 'net/https' +require 'puppet/util/cacher' -module Puppet::Network -end +module Puppet::Network; end # Manage Net::HTTP instances for keep-alive. module Puppet::Network::HttpPool + extend Puppet::Util::Cacher + # 2008/03/23 # LAK:WARNING: Enabling this has a high propability of # causing corrupt files and who knows what else. See #1010. @@ -15,18 +17,18 @@ module Puppet::Network::HttpPool HTTP_KEEP_ALIVE end - # This handles reading in the key and such-like. - extend Puppet::SSLCertificates::Support - @http_cache = {} + # Create an ssl host instance for getting certificate + # information. + def self.ssl_host + attr_cache(:ssl_host) { Puppet::SSL::Host.new } + end # Clear our http cache, closing all connections. def self.clear_http_instances - @http_cache.each do |name, connection| + http_cache.each do |name, connection| connection.finish if connection.started? end - @http_cache.clear - @cert = nil - @key = nil + Puppet::Util::Cacher.invalidate end # Make sure we set the driver up when we read the cert in. @@ -44,17 +46,13 @@ module Puppet::Network::HttpPool # Use cert information from a Puppet client to set up the http object. def self.cert_setup(http) # Just no-op if we don't have certs. - return false unless (defined?(@cert) and @cert) or self.read_cert + return false unless FileTest.exist?(Puppet[:hostcert]) # ssl_host.certificate - store = OpenSSL::X509::Store.new - store.add_file Puppet[:localcacert] - store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT - - http.cert_store = store + http.cert_store = ssl_host.ssl_store http.ca_file = Puppet[:localcacert] - http.cert = self.cert + http.cert = ssl_host.certificate.content http.verify_mode = OpenSSL::SSL::VERIFY_PEER - http.key = self.key + http.key = ssl_host.key.content end # Retrieve a cached http instance of caching is enabled, else return @@ -66,11 +64,11 @@ module Puppet::Network::HttpPool # Return our cached instance if we've got a cache, as long as we're not # resetting the instance. if keep_alive? - return @http_cache[key] if ! reset and @http_cache[key] + return http_cache[key] if ! reset and http_cache[key] # Clean up old connections if we have them. - if http = @http_cache[key] - @http_cache.delete(key) + if http = http_cache[key] + http_cache.delete(key) http.finish if http.started? end end @@ -100,8 +98,15 @@ module Puppet::Network::HttpPool cert_setup(http) - @http_cache[key] = http if keep_alive? + http_cache[key] = http if keep_alive? return http end + + private + + def self.http_cache + # Default to an empty hash. + attr_cache(:http) { Hash.new } + end end diff --git a/lib/puppet/network/http_server/mongrel.rb b/lib/puppet/network/http_server/mongrel.rb index 6b2325d29..e9421c781 100644 --- a/lib/puppet/network/http_server/mongrel.rb +++ b/lib/puppet/network/http_server/mongrel.rb @@ -64,11 +64,11 @@ module Puppet::Network # behaviour and we have to subclass Mongrel::HttpHandler so our handler # works for Mongrel. @xmlrpc_server = Puppet::Network::XMLRPCServer.new - handlers.each do |name, args| + handlers.each do |name| unless handler = Puppet::Network::Handler.handler(name) raise ArgumentError, "Invalid handler %s" % name end - @xmlrpc_server.add_handler(handler.interface, handler.new(args)) + @xmlrpc_server.add_handler(handler.interface, handler.new({})) end end diff --git a/lib/puppet/network/server.rb b/lib/puppet/network/server.rb index cab14519b..de32db02f 100644 --- a/lib/puppet/network/server.rb +++ b/lib/puppet/network/server.rb @@ -1,59 +1,153 @@ require 'puppet/network/http' +require 'puppet/util/pidlock' class Puppet::Network::Server - attr_reader :server_type, :protocols, :address, :port + attr_reader :server_type, :protocols, :address, :port + + # Put the daemon into the background. + def daemonize + if pid = fork() + Process.detach(pid) + exit(0) + end + + # Get rid of console logging + Puppet::Util::Log.close(:console) + + Process.setsid + Dir.chdir("/") + begin + $stdin.reopen "/dev/null" + $stdout.reopen "/dev/null", "a" + $stderr.reopen $stdout + Puppet::Util::Log.reopen + rescue => detail + File.open("/tmp/daemonout", "w") { |f| + f.puts "Could not start %s: %s" % [Puppet[:name], detail] + } + raise "Could not start %s: %s" % [Puppet[:name], detail] + end + end + + # Create a pidfile for our daemon, so we can be stopped and others + # don't try to start. + def create_pidfile + Puppet::Util.sync(Puppet[:name]).synchronize(Sync::EX) do + unless Puppet::Util::Pidlock.new(pidfile).lock + raise "Could not create PID file: %s" % [pidfile] + end + end + end + + # Remove the pid file for our daemon. + def remove_pidfile + Puppet::Util.sync(Puppet[:name]).synchronize(Sync::EX) do + locker = Puppet::Util::Pidlock.new(pidfile) + if locker.locked? + locker.unlock or Puppet.err "Could not remove PID file %s" % [pidfile] + end + end + end + + # Provide the path to our pidfile. + def pidfile + Puppet[:pidfile] + end def initialize(args = {}) @server_type = Puppet[:servertype] or raise "No servertype configuration found." # e.g., WEBrick, Mongrel, etc. http_server_class || raise(ArgumentError, "Could not determine HTTP Server class for server type [#{@server_type}]") - @address = args[:address] || Puppet[:bindaddress] || - raise(ArgumentError, "Must specify :address or configure Puppet :bindaddress.") - @port = args[:port] || Puppet[:masterport] || - raise(ArgumentError, "Must specify :port or configure Puppet :masterport") - @protocols = [ :rest ] + + @address = args[:address] || Puppet[:bindaddress] || raise(ArgumentError, "Must specify :address or configure Puppet :bindaddress.") + @port = args[:port] || Puppet[:masterport] || raise(ArgumentError, "Must specify :port or configure Puppet :masterport") + + @protocols = [ :rest, :xmlrpc ] @listening = false @routes = {} + @xmlrpc_routes = {} self.register(args[:handlers]) if args[:handlers] + self.register_xmlrpc(args[:xmlrpc_handlers]) if args[:xmlrpc_handlers] + + # Make sure we have all of the directories we need to function. + Puppet.settings.use(:main, :ssl, Puppet[:name]) end + # Register handlers for REST networking, based on the Indirector. def register(*indirections) - raise ArgumentError, "Indirection names are required." if indirections.empty? - indirections.flatten.each { |i| @routes[i.to_sym] = true } + raise ArgumentError, "Indirection names are required." if indirections.empty? + indirections.flatten.each do |name| + Puppet::Indirector::Indirection.model(name) || raise(ArgumentError, "Cannot locate indirection '#{name}'.") + @routes[name.to_sym] = true + end end + # Unregister Indirector handlers. def unregister(*indirections) - raise "Cannot unregister indirections while server is listening." if listening? - indirections = @routes.keys if indirections.empty? + raise "Cannot unregister indirections while server is listening." if listening? + indirections = @routes.keys if indirections.empty? + + indirections.flatten.each do |i| + raise(ArgumentError, "Indirection [%s] is unknown." % i) unless @routes[i.to_sym] + end + + indirections.flatten.each do |i| + @routes.delete(i.to_sym) + end + end + + # Register xmlrpc handlers for backward compatibility. + def register_xmlrpc(*namespaces) + raise ArgumentError, "XMLRPC namespaces are required." if namespaces.empty? + namespaces.flatten.each do |name| + Puppet::Network::Handler.handler(name) || raise(ArgumentError, "Cannot locate XMLRPC handler for namespace '#{name}'.") + @xmlrpc_routes[name.to_sym] = true + end + end + + # Unregister xmlrpc handlers. + def unregister_xmlrpc(*namespaces) + raise "Cannot unregister xmlrpc handlers while server is listening." if listening? + namespaces = @xmlrpc_routes.keys if namespaces.empty? - indirections.flatten.each do |i| - raise(ArgumentError, "Indirection [%s] is unknown." % i) unless @routes[i.to_sym] - end + namespaces.flatten.each do |i| + raise(ArgumentError, "XMLRPC handler '%s' is unknown." % i) unless @xmlrpc_routes[i.to_sym] + end - indirections.flatten.each do |i| - @routes.delete(i.to_sym) - end + namespaces.flatten.each do |i| + @xmlrpc_routes.delete(i.to_sym) + end end def listening? - @listening + @listening end def listen - raise "Cannot listen -- already listening." if listening? - @listening = true - http_server.listen(:address => address, :port => port, :handlers => @routes.keys, :protocols => protocols) + raise "Cannot listen -- already listening." if listening? + @listening = true + http_server.listen(:address => address, :port => port, :handlers => @routes.keys, :xmlrpc_handlers => @xmlrpc_routes.keys, :protocols => protocols) end def unlisten - raise "Cannot unlisten -- not currently listening." unless listening? - http_server.unlisten - @listening = false + raise "Cannot unlisten -- not currently listening." unless listening? + http_server.unlisten + @listening = false end def http_server_class http_server_class_by_type(@server_type) end + def start + create_pidfile + listen + end + + def stop + unlisten + remove_pidfile + end + private def http_server diff --git a/lib/puppet/network/xmlrpc/client.rb b/lib/puppet/network/xmlrpc/client.rb index e0fb5a0ab..dfd4a95a7 100644 --- a/lib/puppet/network/xmlrpc/client.rb +++ b/lib/puppet/network/xmlrpc/client.rb @@ -51,7 +51,8 @@ module Puppet::Network end ["certificate verify failed", "hostname was not match", "hostname not match"].each do |str| if detail.message.include?(str) - Puppet.warning "Certificate validation failed; considering using the certname configuration option" + Puppet.warning "Certificate validation failed; consider using the certname configuration option" + break end end raise XMLRPCClientError, diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb index c0628ecdc..252ab961e 100644 --- a/lib/puppet/node.rb +++ b/lib/puppet/node.rb @@ -161,5 +161,7 @@ class Puppet::Node params.each do |name, value| @parameters[name] = value unless @parameters.include?(name) end + + @parameters["environment"] ||= self.environment if self.environment end end diff --git a/lib/puppet/node/catalog.rb b/lib/puppet/node/catalog.rb index ecda472be..c5e32a032 100644 --- a/lib/puppet/node/catalog.rb +++ b/lib/puppet/node/catalog.rb @@ -61,7 +61,7 @@ class Puppet::Node::Catalog < Puppet::PGraph def add_resource(*resources) resources.each do |resource| unless resource.respond_to?(:ref) - raise ArgumentError, "Can only add objects that respond to :ref" + raise ArgumentError, "Can only add objects that respond to :ref, not instances of %s" % resource.class end fail_unless_unique(resource) @@ -309,7 +309,7 @@ class Puppet::Node::Catalog < Puppet::PGraph # And filebuckets if bucket = Puppet::Type.type(:filebucket).mkdefaultbucket - add_resource(bucket) + add_resource(bucket) unless resource(bucket.ref) end end @@ -337,7 +337,7 @@ class Puppet::Node::Catalog < Puppet::PGraph # Lastly, add in any autorequires @relationship_graph.vertices.each do |vertex| - vertex.autorequire.each do |edge| + vertex.autorequire(self).each do |edge| unless @relationship_graph.edge?(edge.source, edge.target) # don't let automatic relationships conflict with manual ones. unless @relationship_graph.edge?(edge.target, edge.source) vertex.debug "Autorequiring %s" % [edge.source] diff --git a/lib/puppet/resource_reference.rb b/lib/puppet/resource_reference.rb index 44b518816..12b9f54a9 100644 --- a/lib/puppet/resource_reference.rb +++ b/lib/puppet/resource_reference.rb @@ -22,15 +22,8 @@ class Puppet::ResourceReference # Find our resource. def resolve - if catalog - return catalog.resource(to_s) - end - # If it's builtin, then just ask for it directly from the type. - if t = builtin_type - t[@title] - else # Else, look for a component with the full reference as the name. - Puppet::Type::Component[to_s] - end + return catalog.resource(to_s) if catalog + return nil end # If the title has square brackets, treat it like a reference and diff --git a/lib/puppet/ssl.rb b/lib/puppet/ssl.rb new file mode 100644 index 000000000..68c65ca80 --- /dev/null +++ b/lib/puppet/ssl.rb @@ -0,0 +1,6 @@ +# Just to make the constants work out. +require 'puppet' +require 'openssl' + +module Puppet::SSL # :nodoc: +end diff --git a/lib/puppet/ssl/base.rb b/lib/puppet/ssl/base.rb new file mode 100644 index 000000000..80bfcae84 --- /dev/null +++ b/lib/puppet/ssl/base.rb @@ -0,0 +1,51 @@ +require 'puppet/ssl' + +# The base class for wrapping SSL instances. +class Puppet::SSL::Base + def self.wraps(klass) + @wrapped_class = klass + end + + def self.wrapped_class + raise(Puppet::DevError, "%s has not declared what class it wraps" % self) unless defined?(@wrapped_class) + @wrapped_class + end + + attr_accessor :name, :content + + # Is this file for the CA? + def ca? + name == Puppet::SSL::Host.ca_name + end + + def generate + raise Puppet::DevError, "%s did not override 'generate'" % self.class + end + + def initialize(name) + @name = name + end + + # Read content from disk appropriately. + def read(path) + @content = wrapped_class.new(File.read(path)) + end + + # Convert our thing to pem. + def to_s + return "" unless content + content.to_pem + end + + # Provide the full text of the thing we're dealing with. + def to_text + return "" unless content + content.to_text + end + + private + + def wrapped_class + self.class.wrapped_class + end +end diff --git a/lib/puppet/ssl/certificate.rb b/lib/puppet/ssl/certificate.rb new file mode 100644 index 000000000..16af85d06 --- /dev/null +++ b/lib/puppet/ssl/certificate.rb @@ -0,0 +1,19 @@ +require 'puppet/ssl/base' + +# Manage certificates themselves. This class has no +# 'generate' method because the CA is responsible +# for turning CSRs into certificates; we can only +# retrieve them from the CA (or not, as is often +# the case). +class Puppet::SSL::Certificate < Puppet::SSL::Base + # This is defined from the base class + wraps OpenSSL::X509::Certificate + + extend Puppet::Indirector + indirects :certificate, :terminus_class => :file + + def expiration + return nil unless content + return content.not_after + end +end diff --git a/lib/puppet/ssl/certificate_authority.rb b/lib/puppet/ssl/certificate_authority.rb new file mode 100644 index 000000000..6947af11c --- /dev/null +++ b/lib/puppet/ssl/certificate_authority.rb @@ -0,0 +1,285 @@ +require 'puppet/ssl/host' +require 'puppet/ssl/certificate_request' +require 'puppet/util/cacher' + +# The class that knows how to sign certificates. It creates +# a 'special' SSL::Host whose name is 'ca', thus indicating +# that, well, it's the CA. There's some magic in the +# indirector/ssl_file terminus base class that does that +# for us. +# This class mostly just signs certs for us, but +# it can also be seen as a general interface into all of the +# SSL stuff. +class Puppet::SSL::CertificateAuthority + require 'puppet/ssl/certificate_factory' + require 'puppet/ssl/inventory' + require 'puppet/ssl/certificate_revocation_list' + + require 'puppet/ssl/certificate_authority/interface' + + extend Puppet::Util::Cacher + + def self.ca? + return false unless Puppet[:ca] + return false unless Puppet[:name] == "puppetmasterd" + return true + end + + # If this process can function as a CA, then return a singleton + # instance. + def self.instance + return nil unless ca? + + attr_cache(:instance) { new } + end + + attr_reader :name, :host + + # Create and run an applicator. I wanted to build an interface where you could do + # something like 'ca.apply(:generate).to(:all) but I don't think it's really possible. + def apply(method, options) + unless options[:to] + raise ArgumentError, "You must specify the hosts to apply to; valid values are an array or the symbol :all" + end + applier = Interface.new(method, options[:to]) + + applier.apply(self) + end + + # If autosign is configured, then autosign all CSRs that match our configuration. + def autosign + return unless auto = autosign? + + store = nil + if auto != true + store = autosign_store(auto) + end + + Puppet::SSL::CertificateRequest.search("*").each do |csr| + sign(csr.name) if auto == true or store.allowed?(csr.name, "127.1.1.1") + end + end + + # Do we autosign? This returns true, false, or a filename. + def autosign? + auto = Puppet[:autosign] + return false if ['false', false].include?(auto) + return true if ['true', true].include?(auto) + + raise ArgumentError, "The autosign configuration '%s' must be a fully qualified file" % auto unless auto =~ /^\// + if FileTest.exist?(auto) + return auto + else + return false + end + end + + # Create an AuthStore for autosigning. + def autosign_store(file) + auth = Puppet::Network::AuthStore.new + File.readlines(file).each do |line| + next if line =~ /^\s*#/ + next if line =~ /^\s*$/ + auth.allow(line.chomp) + end + + auth + end + + # Retrieve (or create, if necessary) the certificate revocation list. + def crl + unless defined?(@crl) + unless @crl = Puppet::SSL::CertificateRevocationList.find("ca") + @crl = Puppet::SSL::CertificateRevocationList.new("ca") + @crl.generate(host.certificate.content, host.key.content) + @crl.save + end + end + @crl + end + + # Delegate this to our Host class. + def destroy(name) + Puppet::SSL::Host.destroy(name) + end + + # Generate a new certificate. + def generate(name) + raise ArgumentError, "A Certificate already exists for %s" % name if Puppet::SSL::Certificate.find(name) + host = Puppet::SSL::Host.new(name) + + host.generate_certificate_request + + sign(name) + end + + # Generate our CA certificate. + def generate_ca_certificate + generate_password unless password? + + host.generate_key unless host.key + + # Create a new cert request. We do this + # specially, because we don't want to actually + # save the request anywhere. + request = Puppet::SSL::CertificateRequest.new(host.name) + request.generate(host.key) + + # Create a self-signed certificate. + @certificate = sign(host.name, :ca, request) + + # And make sure we initialize our CRL. + crl() + end + + def initialize + Puppet.settings.use :main, :ssl, :ca + + @name = Puppet[:certname] + + @host = Puppet::SSL::Host.new(Puppet::SSL::Host.ca_name) + + setup() + end + + # Retrieve (or create, if necessary) our inventory manager. + def inventory + unless defined?(@inventory) + @inventory = Puppet::SSL::Inventory.new + end + @inventory + end + + # Generate a new password for the CA. + def generate_password + pass = "" + 20.times { pass += (rand(74) + 48).chr } + + begin + Puppet.settings.write(:capass) { |f| f.print pass } + rescue Errno::EACCES => detail + raise Puppet::Error, "Could not write CA password: %s" % detail.to_s + end + + @password = pass + + return pass + end + + # List all signed certificates. + def list + Puppet::SSL::Certificate.search("*").collect { |c| c.name } + end + + # Read the next serial from the serial file, and increment the + # file so this one is considered used. + def next_serial + serial = nil + + # This is slightly odd. If the file doesn't exist, our readwritelock creates + # it, but with a mode we can't actually read in some cases. So, use + # a default before the lock. + unless FileTest.exist?(Puppet[:serial]) + serial = 0x0 + end + + Puppet.settings.readwritelock(:serial) { |f| + if FileTest.exist?(Puppet[:serial]) + serial ||= File.read(Puppet.settings[:serial]).chomp.hex + end + + # We store the next valid serial, not the one we just used. + f << "%04X" % (serial + 1) + } + + return serial + end + + # Does the password file exist? + def password? + FileTest.exist? Puppet[:capass] + end + + # Print a given host's certificate as text. + def print(name) + if cert = Puppet::SSL::Certificate.find(name) + return cert.to_text + else + return nil + end + end + + # Revoke a given certificate. + def revoke(name) + raise ArgumentError, "Cannot revoke certificates when the CRL is disabled" unless crl + + if cert = Puppet::SSL::Certificate.find(name) + serial = cert.content.serial + elsif ! serial = inventory.serial(name) + raise ArgumentError, "Could not find a serial number for %s" % name + end + crl.revoke(serial, host.key.content) + end + + # This initializes our CA so it actually works. This should be a private + # method, except that you can't any-instance stub private methods, which is + # *awesome*. This method only really exists to provide a stub-point during + # testing. + def setup + generate_ca_certificate unless @host.certificate + end + + # Sign a given certificate request. + def sign(hostname, cert_type = :server, self_signing_csr = nil) + # This is a self-signed certificate + if self_signing_csr + csr = self_signing_csr + issuer = csr.content + else + unless csr = Puppet::SSL::CertificateRequest.find(hostname) + raise ArgumentError, "Could not find certificate request for %s" % hostname + end + issuer = host.certificate.content + end + + cert = Puppet::SSL::Certificate.new(hostname) + cert.content = Puppet::SSL::CertificateFactory.new(cert_type, csr.content, issuer, next_serial).result + cert.content.sign(host.key.content, OpenSSL::Digest::SHA1.new) + + Puppet.notice "Signed certificate request for %s" % hostname + + # Add the cert to the inventory before we save it, since + # otherwise we could end up with it being duplicated, if + # this is the first time we build the inventory file. + inventory.add(cert) + + # Save the now-signed cert. This should get routed correctly depending + # on the certificate type. + cert.save + + # And remove the CSR if this wasn't self signed. + Puppet::SSL::CertificateRequest.destroy(csr.name) unless self_signing_csr + + return cert + end + + # Verify a given host's certificate. + def verify(name) + unless cert = Puppet::SSL::Certificate.find(name) + raise ArgumentError, "Could not find a certificate for %s" % name + end + store = OpenSSL::X509::Store.new + store.add_file Puppet[:cacert] + store.add_crl crl.content if self.crl + store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT + + unless store.verify(cert.content) + raise "Certificate for %s failed verification" % name + end + end + + # List the waiting certificate requests. + def waiting? + Puppet::SSL::CertificateRequest.search("*").collect { |r| r.name } + end +end diff --git a/lib/puppet/ssl/certificate_authority/interface.rb b/lib/puppet/ssl/certificate_authority/interface.rb new file mode 100644 index 000000000..b355e21f0 --- /dev/null +++ b/lib/puppet/ssl/certificate_authority/interface.rb @@ -0,0 +1,110 @@ +# This class is basically a hidden class that knows how to act +# on the CA. It's only used by the 'puppetca' executable, and its +# job is to provide a CLI-like interface to the CA class. +class Puppet::SSL::CertificateAuthority::Interface + INTERFACE_METHODS = [:destroy, :list, :revoke, :generate, :sign, :print, :verify] + + class InterfaceError < ArgumentError; end + + attr_reader :method, :subjects + + # Actually perform the work. + def apply(ca) + unless subjects or method == :list + raise ArgumentError, "You must provide hosts or :all when using %s" % method + end + + begin + if respond_to?(method) + return send(method, ca) + end + + (subjects == :all ? ca.list : subjects).each do |host| + ca.send(method, host) + end + rescue InterfaceError + raise + rescue => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not call %s: %s" % [method, detail] + end + end + + def generate(ca) + raise InterfaceError, "It makes no sense to generate all hosts; you must specify a list" if subjects == :all + + subjects.each do |host| + ca.generate(host) + end + end + + def initialize(method, subjects) + self.method = method + self.subjects = subjects + end + + # List the hosts. + def list(ca) + unless subjects + puts ca.waiting?.join("\n") + return nil + end + + signed = ca.list + requests = ca.waiting? + + if subjects == :all + hosts = [signed, requests].flatten + else + hosts = subjects + end + + hosts.uniq.sort.each do |host| + if signed.include?(host) + puts "+ " + host + else + puts host + end + end + end + + # Set the method to apply. + def method=(method) + raise ArgumentError, "Invalid method %s to apply" % method unless INTERFACE_METHODS.include?(method) + @method = method + end + + # Print certificate information. + def print(ca) + (subjects == :all ? ca.list : subjects).each do |host| + if value = ca.print(host) + puts value + else + Puppet.err "Could not find certificate for %s" % host + end + end + end + + # Sign a given certificate. + def sign(ca) + list = subjects == :all ? ca.waiting? : subjects + raise InterfaceError, "No waiting certificate requests to sign" if list.empty? + list.each do |host| + ca.sign(host) + end + end + + # Set the list of hosts we're operating on. Also supports keywords. + def subjects=(value) + unless value == :all or value.is_a?(Array) + raise ArgumentError, "Subjects must be an array or :all; not %s" % value + end + + if value.is_a?(Array) and value.empty? + value = nil + end + + @subjects = value + end +end + diff --git a/lib/puppet/ssl/certificate_factory.rb b/lib/puppet/ssl/certificate_factory.rb new file mode 100644 index 000000000..41155fd41 --- /dev/null +++ b/lib/puppet/ssl/certificate_factory.rb @@ -0,0 +1,145 @@ +require 'puppet/ssl' + +# The tedious class that does all the manipulations to the +# certificate to correctly sign it. Yay. +class Puppet::SSL::CertificateFactory + # How we convert from various units to the required seconds. + UNITMAP = { + "y" => 365 * 24 * 60 * 60, + "d" => 24 * 60 * 60, + "h" => 60 * 60, + "s" => 1 + } + + attr_reader :name, :cert_type, :csr, :issuer, :serial + + def initialize(cert_type, csr, issuer, serial) + @cert_type, @csr, @issuer, @serial = cert_type, csr, issuer, serial + + @name = @csr.subject + end + + # Actually generate our certificate. + def result + @cert = OpenSSL::X509::Certificate.new + + @cert.version = 2 # X509v3 + @cert.subject = @csr.subject + @cert.issuer = @issuer.subject + @cert.public_key = @csr.public_key + @cert.serial = @serial + + build_extensions() + + set_ttl + + @cert + end + + private + + # This is pretty ugly, but I'm not really sure it's even possible to do + # it any other way. + def build_extensions + @ef = OpenSSL::X509::ExtensionFactory.new + + @ef.subject_certificate = @cert + + if @issuer.is_a?(OpenSSL::X509::Request) # It's a self-signed cert + @ef.issuer_certificate = @cert + else + @ef.issuer_certificate = @issuer + end + + @subject_alt_name = [] + @key_usage = nil + @ext_key_usage = nil + @extensions = [] + + method = "add_#{@cert_type.to_s}_extensions" + + begin + send(method) + rescue NoMethodError + raise ArgumentError, "%s is an invalid certificate type" % @cert_type + end + + @extensions << @ef.create_extension("nsComment", "Puppet Ruby/OpenSSL Generated Certificate") + @extensions << @ef.create_extension("basicConstraints", @basic_constraint, true) + @extensions << @ef.create_extension("subjectKeyIdentifier", "hash") + @extensions << @ef.create_extension("keyUsage", @key_usage.join(",")) if @key_usage + @extensions << @ef.create_extension("extendedKeyUsage", @ext_key_usage.join(",")) if @ext_key_usage + @extensions << @ef.create_extension("subjectAltName", @subject_alt_name.join(",")) if ! @subject_alt_name.empty? + + @cert.extensions = @extensions + + # for some reason this _must_ be the last extension added + @extensions << @ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") if @cert_type == :ca + end + + # TTL for new certificates in seconds. If config param :ca_ttl is set, + # use that, otherwise use :ca_days for backwards compatibility + def ttl + ttl = Puppet.settings[:ca_ttl] + + return ttl unless ttl.is_a?(String) + + raise ArgumentError, "Invalid ca_ttl #{ttl}" unless ttl =~ /^(\d+)(y|d|h|s)$/ + + return $1.to_i * UNITMAP[$2] + end + + def set_ttl + # Make the certificate valid as of yesterday, because + # so many people's clocks are out of sync. + from = Time.now - (60*60*24) + @cert.not_before = from + @cert.not_after = from + ttl + end + + # Woot! We're a CA. + def add_ca_extensions + @basic_constraint = "CA:TRUE" + @key_usage = %w{cRLSign keyCertSign} + end + + # We're a terminal CA, probably not self-signed. + def add_terminalsubca_extensions + @basic_constraint = "CA:TRUE,pathlen:0" + @key_usage = %w{cRLSign keyCertSign} + end + + # We're a normal server. + def add_server_extensions + @basic_constraint = "CA:FALSE" + dnsnames = Puppet[:certdnsnames] + name = @name.to_s.sub(%r{/CN=},'') + if dnsnames != "" + dnsnames.split(':').each { |d| @subject_alt_name << 'DNS:' + d } + @subject_alt_name << 'DNS:' + name # Add the fqdn as an alias + elsif name == Facter.value(:fqdn) # we're a CA server, and thus probably the server + @subject_alt_name << 'DNS:' + "puppet" # Add 'puppet' as an alias + @subject_alt_name << 'DNS:' + name # Add the fqdn as an alias + @subject_alt_name << 'DNS:' + name.sub(/^[^.]+./, "puppet.") # add puppet.domain as an alias + end + @key_usage = %w{digitalSignature keyEncipherment} + @ext_key_usage = %w{serverAuth clientAuth emailProtection} + end + + # Um, no idea. + def add_ocsp_extensions + @basic_constraint = "CA:FALSE" + @key_usage = %w{nonRepudiation digitalSignature} + @ext_key_usage = %w{serverAuth OCSPSigning} + end + + # Normal client. + def add_client_extensions + @basic_constraint = "CA:FALSE" + @key_usage = %w{nonRepudiation digitalSignature keyEncipherment} + @ext_key_usage = %w{clientAuth emailProtection} + + @extensions << @ef.create_extension("nsCertType", "client,email") + end +end + diff --git a/lib/puppet/ssl/certificate_request.rb b/lib/puppet/ssl/certificate_request.rb new file mode 100644 index 000000000..34cae5a3e --- /dev/null +++ b/lib/puppet/ssl/certificate_request.rb @@ -0,0 +1,36 @@ +require 'puppet/ssl/base' + +# Manage certificate requests. +class Puppet::SSL::CertificateRequest < Puppet::SSL::Base + wraps OpenSSL::X509::Request + + extend Puppet::Indirector + indirects :certificate_request, :terminus_class => :file + + # How to create a certificate request with our system defaults. + def generate(key) + Puppet.info "Creating a new SSL certificate request for %s" % name + + # Support either an actual SSL key, or a Puppet key. + key = key.content if key.is_a?(Puppet::SSL::Key) + + csr = OpenSSL::X509::Request.new + csr.version = 0 + csr.subject = OpenSSL::X509::Name.new([["CN", name]]) + csr.public_key = key.public_key + csr.sign(key, OpenSSL::Digest::MD5.new) + + raise Puppet::Error, "CSR sign verification failed; you need to clean the certificate request for %s on the server" % name unless csr.verify(key.public_key) + + @content = csr + end + + def save + super() + + # Try to autosign the CSR. + if ca = Puppet::SSL::CertificateAuthority.instance + ca.autosign + end + end +end diff --git a/lib/puppet/ssl/certificate_revocation_list.rb b/lib/puppet/ssl/certificate_revocation_list.rb new file mode 100644 index 000000000..3029c14a4 --- /dev/null +++ b/lib/puppet/ssl/certificate_revocation_list.rb @@ -0,0 +1,72 @@ +require 'puppet/ssl/base' +require 'puppet/indirector' + +# Manage the CRL. +class Puppet::SSL::CertificateRevocationList < Puppet::SSL::Base + wraps OpenSSL::X509::CRL + + extend Puppet::Indirector + indirects :certificate_revocation_list, :terminus_class => :file + + # Knows how to create a CRL with our system defaults. + def generate(cert, cakey) + Puppet.info "Creating a new certificate revocation list" + @content = wrapped_class.new + @content.issuer = cert.subject + @content.version = 1 + + # Init the CRL number. + crlNum = OpenSSL::ASN1::Integer(0) + @content.extensions = [OpenSSL::X509::Extension.new("crlNumber", crlNum)] + + # Set last/next update + @content.last_update = Time.now + # Keep CRL valid for 5 years + @content.next_update = Time.now + 5 * 365*24*60*60 + + @content.sign(cakey, OpenSSL::Digest::SHA1.new) + + @content + end + + # The name doesn't actually matter; there's only one CRL. + # We just need the name so our Indirector stuff all works more easily. + def initialize(fakename) + raise Puppet::Error, "Cannot manage the CRL when :cacrl is set to false" if [false, "false"].include?(Puppet[:cacrl]) + + @name = "crl" + end + + # Revoke the certificate with serial number SERIAL issued by this + # CA, then write the CRL back to disk. The REASON must be one of the + # OpenSSL::OCSP::REVOKED_* reasons + def revoke(serial, cakey, reason = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE) + Puppet.notice "Revoked certificate with serial %s" % serial + time = Time.now + + # Add our revocation to the CRL. + revoked = OpenSSL::X509::Revoked.new + revoked.serial = serial + revoked.time = time + enum = OpenSSL::ASN1::Enumerated(reason) + ext = OpenSSL::X509::Extension.new("CRLReason", enum) + revoked.add_extension(ext) + @content.add_revoked(revoked) + + # Increment the crlNumber + e = @content.extensions.find { |e| e.oid == 'crlNumber' } + ext = @content.extensions.reject { |e| e.oid == 'crlNumber' } + crlNum = OpenSSL::ASN1::Integer(e ? e.value.to_i + 1 : 0) + ext << OpenSSL::X509::Extension.new("crlNumber", crlNum) + @content.extensions = ext + + # Set last/next update + @content.last_update = time + # Keep CRL valid for 5 years + @content.next_update = time + 5 * 365*24*60*60 + + @content.sign(cakey, OpenSSL::Digest::SHA1.new) + + save + end +end diff --git a/lib/puppet/ssl/host.rb b/lib/puppet/ssl/host.rb new file mode 100644 index 000000000..e366bfbdd --- /dev/null +++ b/lib/puppet/ssl/host.rb @@ -0,0 +1,185 @@ +require 'puppet/ssl' +require 'puppet/ssl/key' +require 'puppet/ssl/certificate' +require 'puppet/ssl/certificate_request' +require 'puppet/ssl/certificate_revocation_list' +require 'puppet/util/constant_inflector' + +# The class that manages all aspects of our SSL certificates -- +# private keys, public keys, requests, etc. +class Puppet::SSL::Host + # Yay, ruby's strange constant lookups. + Key = Puppet::SSL::Key + Certificate = Puppet::SSL::Certificate + CertificateRequest = Puppet::SSL::CertificateRequest + CertificateRevocationList = Puppet::SSL::CertificateRevocationList + + extend Puppet::Util::ConstantInflector + + attr_reader :name + attr_accessor :ca + + attr_writer :key, :certificate, :certificate_request + + CA_NAME = "ca" + + # This is the constant that people will use to mark that a given host is + # a certificate authority. + def self.ca_name + CA_NAME + end + + class << self + attr_reader :ca_location + end + + # Configure how our various classes interact with their various terminuses. + def self.configure_indirection(terminus, cache = nil) + Certificate.terminus_class = terminus + CertificateRequest.terminus_class = terminus + CertificateRevocationList.terminus_class = terminus + + if cache + # This is weird; we don't actually cache our keys or CRL, we + # use what would otherwise be the cache as our normal + # terminus. + Key.terminus_class = cache + else + Key.terminus_class = terminus + end + + if cache + Certificate.cache_class = cache + CertificateRequest.cache_class = cache + CertificateRevocationList.cache_class = cache + end + end + + # Specify how we expect to interact with our certificate authority. + def self.ca_location=(mode) + raise ArgumentError, "CA Mode can only be :local, :remote, or :none" unless [:local, :remote, :none].include?(mode) + + @ca_mode = mode + + case @ca_mode + when :local: + # Our ca is local, so we use it as the ultimate source of information + # And we cache files locally. + configure_indirection :ca, :file + when :remote: + configure_indirection :rest, :file + when :none: + # We have no CA, so we just look in the local file store. + configure_indirection :file + end + end + + # Remove all traces of a given host + def self.destroy(name) + [Key, Certificate, CertificateRequest].inject(false) do |result, klass| + if klass.destroy(name) + result = true + end + result + end + end + + # Search for more than one host, optionally only specifying + # an interest in hosts with a given file type. + # This just allows our non-indirected class to have one of + # indirection methods. + def self.search(options = {}) + classes = [Key, CertificateRequest, Certificate] + if klass = options[:for] + classlist = [klass].flatten + else + classlist = [Key, CertificateRequest, Certificate] + end + + # Collect the results from each class, flatten them, collect all of the names, make the name list unique, + # then create a Host instance for each one. + classlist.collect { |klass| klass.search }.flatten.collect { |r| r.name }.uniq.collect do |name| + new(name) + end + end + + # Is this a ca host, meaning that all of its files go in the CA location? + def ca? + ca + end + + def key + return nil unless @key ||= Key.find(name) + @key + end + + # This is the private key; we can create it from scratch + # with no inputs. + def generate_key + @key = Key.new(name) + @key.generate + @key.save + true + end + + def certificate_request + return nil unless @certificate_request ||= CertificateRequest.find(name) + @certificate_request + end + + # Our certificate request requires the key but that's all. + def generate_certificate_request + generate_key unless key + @certificate_request = CertificateRequest.new(name) + @certificate_request.generate(key.content) + @certificate_request.save + return true + end + + def certificate + return nil unless @certificate ||= Certificate.find(name) + @certificate + end + + # Generate all necessary parts of our ssl host. + def generate + generate_key unless key + generate_certificate_request unless certificate_request + + # If we can get a CA instance, then we're a valid CA, and we + # should use it to sign our request; else, just try to read + # the cert. + if ! certificate() and ca = Puppet::SSL::CertificateAuthority.instance + ca.sign(self.name) + end + end + + def initialize(name = nil) + @name = name || Puppet[:certname] + @key = @certificate = @certificate_request = nil + @ca = (name == self.class.ca_name) + end + + # Extract the public key from the private key. + def public_key + key.content.public_key + end + + # Create/return a store that uses our SSL info to validate + # connections. + def ssl_store(purpose = OpenSSL::X509::PURPOSE_ANY) + store = OpenSSL::X509::Store.new + store.purpose = purpose + + store.add_file(Puppet[:localcacert]) + + # If there's a CRL, add it to our store. + if crl = Puppet::SSL::CertificateRevocationList.find("ca") + store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK + store.add_crl(crl.content) + end + return store + end +end + +require 'puppet/ssl/certificate_authority' diff --git a/lib/puppet/ssl/inventory.rb b/lib/puppet/ssl/inventory.rb new file mode 100644 index 000000000..38cbf46e9 --- /dev/null +++ b/lib/puppet/ssl/inventory.rb @@ -0,0 +1,52 @@ +require 'puppet/ssl' +require 'puppet/ssl/certificate' + +# Keep track of all of our known certificates. +class Puppet::SSL::Inventory + attr_reader :path + + # Add a certificate to our inventory. + def add(cert) + cert = cert.content if cert.is_a?(Puppet::SSL::Certificate) + + # Create our file, if one does not already exist. + rebuild unless FileTest.exist?(@path) + + Puppet.settings.write(:cert_inventory, "a") do |f| + f.print format(cert) + end + end + + # Format our certificate for output. + def format(cert) + iso = '%Y-%m-%dT%H:%M:%S%Z' + return "0x%04x %s %s %s\n" % [cert.serial, cert.not_before.strftime(iso), cert.not_after.strftime(iso), cert.subject] + end + + def initialize + @path = Puppet[:cert_inventory] + end + + # Rebuild the inventory from scratch. This should happen if + # the file is entirely missing or if it's somehow corrupted. + def rebuild + Puppet.notice "Rebuilding inventory file" + + Puppet.settings.write(:cert_inventory) do |f| + f.print "# Inventory of signed certificates\n# SERIAL NOT_BEFORE NOT_AFTER SUBJECT\n" + end + + Puppet::SSL::Certificate.search("*").each { |cert| add(cert) } + end + + # Find the serial number for a given certificate. + def serial(name) + return nil unless FileTest.exist?(@path) + + File.readlines(@path).each do |line| + next unless line =~ /^(\S+).+\/CN=#{name}$/ + + return Integer($1) + end + end +end diff --git a/lib/puppet/ssl/key.rb b/lib/puppet/ssl/key.rb new file mode 100644 index 000000000..a1d436090 --- /dev/null +++ b/lib/puppet/ssl/key.rb @@ -0,0 +1,50 @@ +require 'puppet/ssl/base' +require 'puppet/indirector' + +# Manage private and public keys as a pair. +class Puppet::SSL::Key < Puppet::SSL::Base + wraps OpenSSL::PKey::RSA + + extend Puppet::Indirector + indirects :key, :terminus_class => :file + + attr_accessor :password_file + + # Knows how to create keys with our system defaults. + def generate + Puppet.info "Creating a new SSL key for %s" % name + @content = OpenSSL::PKey::RSA.new(Puppet[:keylength].to_i) + end + + def initialize(name) + super + + if ca? + @password_file = Puppet[:capass] + else + @password_file = Puppet[:passfile] + end + end + + def password + return nil unless password_file and FileTest.exist?(password_file) + + ::File.read(password_file) + end + + # Optionally support specifying a password file. + def read(path) + return super unless password_file + + #@content = wrapped_class.new(::File.read(path), password) + @content = wrapped_class.new(::File.read(path), password) + end + + def to_s + if pass = password + @content.export(OpenSSL::Cipher::DES.new(:EDE3, :CBC), pass) + else + return super + end + end +end diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index 14b2037f4..d4bc42100 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -153,7 +153,7 @@ class Transaction # contained resources might never get cleaned up. def cleanup if defined? @generated - relationship_graph.remove_resource(*@generated) + catalog.remove_resource(*@generated) end end @@ -503,7 +503,7 @@ class Transaction # Now add any dynamically generated resources generate() - + # This will throw an error if there are cycles in the graph. @sorted_resources = relationship_graph.topsort end diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index f8949ec90..7deb25fff 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -229,14 +229,14 @@ class Type end # If the name and title differ, set up an alias - if self.name != self.title - if obj = self.class[self.name] + if self.name != self.title and self.catalog + if obj = catalog.resource(self.class.name, self.name) if self.class.isomorphic? raise Puppet::Error, "%s already exists with name %s" % [obj.title, self.name] end else - self.class.alias(self.name, self) + catalog.alias(self, self.name) end end diff --git a/lib/puppet/type/component.rb b/lib/puppet/type/component.rb index 356205089..1d1bc9ee8 100644 --- a/lib/puppet/type/component.rb +++ b/lib/puppet/type/component.rb @@ -88,8 +88,8 @@ Puppet::Type.newtype(:component) do @reference = Puppet::ResourceReference.new(:component, @title) - unless self.class[@reference.to_s] - self.class.alias(@reference.to_s, self) + if catalog and ! catalog.resource[@reference.to_s] + catalog.alias(self, @reference.to_s) end end diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb index 3518e8bb3..ef010efda 100644 --- a/lib/puppet/type/file.rb +++ b/lib/puppet/type/file.rb @@ -78,9 +78,8 @@ module Puppet munge do |value| # I don't really know how this is happening. - if value.is_a?(Array) - value = value.shift - end + value = value.shift if value.is_a?(Array) + case value when false, "false", :false: false @@ -92,7 +91,7 @@ module Puppet # We can't depend on looking this up right now, # we have to do it after all of the objects # have been instantiated. - if bucketobj = Puppet::Type.type(:filebucket)[value] + if resource.catalog and bucketobj = resource.catalog.resource(:filebucket, value) @resource.bucket = bucketobj.bucket bucketobj.title else @@ -304,30 +303,23 @@ module Puppet # We have to do some extra finishing, to retrieve our bucket if # there is one. def finish - # Let's cache these values, since there should really only be - # a couple of these buckets - @@filebuckets ||= {} - # Look up our bucket, if there is one if bucket = self.bucket case bucket when String: - if obj = @@filebuckets[bucket] - # This sets the @value on :backup, too - self.bucket = obj + if catalog and obj = catalog.resource(:filebucket, bucket) + self.bucket = obj.bucket elsif bucket == "puppet" obj = Puppet::Network::Client.client(:Dipper).new( :Path => Puppet[:clientbucketdir] ) self.bucket = obj - @@filebuckets[bucket] = obj - elsif obj = Puppet::Type.type(:filebucket).bucket(bucket) - @@filebuckets[bucket] = obj - self.bucket = obj else - self.fail "Could not find filebucket %s" % bucket + self.fail "Could not find filebucket '%s'" % bucket end when Puppet::Network::Client.client(:Dipper): # things are hunky-dorey + when Puppet::Type::Filebucket # things are hunky-dorey + self.bucket = bucket.bucket else self.fail "Invalid bucket type %s" % bucket.class end @@ -432,8 +424,7 @@ module Puppet end when "link": return true else - self.notice "Cannot backup files of type %s" % - File.stat(file).ftype + self.notice "Cannot backup files of type %s" % File.stat(file).ftype return false end end @@ -805,7 +796,6 @@ module Puppet # a wrapper method to make sure the file exists before doing anything def retrieve unless stat = self.stat(true) - self.debug "File does not exist" # If the file doesn't exist but we have a source, then call # retrieve on that property diff --git a/lib/puppet/type/filebucket.rb b/lib/puppet/type/filebucket.rb index b268610e9..872bded4e 100755 --- a/lib/puppet/type/filebucket.rb +++ b/lib/puppet/type/filebucket.rb @@ -54,21 +54,9 @@ module Puppet defaultto { Puppet[:clientbucketdir] } end - # get the actual filebucket object - def self.bucket(name) - if object = self[name] - return object.bucket - else - return nil - end - end - # Create a default filebucket. def self.mkdefaultbucket - unless default = self["puppet"] - return self.create(:name => "puppet", :path => Puppet[:clientbucketdir]) - end - return nil + self.create(:name => "puppet", :path => Puppet[:clientbucketdir]) end def self.instances diff --git a/lib/puppet/type/schedule.rb b/lib/puppet/type/schedule.rb index b8479e01d..ecbf7d49a 100755 --- a/lib/puppet/type/schedule.rb +++ b/lib/puppet/type/schedule.rb @@ -241,7 +241,9 @@ module Puppet :daily => :day, :monthly => :month, :weekly => proc do |prev, now| - prev.strftime("%U") != now.strftime("%U") + # Run the resource if the previous day was after this weekday (e.g., prev is wed, current is tue) + # or if it's been more than a week since we ran + prev.wday > now.wday or (now - prev) > (24 * 3600 * 7) end } @@ -314,8 +316,6 @@ module Puppet end def self.mkdefaultschedules - return [] if self["puppet"] - result = [] Puppet.debug "Creating default schedules" result << self.create( @@ -326,12 +326,10 @@ module Puppet # And then one for every period @parameters.find { |p| p.name == :period }.values.each { |value| - unless self[value.to_s] - result << self.create( - :name => value.to_s, - :period => value - ) - end + result << self.create( + :name => value.to_s, + :period => value + ) } result diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb index 71507d172..0b668395d 100755 --- a/lib/puppet/type/user.rb +++ b/lib/puppet/type/user.rb @@ -302,19 +302,7 @@ module Puppet group end } - groups.each { |group| - case group - when Integer: - if obj = Puppet.type(:group).find { |gobj| - gobj.should(:gid) == group - } - autos << obj - - end - else - autos << group - end - } + autos = groups.reject { |g| g.is_a?(Integer) } end if obj = @parameters[:groups] and groups = obj.should diff --git a/lib/puppet/type/yumrepo.rb b/lib/puppet/type/yumrepo.rb index acb3b9b83..8800dd71d 100644 --- a/lib/puppet/type/yumrepo.rb +++ b/lib/puppet/type/yumrepo.rb @@ -67,8 +67,9 @@ module Puppet class << self attr_accessor :filetype # The writer is only used for testing, there should be no need - # to change yumconf in any other context + # to change yumconf or inifile in any other context attr_accessor :yumconf + attr_writer :inifile end self.filetype = Puppet::Util::FileType.filetype(:flat) @@ -181,11 +182,11 @@ module Puppet inifile.store end + # This is only used during testing. def self.clear @inifile = nil @yumconf = "/etc/yum.conf" @defaultrepodir = nil - super end # Return the Puppet::Util::IniConfig::Section for this yumrepo resource diff --git a/lib/puppet/util/cacher.rb b/lib/puppet/util/cacher.rb new file mode 100644 index 000000000..7b352c72a --- /dev/null +++ b/lib/puppet/util/cacher.rb @@ -0,0 +1,69 @@ +module Puppet::Util::Cacher + # Cause all cached values to be considered invalid. + def self.invalidate + @timestamp = Time.now + end + + def self.valid?(timestamp) + unless defined?(@timestamp) and @timestamp + @timestamp = Time.now + return true + end + return timestamp >= @timestamp + end + + def self.extended(other) + other.extend(InstanceMethods) + end + + def self.included(other) + other.extend(ClassMethods) + other.send(:include, InstanceMethods) + end + + module ClassMethods + private + + def cached_attr(name, &block) + define_method(name) do + attr_cache(name, &block) + end + end + end + + module InstanceMethods + private + + def attr_cache(name, &block) + unless defined?(@cacher_caches) and @cacher_caches + @cacher_caches = Cache.new + end + + @cacher_caches.value(name, &block) + end + end + + class Cache + attr_accessor :caches, :timestamp + + def initialize + @caches = {} + end + + def value(name) + raise ArgumentError, "You must provide a block when using the cache" unless block_given? + + if timestamp.nil? or ! Puppet::Util::Cacher.valid?(timestamp) + caches.clear + self.timestamp = Time.now + end + + # Use 'include?' here rather than testing for truth, so we + # can cache false values. + unless caches.include?(name) + caches[name] = yield + end + caches[name] + end + end +end diff --git a/lib/puppet/util/filetype.rb b/lib/puppet/util/filetype.rb index 8dcb67286..ae078fff4 100755 --- a/lib/puppet/util/filetype.rb +++ b/lib/puppet/util/filetype.rb @@ -74,8 +74,10 @@ class Puppet::Util::FileType # Pick or create a filebucket to use. def bucket - filebucket = Puppet::Type.type(:filebucket) - (filebucket["puppet"] || filebucket.mkdefaultbucket).bucket + unless defined?(@bucket) + @bucket = Puppet::Type.type(:filebucket).mkdefaultbucket.bucket + end + @bucket end def initialize(path) @@ -104,6 +106,9 @@ class Puppet::Util::FileType # Overwrite the file. def write(text) backup() + + raise("Cannot create file %s in absent directory" % @path) unless FileTest.exist?(File.dirname(@path)) + require "tempfile" tf = Tempfile.new("puppet") tf.print text; tf.flush diff --git a/lib/puppet/util/posix.rb b/lib/puppet/util/posix.rb index cc0340ef7..8228734ef 100755 --- a/lib/puppet/util/posix.rb +++ b/lib/puppet/util/posix.rb @@ -63,45 +63,6 @@ module Puppet::Util::POSIX return nil end - # Look in memory for an already-managed type and use its info if available. - # Currently unused. - def get_provider_value(type, field, id) - unless typeklass = Puppet::Type.type(type) - raise ArgumentError, "Invalid type %s" % type - end - - id = id.to_s - - chkfield = idfield(type) - obj = typeklass.find { |obj| - if id =~ /^\d+$/ - obj.should(chkfield).to_s == id || - obj.provider.send(chkfield) == id - else - obj[:name] == id - end - } - - return nil unless obj - - if obj.provider - begin - val = obj.provider.send(field) - if val == :absent - return nil - else - return val - end - rescue => detail - if Puppet[:trace] - puts detail.backtrace - Puppet.err detail - return nil - end - end - end - end - # Determine what the field name is for users and groups. def idfield(space) case Puppet::Util.symbolize(space) diff --git a/lib/puppet/util/settings.rb b/lib/puppet/util/settings.rb index 0e6f91e48..eec86625d 100644 --- a/lib/puppet/util/settings.rb +++ b/lib/puppet/util/settings.rb @@ -39,6 +39,8 @@ class Puppet::Util::Settings end @values[:memory][param] = value @cache.clear + + clearused end return value @@ -65,7 +67,6 @@ class Puppet::Util::Settings config = trans.to_catalog config.store_state = false config.apply - config.clear rescue => detail if Puppet[:trace] puts detail.backtrace @@ -312,94 +313,6 @@ class Puppet::Util::Settings end end - # Parse the configuration file. As of May 2007, this is a backward-compatibility method and - # will be deprecated soon. - def old_parse(file) - text = nil - - if file.is_a? Puppet::Util::LoadedFile - @file = file - else - @file = Puppet::Util::LoadedFile.new(file) - end - - # Don't create a timer for the old style parsing. - # settimer() - - begin - text = File.read(@file.file) - rescue Errno::ENOENT - raise Puppet::Error, "No such file %s" % file - rescue Errno::EACCES - raise Puppet::Error, "Permission denied to file %s" % file - end - - @values = Hash.new { |names, name| - names[name] = {} - } - - # Get rid of the values set by the file, keeping cli values. - self.clear(true) - - section = "puppet" - metas = %w{owner group mode} - values = Hash.new { |hash, key| hash[key] = {} } - text.split(/\n/).each { |line| - case line - when /^\[(\w+)\]$/: section = $1 # Section names - when /^\s*#/: next # Skip comments - when /^\s*$/: next # Skip blanks - when /^\s*(\w+)\s*=\s*(.+)$/: # settings - var = $1.intern - if var == :mode - value = $2 - else - value = munge_value($2) - end - - # Only warn if we don't know what this config var is. This - # prevents exceptions later on. - unless @config.include?(var) or metas.include?(var.to_s) - Puppet.warning "Discarded unknown configuration parameter %s" % var.inspect - next # Skip this line. - end - - # Mmm, "special" attributes - if metas.include?(var.to_s) - unless values.include?(section) - values[section] = {} - end - values[section][var.to_s] = value - - # If the parameter is valid, then set it. - if section == Puppet[:name] and @config.include?(var) - #@config[var].value = value - @values[:main][var] = value - end - next - end - - # Don't override set parameters, since the file is parsed - # after cli arguments are handled. - unless @config.include?(var) and @config[var].setbycli - Puppet.debug "%s: Setting %s to '%s'" % [section, var, value] - @values[:main][var] = value - end - @config[var].section = symbolize(section) - - metas.each { |meta| - if values[section][meta] - if @config[var].respond_to?(meta + "=") - @config[var].send(meta + "=", values[section][meta]) - end - end - } - else - raise Puppet::Error, "Could not match line %s" % line - end - } - end - # Create a new element. The value is passed in because it's used to determine # what kind of element we're creating, but the value itself might be either # a default or a value, so we can't actually assign it. @@ -663,7 +576,7 @@ Generated on #{Time.now}. catalog = bucket.to_catalog rescue => detail puts detail.backtrace if Puppet[:trace] - Puppet.err "Could not create resources for managing Puppet's files and directories: %s" % detail + Puppet.err "Could not create resources for managing Puppet's files and directories in sections %s: %s" % [sections.inspect, detail] # We need some way to get rid of any resources created during the catalog creation # but not cleaned up. @@ -674,11 +587,14 @@ Generated on #{Time.now}. catalog.host_config = false catalog.apply do |transaction| if failures = transaction.any_failed? - raise "Could not configure for running; got %s failure(s)" % failures + # LAK:NOTE We should do something like this for some cases, + # since it can otherwise be hard to know what failed. + #transaction.report.logs.find_all { |log| log.level == :err }.each do |log| + # puts log.message + #end + raise "Could not configure myself; got %s failure(s)" % failures end end - ensure - catalog.clear end sections.each { |s| @used << s } @@ -776,20 +692,26 @@ Generated on #{Time.now}. end sync.synchronize(Sync::EX) do - File.open(file, "r+", 0600) do |rf| + File.open(file, ::File::CREAT|::File::RDWR, 0600) do |rf| rf.lock_exclusive do if File.exist?(tmpfile) raise Puppet::Error, ".tmp file already exists for %s; Aborting locked write. Check the .tmp file and delete if appropriate" % [file] end - writesub(default, tmpfile, *args, &bloc) + # If there's a failure, remove our tmpfile + begin + writesub(default, tmpfile, *args, &bloc) + rescue + File.unlink(tmpfile) if FileTest.exist?(tmpfile) + raise + end begin File.rename(tmpfile, file) rescue => detail - Puppet.err "Could not rename %s to %s: %s" % - [file, tmpfile, detail] + Puppet.err "Could not rename %s to %s: %s" % [file, tmpfile, detail] + File.unlink(tmpfile) if FileTest.exist?(tmpfile) end end end @@ -1163,7 +1085,6 @@ Generated on #{Time.now}. return nil unless path.is_a?(String) return nil if path =~ /^\/dev/ - return nil if Puppet::Type.type(:file)[path] # skip files that are in our global resource list. objects = [] diff --git a/man/man8/puppet.8 b/man/man8/puppet.8 index 990b7cee9..8fc24aab2 100644 --- a/man/man8/puppet.8 +++ b/man/man8/puppet.8 @@ -67,8 +67,7 @@ puppet \-l /tmp/script.log script.pp .SH AUTHOR Luke Kanies - - +.\" Generated by docutils manpage writer on 2008-01-20 10:28. .SH COPYRIGHT Copyright (c) 2005 Reductive Labs, LLC Licensed under the GNU Public License diff --git a/spec/integration/bin/puppetmasterd.rb b/spec/integration/bin/puppetmasterd.rb new file mode 100755 index 000000000..447344472 --- /dev/null +++ b/spec/integration/bin/puppetmasterd.rb @@ -0,0 +1,109 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe "puppetmasterd" do + before do + # Get a safe temporary file + file = Tempfile.new("puppetmaster_integration_testing") + @dir = file.path + file.delete + + Dir.mkdir(@dir) + + Puppet.settings[:confdir] = @dir + Puppet.settings[:vardir] = @dir + Puppet[:certdnsnames] = "localhost" + + @@port = 12345 + end + + after { + stop + + Puppet::SSL::Host.ca_location = :none + + system("rm -rf %s" % @dir) + Puppet.settings.clear + } + + def arguments + rundir = File.join(Puppet[:vardir], "run") + @pidfile = File.join(rundir, "testing.pid") + args = "" + args += " --confdir %s" % Puppet[:confdir] + args += " --rundir %s" % rundir + args += " --pidfile %s" % @pidfile + args += " --vardir %s" % Puppet[:vardir] + args += " --certdnsnames %s" % Puppet[:certdnsnames] + args += " --masterport %s" % @@port + args += " --user %s" % Puppet::Util::SUIDManager.uid + args += " --group %s" % Puppet::Util::SUIDManager.gid + args += " --autosign true" + end + + def start(addl_args = "") + Puppet.settings.mkdir(:manifestdir) + Puppet.settings.write(:manifest) do |f| + f.puts { "notify { testing: }" } + end + + args = arguments + addl_args + + output = %x{puppetmasterd #{args}}.chomp + end + + def stop + if @pidfile and FileTest.exist?(@pidfile) + pid = File.read(@pidfile).chomp.to_i + Process.kill(:TERM, pid) + end + end + + it "should create a PID file" do + start + + FileTest.exist?(@pidfile).should be_true + end + + it "should be serving status information over REST" + + it "should be serving status information over xmlrpc" do + start + + sleep 0.5 + + client = Puppet::Network::Client.status.new(:Server => "localhost", :Port => @@port) + + FileUtils.mkdir_p(File.dirname(Puppet[:autosign])) + File.open(Puppet[:autosign], "w") { |f| + f.puts Puppet[:certname] + } + + client.cert + retval = client.status + + retval.should == 1 + end + + it "should exit with return code 0 after parsing if --parseonly is set and there are no errors" do + start(" --parseonly > /dev/null") + sleep(1) + + ps = Facter["ps"].value || "ps -ef" + pid = nil + %x{#{ps}}.chomp.split(/\n/).each { |line| + next if line =~ /^puppet/ # skip normal master procs + if line =~ /puppetmasterd.+--manifest/ + ary = line.split(" ") + pid = ary[1].to_i + end + } + + $?.should == 0 + + pid.should be_nil + end + + it "should exit with return code 1 after parsing if --parseonly is set and there are errors" +end diff --git a/spec/integration/defaults.rb b/spec/integration/defaults.rb index b14a141fb..b6f6bf109 100755 --- a/spec/integration/defaults.rb +++ b/spec/integration/defaults.rb @@ -5,13 +5,20 @@ require File.dirname(__FILE__) + '/../spec_helper' require 'puppet/defaults' describe "Puppet defaults" do - describe "when setting the :factpath" do - after { Puppet.settings.clear } - + after { Puppet.settings.clear } + describe "when configuring the :crl" do it "should add the :factpath to Facter's search paths" do Facter.expects(:search).with("/my/fact/path") Puppet.settings[:factpath] = "/my/fact/path" end end + + describe "when setting the :factpath" do + + it "should warn if :cacrl is set to false" do + Puppet.expects(:warning) + Puppet.settings[:cacrl] = 'false' + end + end end diff --git a/spec/integration/file_serving/configuration.rb b/spec/integration/file_serving/configuration.rb index 6975594a8..cb5a23d3b 100755 --- a/spec/integration/file_serving/configuration.rb +++ b/spec/integration/file_serving/configuration.rb @@ -10,7 +10,7 @@ require 'puppet/file_serving/configuration' describe Puppet::FileServing::Configuration, " when finding files with Puppet::FileServing::Mount" do before do # Just in case it already exists. - Puppet::FileServing::Configuration.clear_cache + Puppet::Util::Cacher.invalidate @mount = Puppet::FileServing::Mount.new("mymount") FileTest.stubs(:exists?).with("/my/path").returns(true) @@ -38,6 +38,6 @@ describe Puppet::FileServing::Configuration, " when finding files with Puppet::F end after do - Puppet::FileServing::Configuration.clear_cache + Puppet::Util::Cacher.invalidate end end diff --git a/spec/integration/indirector/rest.rb b/spec/integration/indirector/rest.rb index cafcce713..1a9671265 100644..100755 --- a/spec/integration/indirector/rest.rb +++ b/spec/integration/indirector/rest.rb @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/network/server' require 'puppet/indirector' @@ -5,456 +7,476 @@ require 'puppet/indirector/rest' # a fake class that will be indirected via REST class Puppet::TestIndirectedFoo - extend Puppet::Indirector - indirects :test_indirected_foo, :terminus_setting => :test_indirected_foo_terminus - - attr_reader :value - - def initialize(value = 0) - @value = value - end - - def self.from_yaml(yaml) - YAML.load(yaml) - end - - def name - "bob" - end + extend Puppet::Indirector + indirects :test_indirected_foo, :terminus_setting => :test_indirected_foo_terminus + + attr_reader :value + + def initialize(value = 0) + @value = value + end + + def self.from_yaml(yaml) + YAML.load(yaml) + end + + def name + "bob" + end end # empty Terminus class -- this would normally have to be in a directory findable by the autoloader, but we short-circuit that below class Puppet::TestIndirectedFoo::Rest < Puppet::Indirector::REST end +# This way the retrieval of the class by name works. +Puppet::Indirector::Terminus.register_terminus_class(Puppet::TestIndirectedFoo::Rest) describe Puppet::Indirector::REST do - describe "when using webrick" do - before :each do - Puppet[:servertype] = 'webrick' - @params = { :address => "127.0.0.1", :port => 34343, :handlers => [ :test_indirected_foo ] } - @server = Puppet::Network::Server.new(@params) - @server.listen + before do + Puppet[:masterport] = 34343 + Puppet[:server] = "localhost" + + # Get a safe temporary file + @tmpfile = Tempfile.new("webrick_integration_testing") + @dir = @tmpfile.path + "_dir" - # the autoloader was clearly not written test-first. We subvert the integration test to get around its bullshit. - Puppet::Indirector::Terminus.stubs(:terminus_class).returns(Puppet::TestIndirectedFoo::Rest) - Puppet::TestIndirectedFoo.indirection.stubs(:terminus_class).returns :rest + Puppet.settings[:confdir] = @dir + Puppet.settings[:vardir] = @dir + Puppet.settings[:http_enable_post_connection_check] = false - # Stub the connection information. - Puppet::TestIndirectedFoo.indirection.terminus(:rest).stubs(:rest_connection_details).returns(:host => "localhost", :port => 34343) + Puppet::SSL::Host.ca_location = :local + + Puppet::TestIndirectedFoo.terminus_class = :rest + Puppet::TestIndirectedFoo.indirection.terminus.stubs(:rest_connection_details).returns(:host => "127.0.0.1", :port => "34343") end - - describe "when finding a model instance over REST" do - describe "when a matching model instance can be found" do - before :each do - @model_instance = Puppet::TestIndirectedFoo.new(23) - @mock_model = stub('faked model', :find => @model_instance) - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) - end - - it "should not fail" do - Puppet::TestIndirectedFoo.find('bar') - lambda { Puppet::TestIndirectedFoo.find('bar') }.should_not raise_error - end - - it 'should return an instance of the model class' do - Puppet::TestIndirectedFoo.find('bar').class.should == Puppet::TestIndirectedFoo - end - - it 'should return the instance of the model class associated with the provided lookup key' do - Puppet::TestIndirectedFoo.find('bar').value.should == @model_instance.value - end - - it 'should set an expiration on model instance' do - Puppet::TestIndirectedFoo.find('bar').expiration.should_not be_nil - end - end - - describe "when no matching model instance can be found" do - before :each do - @mock_model = stub('faked model', :find => nil) - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) - end - - it "should return nil" do - Puppet::TestIndirectedFoo.find('bar').should be_nil - end - end - - describe "when an exception is encountered in looking up a model instance" do - before :each do - @mock_model = stub('faked model') - @mock_model.stubs(:find).raises(RuntimeError) - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) - end - - it "should raise an exception" do - lambda { Puppet::TestIndirectedFoo.find('bar') }.should raise_error(RuntimeError) - end - end + + after do + Puppet::Network::HttpPool.instance_variable_set("@ssl_host", nil) end - describe "when searching for model instances over REST" do - describe "when matching model instances can be found" do + describe "when using webrick" do before :each do - @model_instances = [ Puppet::TestIndirectedFoo.new(23), Puppet::TestIndirectedFoo.new(24) ] - @mock_model = stub('faked model', :search => @model_instances) - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) - end - - it "should not fail" do - lambda { Puppet::TestIndirectedFoo.search('bar') }.should_not raise_error - end - - it 'should return all matching results' do - Puppet::TestIndirectedFoo.search('bar').length.should == @model_instances.length - end - - it 'should return model instances' do - Puppet::TestIndirectedFoo.search('bar').each do |result| - result.class.should == Puppet::TestIndirectedFoo - end + Puppet::Util::Cacher.invalidate + + Puppet[:servertype] = 'webrick' + Puppet[:server] = '127.0.0.1' + Puppet[:certname] = '127.0.0.1' + + ca = Puppet::SSL::CertificateAuthority.new + ca.generate(Puppet[:certname]) unless Puppet::SSL::Certificate.find(Puppet[:certname]) + + @params = { :address => "127.0.0.1", :port => 34343, :handlers => [ :test_indirected_foo ], :xmlrpc_handlers => [ :status ] } + @server = Puppet::Network::Server.new(@params) + @server.listen end - - it 'should return the instance of the model class associated with the provided lookup key' do - Puppet::TestIndirectedFoo.search('bar').collect(&:value).should == @model_instances.collect(&:value) + + after do + @server.unlisten + @tmpfile.delete + Puppet.settings.clear + Puppet::Util::Cacher.invalidate end - - it 'should set a version timestamp on model instances' do - pending("Luke looking at why this version magic might not be working") do - Puppet::TestIndirectedFoo.search('bar').each do |result| - result.version.should_not be_nil + + describe "when finding a model instance over REST" do + describe "when a matching model instance can be found" do + before :each do + @model_instance = Puppet::TestIndirectedFoo.new(23) + @mock_model = stub('faked model', :find => @model_instance) + Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should not fail" do + Puppet::TestIndirectedFoo.find('bar') + lambda { Puppet::TestIndirectedFoo.find('bar') }.should_not raise_error + end + + it 'should return an instance of the model class' do + Puppet::TestIndirectedFoo.find('bar').class.should == Puppet::TestIndirectedFoo + end + + it 'should return the instance of the model class associated with the provided lookup key' do + Puppet::TestIndirectedFoo.find('bar').value.should == @model_instance.value + end + + it 'should set an expiration on model instance' do + Puppet::TestIndirectedFoo.find('bar').expiration.should_not be_nil + end + end + + describe "when no matching model instance can be found" do + before :each do + @mock_model = stub('faked model', :find => nil) + Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should return nil" do + Puppet::TestIndirectedFoo.find('bar').should be_nil + end + end + + describe "when an exception is encountered in looking up a model instance" do + before :each do + @mock_model = stub('faked model') + @mock_model.stubs(:find).raises(RuntimeError) + Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should raise an exception" do + lambda { Puppet::TestIndirectedFoo.find('bar') }.should raise_error(RuntimeError) + end end - end end - end + + describe "when searching for model instances over REST" do + describe "when matching model instances can be found" do + before :each do + @model_instances = [ Puppet::TestIndirectedFoo.new(23), Puppet::TestIndirectedFoo.new(24) ] + @mock_model = stub('faked model', :search => @model_instances) + Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should not fail" do + lambda { Puppet::TestIndirectedFoo.search('bar') }.should_not raise_error + end - describe "when no matching model instance can be found" do - before :each do - @mock_model = stub('faked model', :find => nil) - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) - end - - it "should return nil" do - Puppet::TestIndirectedFoo.find('bar').should be_nil - end - end + it 'should return all matching results' do + Puppet::TestIndirectedFoo.search('bar').length.should == @model_instances.length + end - describe "when an exception is encountered in looking up a model instance" do - before :each do - @mock_model = stub('faked model') - @mock_model.stubs(:find).raises(RuntimeError) - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) - end - - it "should raise an exception" do - lambda { Puppet::TestIndirectedFoo.find('bar') }.should raise_error(RuntimeError) + it 'should return model instances' do + Puppet::TestIndirectedFoo.search('bar').each do |result| + result.class.should == Puppet::TestIndirectedFoo + end + end + + it 'should return the instance of the model class associated with the provided lookup key' do + Puppet::TestIndirectedFoo.search('bar').collect(&:value).should == @model_instances.collect(&:value) + end + end + + describe "when no matching model instance can be found" do + before :each do + @mock_model = stub('faked model', :find => nil) + Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should return nil" do + Puppet::TestIndirectedFoo.find('bar').should be_nil + end + end + + describe "when an exception is encountered in looking up a model instance" do + before :each do + @mock_model = stub('faked model') + @mock_model.stubs(:find).raises(RuntimeError) + Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should raise an exception" do + lambda { Puppet::TestIndirectedFoo.find('bar') }.should raise_error(RuntimeError) + end + end end - end - end - describe "when destroying a model instance over REST" do - describe "when a matching model instance can be found" do - before :each do - @mock_model = stub('faked model', :destroy => true) - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) - end - - it "should not fail" do - lambda { Puppet::TestIndirectedFoo.destroy('bar') }.should_not raise_error - end - - it 'should return success' do - Puppet::TestIndirectedFoo.destroy('bar').should == true - end - end + describe "when destroying a model instance over REST" do + describe "when a matching model instance can be found" do + before :each do + @mock_model = stub('faked model', :destroy => true) + Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should not fail" do + lambda { Puppet::TestIndirectedFoo.destroy('bar') }.should_not raise_error + end - describe "when no matching model instance can be found" do - before :each do - @mock_model = stub('faked model', :destroy => false) - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) - end - - it "should return failure" do - Puppet::TestIndirectedFoo.destroy('bar').should == false + it 'should return success' do + Puppet::TestIndirectedFoo.destroy('bar').should == true + end + end + + describe "when no matching model instance can be found" do + before :each do + @mock_model = stub('faked model', :destroy => false) + Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should return failure" do + Puppet::TestIndirectedFoo.destroy('bar').should == false + end + end + + describe "when an exception is encountered in destroying a model instance" do + before :each do + @mock_model = stub('faked model') + @mock_model.stubs(:destroy).raises(RuntimeError) + Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should raise an exception" do + lambda { Puppet::TestIndirectedFoo.destroy('bar') }.should raise_error(RuntimeError) + end + end end - end + + describe "when saving a model instance over REST" do + before :each do + @instance = Puppet::TestIndirectedFoo.new(42) + @mock_model = stub('faked model', :from_yaml => @instance) + Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:save_object).returns(@instance) + end + + describe "when a successful save can be performed" do + before :each do + end + + it "should not fail" do + lambda { @instance.save }.should_not raise_error + end - describe "when an exception is encountered in destroying a model instance" do - before :each do - @mock_model = stub('faked model') - @mock_model.stubs(:destroy).raises(RuntimeError) - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) - end - - it "should raise an exception" do - lambda { Puppet::TestIndirectedFoo.destroy('bar') }.should raise_error(RuntimeError) + it 'should return an instance of the model class' do + @instance.save.class.should == Puppet::TestIndirectedFoo + end + + it 'should return a matching instance of the model class' do + @instance.save.value.should == @instance.value + end + end + + describe "when a save cannot be completed" do + before :each do + Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:save_object).returns(false) + end + + it "should return failure" do + @instance.save.should == false + end + end + + describe "when an exception is encountered in performing a save" do + before :each do + Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:save_object).raises(RuntimeError) + end + + it "should raise an exception" do + lambda { @instance.save }.should raise_error(RuntimeError) + end + end end - end end - describe "when saving a model instance over REST" do - before :each do - @instance = Puppet::TestIndirectedFoo.new(42) - @mock_model = stub('faked model', :from_yaml => @instance) - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:save_object).returns(@instance) - end - - describe "when a successful save can be performed" do - before :each do - end - - it "should not fail" do - lambda { @instance.save }.should_not raise_error - end - - it 'should return an instance of the model class' do - @instance.save.class.should == Puppet::TestIndirectedFoo - end - - it 'should return a matching instance of the model class' do - @instance.save.value.should == @instance.value - end - end - - describe "when a save cannot be completed" do - before :each do - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:save_object).returns(false) - end - - it "should return failure" do - @instance.save.should == false - end - end - - describe "when an exception is encountered in performing a save" do + describe "when using mongrel" do + confine "Mongrel is not available" => Puppet.features.mongrel? + before :each do - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:save_object).raises(RuntimeError) - end - - it "should raise an exception" do - lambda { @instance.save }.should raise_error(RuntimeError) - end - end - end + Puppet[:servertype] = 'mongrel' + @params = { :address => "127.0.0.1", :port => 34343, :handlers => [ :test_indirected_foo ] } - after :each do - @server.unlisten - end - end + # Make sure we never get a cert, since mongrel can't speak ssl + Puppet::SSL::Certificate.stubs(:find).returns nil - describe "when using mongrel" do - confine "Mongrel is not available" => Puppet.features.mongrel? - - before :each do - Puppet[:servertype] = 'mongrel' - @params = { :address => "127.0.0.1", :port => 34343, :handlers => [ :test_indirected_foo ] } - @server = Puppet::Network::Server.new(@params) - @server.listen + # We stub ssl to be off, since mongrel can't speak ssl + Net::HTTP.any_instance.stubs(:use_ssl?).returns false - # the autoloader was clearly not written test-first. We subvert the integration test to get around its bullshit. - Puppet::Indirector::Terminus.stubs(:terminus_class).returns(Puppet::TestIndirectedFoo::Rest) - Puppet::TestIndirectedFoo.indirection.stubs(:terminus_class).returns :rest - - # Stub the connection information. - Puppet::TestIndirectedFoo.indirection.terminus(:rest).stubs(:rest_connection_details).returns(:host => "localhost", :port => 34343) - end - - describe "when finding a model instance over REST" do - describe "when a matching model instance can be found" do - before :each do - @model_instance = Puppet::TestIndirectedFoo.new(23) - @mock_model = stub('faked model', :find => @model_instance) - Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) - end - - it "should not fail" do - lambda { Puppet::TestIndirectedFoo.find('bar') }.should_not raise_error + @server = Puppet::Network::Server.new(@params) + @server.listen end - - it 'should return an instance of the model class' do - Puppet::TestIndirectedFoo.find('bar').class.should == Puppet::TestIndirectedFoo - end - - it 'should return the instance of the model class associated with the provided lookup key' do - Puppet::TestIndirectedFoo.find('bar').value.should == @model_instance.value - end - - it 'should set an expiration on model instance' do - Puppet::TestIndirectedFoo.find('bar').expiration.should_not be_nil + + after :each do + @server.unlisten end - end - describe "when no matching model instance can be found" do - before :each do - @mock_model = stub('faked model', :find => nil) - Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) - end - - it "should return nil" do - Puppet::TestIndirectedFoo.find('bar').should be_nil - end - end + describe "when finding a model instance over REST" do + describe "when a matching model instance can be found" do + before :each do + @model_instance = Puppet::TestIndirectedFoo.new(23) + @mock_model = stub('faked model', :find => @model_instance) + Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should not fail" do + lambda { Puppet::TestIndirectedFoo.find('bar') }.should_not raise_error + end - describe "when an exception is encountered in looking up a model instance" do - before :each do - @mock_model = stub('faked model') - @mock_model.stubs(:find).raises(RuntimeError) - Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) - end - - it "should raise an exception" do - lambda { Puppet::TestIndirectedFoo.find('bar') }.should raise_error(RuntimeError) - end - end - end - - describe "when searching for model instances over REST" do - describe "when matching model instances can be found" do - before :each do - @model_instances = [ Puppet::TestIndirectedFoo.new(23), Puppet::TestIndirectedFoo.new(24) ] - @mock_model = stub('faked model', :search => @model_instances) - Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) - end - - it "should not fail" do - lambda { Puppet::TestIndirectedFoo.search('bar') }.should_not raise_error - end - - it 'should return all matching results' do - Puppet::TestIndirectedFoo.search('bar').length.should == @model_instances.length - end - - it 'should return model instances' do - Puppet::TestIndirectedFoo.search('bar').each do |result| - result.class.should == Puppet::TestIndirectedFoo - end - end - - it 'should return the instance of the model class associated with the provided lookup key' do - Puppet::TestIndirectedFoo.search('bar').collect(&:value).should == @model_instances.collect(&:value) - end - - it 'should set an expiration on model instances' do - Puppet::TestIndirectedFoo.search('bar').each do |result| - result.expiration.should_not be_nil - end - end - end + it 'should return an instance of the model class' do + Puppet::TestIndirectedFoo.find('bar').class.should == Puppet::TestIndirectedFoo + end - describe "when no matching model instance can be found" do - before :each do - @mock_model = stub('faked model', :find => nil) - Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) - end - - it "should return nil" do - Puppet::TestIndirectedFoo.find('bar').should be_nil - end - end + it 'should return the instance of the model class associated with the provided lookup key' do + Puppet::TestIndirectedFoo.find('bar').value.should == @model_instance.value + end - describe "when an exception is encountered in looking up a model instance" do - before :each do - @mock_model = stub('faked model') - @mock_model.stubs(:find).raises(RuntimeError) - Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) - end - - it "should raise an exception" do - lambda { Puppet::TestIndirectedFoo.find('bar') }.should raise_error(RuntimeError) + it 'should set an expiration on model instance' do + Puppet::TestIndirectedFoo.find('bar').expiration.should_not be_nil + end + end + + describe "when no matching model instance can be found" do + before :each do + @mock_model = stub('faked model', :find => nil) + Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should return nil" do + Puppet::TestIndirectedFoo.find('bar').should be_nil + end + end + + describe "when an exception is encountered in looking up a model instance" do + before :each do + @mock_model = stub('faked model') + @mock_model.stubs(:find).raises(RuntimeError) + Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should raise an exception" do + lambda { Puppet::TestIndirectedFoo.find('bar') }.should raise_error(RuntimeError) + end + end end - end - end - describe "when destroying a model instance over REST" do - describe "when a matching model instance can be found" do - before :each do - @mock_model = stub('faked model', :destroy => true) - Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) - end - - it "should not fail" do - lambda { Puppet::TestIndirectedFoo.destroy('bar') }.should_not raise_error - end - - it 'should return success' do - Puppet::TestIndirectedFoo.destroy('bar').should == true - end - end + describe "when searching for model instances over REST" do + describe "when matching model instances can be found" do + before :each do + @model_instances = [ Puppet::TestIndirectedFoo.new(23), Puppet::TestIndirectedFoo.new(24) ] + @mock_model = stub('faked model', :search => @model_instances) + Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should not fail" do + lambda { Puppet::TestIndirectedFoo.search('bar') }.should_not raise_error + end - describe "when no matching model instance can be found" do - before :each do - @mock_model = stub('faked model', :destroy => false) - Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) - end - - it "should return failure" do - Puppet::TestIndirectedFoo.destroy('bar').should == false - end - end + it 'should return all matching results' do + Puppet::TestIndirectedFoo.search('bar').length.should == @model_instances.length + end - describe "when an exception is encountered in destroying a model instance" do - before :each do - @mock_model = stub('faked model') - @mock_model.stubs(:destroy).raises(RuntimeError) - Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) - end - - it "should raise an exception" do - lambda { Puppet::TestIndirectedFoo.destroy('bar') }.should raise_error(RuntimeError) + it 'should return model instances' do + Puppet::TestIndirectedFoo.search('bar').each do |result| + result.class.should == Puppet::TestIndirectedFoo + end + end + + it 'should return the instance of the model class associated with the provided lookup key' do + Puppet::TestIndirectedFoo.search('bar').collect(&:value).should == @model_instances.collect(&:value) + end + + it 'should set an expiration on model instances' do + Puppet::TestIndirectedFoo.search('bar').each do |result| + result.expiration.should_not be_nil + end + end + end + + describe "when no matching model instance can be found" do + before :each do + @mock_model = stub('faked model', :find => nil) + Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should return nil" do + Puppet::TestIndirectedFoo.find('bar').should be_nil + end + end + + describe "when an exception is encountered in looking up a model instance" do + before :each do + @mock_model = stub('faked model') + @mock_model.stubs(:find).raises(RuntimeError) + Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should raise an exception" do + lambda { Puppet::TestIndirectedFoo.find('bar') }.should raise_error(RuntimeError) + end + end end - end - end - describe "when saving a model instance over REST" do - before :each do - @instance = Puppet::TestIndirectedFoo.new(42) - @mock_model = stub('faked model', :from_yaml => @instance) - Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) - Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:save_object).returns(@instance) - end - - describe "when a successful save can be performed" do - before :each do - end - - it "should not fail" do - lambda { @instance.save }.should_not raise_error - end - - it 'should return an instance of the model class' do - @instance.save.class.should == Puppet::TestIndirectedFoo - end - - it 'should return a matching instance of the model class' do - @instance.save.value.should == @instance.value - end - end - - describe "when a save cannot be completed" do - before :each do - Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:save_object).returns(false) - end - - it "should return failure" do - @instance.save.should == false - end - end - - describe "when an exception is encountered in performing a save" do - before :each do - Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:save_object).raises(RuntimeError) - end - - it "should raise an exception" do - lambda { @instance.save }.should raise_error(RuntimeError) + describe "when destroying a model instance over REST" do + describe "when a matching model instance can be found" do + before :each do + @mock_model = stub('faked model', :destroy => true) + Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should not fail" do + lambda { Puppet::TestIndirectedFoo.destroy('bar') }.should_not raise_error + end + + it 'should return success' do + Puppet::TestIndirectedFoo.destroy('bar').should == true + end + end + + describe "when no matching model instance can be found" do + before :each do + @mock_model = stub('faked model', :destroy => false) + Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should return failure" do + Puppet::TestIndirectedFoo.destroy('bar').should == false + end + end + + describe "when an exception is encountered in destroying a model instance" do + before :each do + @mock_model = stub('faked model') + @mock_model.stubs(:destroy).raises(RuntimeError) + Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) + end + + it "should raise an exception" do + lambda { Puppet::TestIndirectedFoo.destroy('bar') }.should raise_error(RuntimeError) + end + end end - end - end - after :each do - @server.unlisten + describe "when saving a model instance over REST" do + before :each do + @instance = Puppet::TestIndirectedFoo.new(42) + @mock_model = stub('faked model', :from_yaml => @instance) + Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) + Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:save_object).returns(@instance) + end + + describe "when a successful save can be performed" do + before :each do + end + + it "should not fail" do + lambda { @instance.save }.should_not raise_error + end + + it 'should return an instance of the model class' do + @instance.save.class.should == Puppet::TestIndirectedFoo + end + + it 'should return a matching instance of the model class' do + @instance.save.value.should == @instance.value + end + end + + describe "when a save cannot be completed" do + before :each do + Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:save_object).returns(false) + end + + it "should return failure" do + @instance.save.should == false + end + end + + describe "when an exception is encountered in performing a save" do + before :each do + Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:save_object).raises(RuntimeError) + end + + it "should raise an exception" do + lambda { @instance.save }.should raise_error(RuntimeError) + end + end + end end - end end diff --git a/spec/integration/network/server/mongrel.rb b/spec/integration/network/server/mongrel.rb index 65caf78c9..08fd49b17 100644..100755 --- a/spec/integration/network/server/mongrel.rb +++ b/spec/integration/network/server/mongrel.rb @@ -8,7 +8,7 @@ describe Puppet::Network::Server do before :each do Puppet[:servertype] = 'mongrel' - @params = { :address => "127.0.0.1", :port => 34346, :handlers => [ :node ] } + @params = { :address => "127.0.0.1", :port => 34346, :handlers => [ :node ], :xmlrpc_handlers => [ :status ] } @server = Puppet::Network::Server.new(@params) end @@ -43,4 +43,4 @@ describe Puppet::Network::Server do @server.unlisten if @server.listening? end end -end
\ No newline at end of file +end diff --git a/spec/integration/network/server/webrick.rb b/spec/integration/network/server/webrick.rb index 0ab8d89e4..0e66ee955 100644..100755 --- a/spec/integration/network/server/webrick.rb +++ b/spec/integration/network/server/webrick.rb @@ -1,46 +1,70 @@ +#!/usr/bin/env ruby + require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/network/server' +require 'puppet/ssl/certificate_authority' require 'socket' describe Puppet::Network::Server do - describe "when using webrick" do - before :each do - Puppet[:servertype] = 'webrick' - @params = { :address => "127.0.0.1", :port => 34343, :handlers => [ :node ] } - end - - describe "before listening" do - it "should not be reachable at the specified address and port" do - lambda { TCPSocket.new('127.0.0.1', 34343) }.should raise_error - end - end - - describe "when listening" do - it "should be reachable on the specified address and port" do - @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) - @server.listen - lambda { TCPSocket.new('127.0.0.1', 34343) }.should_not raise_error - end - - it "should not allow multiple servers to listen on the same address and port" do - @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) - @server.listen - @server2 = Puppet::Network::Server.new(@params.merge(:port => 34343)) - lambda { @server2.listen }.should raise_error - end - - after :each do - @server.unlisten if @server.listening? - end - end - - describe "after unlistening" do - it "should not be reachable on the port and address assigned" do - @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) - @server.listen - @server.unlisten - lambda { TCPSocket.new('127.0.0.1', 34343) }.should raise_error(Errno::ECONNREFUSED) - end + describe "when using webrick" do + before :each do + Puppet[:servertype] = 'webrick' + @params = { :address => "127.0.0.1", :port => 34343, :handlers => [ :node ], :xmlrpc_handlers => [ :status ] } + + # Get a safe temporary file + @tmpfile = Tempfile.new("webrick_integration_testing") + @dir = @tmpfile.path + "_dir" + + Puppet.settings[:confdir] = @dir + Puppet.settings[:vardir] = @dir + + Puppet::SSL::Host.ca_location = :local + + ca = Puppet::SSL::CertificateAuthority.new + ca.generate(Puppet[:certname]) unless Puppet::SSL::Certificate.find(Puppet[:certname]) + end + + after do + @tmpfile.delete + Puppet.settings.clear + + system("rm -rf %s" % @dir) + + Puppet::Util::Cacher.invalidate + end + + describe "before listening" do + it "should not be reachable at the specified address and port" do + lambda { TCPSocket.new('127.0.0.1', 34343) }.should raise_error + end + end + + describe "when listening" do + it "should be reachable on the specified address and port" do + @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) + @server.listen + lambda { TCPSocket.new('127.0.0.1', 34343) }.should_not raise_error + end + + it "should not allow multiple servers to listen on the same address and port" do + @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) + @server.listen + @server2 = Puppet::Network::Server.new(@params.merge(:port => 34343)) + lambda { @server2.listen }.should raise_error + end + + after :each do + @server.unlisten if @server.listening? + end + end + + describe "after unlistening" do + it "should not be reachable on the port and address assigned" do + @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) + @server.listen + @server.unlisten + lambda { TCPSocket.new('127.0.0.1', 34343) }.should raise_error(Errno::ECONNREFUSED) + end + end end - end -end
\ No newline at end of file +end diff --git a/spec/integration/node/catalog.rb b/spec/integration/node/catalog.rb index 941d2cc6c..285b85869 100755 --- a/spec/integration/node/catalog.rb +++ b/spec/integration/node/catalog.rb @@ -7,13 +7,13 @@ require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Node::Catalog do describe "when using the indirector" do + after { Puppet::Util::Cacher.invalidate } before do # This is so the tests work w/out networking. Facter.stubs(:to_hash).returns({"hostname" => "foo.domain.com"}) Facter.stubs(:value).returns("eh") end - after { Puppet::Node::Catalog.indirection.clear_cache } it "should be able to delegate to the :yaml terminus" do Puppet::Node::Catalog.indirection.stubs(:terminus_class).returns :yaml diff --git a/spec/integration/node/facts.rb b/spec/integration/node/facts.rb index c2f876578..cef3d79d4 100755 --- a/spec/integration/node/facts.rb +++ b/spec/integration/node/facts.rb @@ -7,13 +7,15 @@ require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Node::Facts do describe "when using the indirector" do - after { Puppet::Node::Facts.indirection.clear_cache } + after { Puppet::Util::Cacher.invalidate } it "should expire any cached node instances when it is saved" do Puppet::Node::Facts.indirection.stubs(:terminus_class).returns :yaml + + Puppet::Node::Facts.indirection.terminus(:yaml).should equal(Puppet::Node::Facts.indirection.terminus(:yaml)) terminus = Puppet::Node::Facts.indirection.terminus(:yaml) + terminus.stubs :save - terminus.expects(:save) Puppet::Node.expects(:expire).with("me") facts = Puppet::Node::Facts.new("me") diff --git a/spec/integration/ssl/certificate_authority.rb b/spec/integration/ssl/certificate_authority.rb new file mode 100755 index 000000000..d838bc586 --- /dev/null +++ b/spec/integration/ssl/certificate_authority.rb @@ -0,0 +1,137 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2008-4-17. +# Copyright (c) 2008. All rights reserved. + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/ssl/certificate_authority' +require 'tempfile' + +describe Puppet::SSL::CertificateAuthority do + before do + # Get a safe temporary file + file = Tempfile.new("ca_integration_testing") + @dir = file.path + file.delete + + Puppet.settings[:confdir] = @dir + Puppet.settings[:vardir] = @dir + + Puppet::SSL::Host.ca_location = :local + @ca = Puppet::SSL::CertificateAuthority.new + end + + after { + Puppet::SSL::Host.ca_location = :none + + system("rm -rf %s" % @dir) + Puppet.settings.clear + + Puppet::Util::Cacher.invalidate + + Puppet::SSL::CertificateAuthority.instance_variable_set("@instance", nil) + } + + it "should create a CA host" do + @ca.host.should be_ca + end + + it "should be able to generate a certificate" do + @ca.generate_ca_certificate + + @ca.host.certificate.should be_instance_of(Puppet::SSL::Certificate) + end + + it "should be able to generate a new host certificate" do + @ca.generate("newhost") + + Puppet::SSL::Certificate.find("newhost").should be_instance_of(Puppet::SSL::Certificate) + end + + it "should be able to revoke a host certificate" do + pending("This test doesn't actually work yet") do + @ca.generate("newhost") + + @ca.revoke("newhost") + + lambda { @ca.verify("newhost") }.should raise_error + end + end + + it "should have a CRL" do + @ca.generate_ca_certificate + @ca.crl.should_not be_nil + end + + it "should be able to read in a previously created CRL" do + @ca.generate_ca_certificate + + # Create it to start with. + @ca.crl + + Puppet::SSL::CertificateAuthority.new.crl.should_not be_nil + end + + describe "when signing certificates" do + before do + @host = Puppet::SSL::Host.new("luke.madstop.com") + + # We have to provide the key, since when we're in :ca_only mode, we can only interact + # with the CA key. + key = Puppet::SSL::Key.new(@host.name) + key.generate + + @host.key = key + @host.generate_certificate_request + + path = File.join(Puppet[:requestdir], "luke.madstop.com.pem") + end + + it "should be able to sign certificates" do + @ca.sign("luke.madstop.com") + end + + it "should save the signed certificate" do + @ca.sign("luke.madstop.com") + + Puppet::SSL::Certificate.find("luke.madstop.com").should be_instance_of(Puppet::SSL::Certificate) + end + + it "should be able to sign multiple certificates" do + @other = Puppet::SSL::Host.new("other.madstop.com") + okey = Puppet::SSL::Key.new(@other.name) + okey.generate + @other.key = okey + @other.generate_certificate_request + + @ca.sign("luke.madstop.com") + @ca.sign("other.madstop.com") + + Puppet::SSL::Certificate.find("other.madstop.com").should be_instance_of(Puppet::SSL::Certificate) + Puppet::SSL::Certificate.find("luke.madstop.com").should be_instance_of(Puppet::SSL::Certificate) + end + + it "should save the signed certificate to the :signeddir" do + @ca.sign("luke.madstop.com") + + client_cert = File.join(Puppet[:signeddir], "luke.madstop.com.pem") + File.read(client_cert).should == Puppet::SSL::Certificate.find("luke.madstop.com").content.to_s + end + + it "should save valid certificates" do + @ca.sign("luke.madstop.com") + + ssl = %x{which openssl} + + unless ssl + pending "No ssl available" + else + ca_cert = Puppet[:cacert] + client_cert = File.join(Puppet[:signeddir], "luke.madstop.com.pem") + output = %x{openssl verify -CAfile #{ca_cert} #{client_cert}} + $?.should == 0 + end + end + end +end diff --git a/spec/integration/ssl/certificate_request.rb b/spec/integration/ssl/certificate_request.rb new file mode 100755 index 000000000..f428718e7 --- /dev/null +++ b/spec/integration/ssl/certificate_request.rb @@ -0,0 +1,57 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2008-4-17. +# Copyright (c) 2008. All rights reserved. + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/ssl/certificate_request' +require 'tempfile' + +describe Puppet::SSL::CertificateRequest do + before do + # Get a safe temporary file + file = Tempfile.new("csr_integration_testing") + @dir = file.path + file.delete + + Puppet.settings.clear + + Puppet.settings[:confdir] = @dir + Puppet.settings[:vardir] = @dir + + @csr = Puppet::SSL::CertificateRequest.new("luke.madstop.com") + + @key = OpenSSL::PKey::RSA.new(512) + end + + after do + system("rm -rf %s" % @dir) + Puppet.settings.clear + + # This is necessary so the terminus instances don't lie around. + Puppet::Util::Cacher.invalidate + end + + it "should be able to generate CSRs" do + @csr.generate(@key) + end + + it "should be able to save CSRs" do + @csr.save + end + + it "should be able to find saved certificate requests via the Indirector" do + @csr.generate(@key) + @csr.save + + Puppet::SSL::CertificateRequest.find("luke.madstop.com").should be_instance_of(Puppet::SSL::CertificateRequest) + end + + it "should save the completely CSR when saving" do + @csr.generate(@key) + @csr.save + + Puppet::SSL::CertificateRequest.find("luke.madstop.com").content.to_s.should == @csr.content.to_s + end +end diff --git a/spec/integration/ssl/certificate_revocation_list.rb b/spec/integration/ssl/certificate_revocation_list.rb new file mode 100755 index 000000000..246654816 --- /dev/null +++ b/spec/integration/ssl/certificate_revocation_list.rb @@ -0,0 +1,42 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2008-5-5. +# Copyright (c) 2008. All rights reserved. + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/ssl/certificate_revocation_list' +require 'tempfile' + +describe Puppet::SSL::CertificateRevocationList do + before do + # Get a safe temporary file + file = Tempfile.new("ca_integration_testing") + @dir = file.path + file.delete + + Puppet.settings[:confdir] = @dir + Puppet.settings[:vardir] = @dir + + Puppet::SSL::Host.ca_location = :local + end + + after { + Puppet::SSL::Host.ca_location = :none + + system("rm -rf %s" % @dir) + Puppet.settings.clear + + # This is necessary so the terminus instances don't lie around. + Puppet::Util::Cacher.invalidate + } + + it "should be able to read in written out CRLs with no revoked certificates" do + ca = Puppet::SSL::CertificateAuthority.new + + raise "CRL not created" unless FileTest.exist?(Puppet[:hostcrl]) + + crl = Puppet::SSL::CertificateRevocationList.new("crl_int_testing") + crl.read(Puppet[:hostcrl]) + end +end diff --git a/spec/integration/ssl/host.rb b/spec/integration/ssl/host.rb new file mode 100755 index 000000000..65f10cef3 --- /dev/null +++ b/spec/integration/ssl/host.rb @@ -0,0 +1,90 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2008-4-17. +# Copyright (c) 2008. All rights reserved. + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/ssl/host' +require 'tempfile' + +describe Puppet::SSL::Host do + before do + # Get a safe temporary file + file = Tempfile.new("host_integration_testing") + @dir = file.path + file.delete + + Puppet.settings[:confdir] = @dir + Puppet.settings[:vardir] = @dir + + Puppet::SSL::Host.ca_location = :local + + @host = Puppet::SSL::Host.new("luke.madstop.com") + @ca = Puppet::SSL::CertificateAuthority.new + end + + after { + Puppet::SSL::Host.ca_location = :none + + system("rm -rf %s" % @dir) + Puppet.settings.clear + Puppet::Util::Cacher.invalidate + } + + it "should be considered a CA host if its name is equal to 'ca'" do + Puppet::SSL::Host.new("ca").should be_ca + end + + describe "when managing its key" do + it "should be able to generate and save a key" do + @host.generate_key + end + + it "should save the key such that the Indirector can find it" do + @host.generate_key + + Puppet::SSL::Key.find(@host.name).content.to_s.should == @host.key.to_s + end + + it "should save the private key into the :privatekeydir" do + @host.generate_key + File.read(File.join(Puppet.settings[:privatekeydir], "luke.madstop.com.pem")).should == @host.key.to_s + end + end + + describe "when managing its certificate request" do + it "should be able to generate and save a certificate request" do + @host.generate_certificate_request + end + + it "should save the certificate request such that the Indirector can find it" do + @host.generate_certificate_request + + Puppet::SSL::CertificateRequest.find(@host.name).content.to_s.should == @host.certificate_request.to_s + end + + it "should save the private certificate request into the :privatekeydir" do + @host.generate_certificate_request + File.read(File.join(Puppet.settings[:requestdir], "luke.madstop.com.pem")).should == @host.certificate_request.to_s + end + end + + describe "when the CA host" do + it "should never store its key in the :privatekeydir" do + Puppet.settings.use(:main, :ssl, :ca) + @ca = Puppet::SSL::Host.new(Puppet::SSL::Host.ca_name) + @ca.generate_key + + FileTest.should_not be_exist(File.join(Puppet[:privatekeydir], "ca.pem")) + end + end + + it "should pass the verification of its own SSL store" do + @host.generate + @ca = Puppet::SSL::CertificateAuthority.new + @ca.sign(@host.name) + + @host.ssl_store.verify(@host.certificate.content).should be_true + end +end diff --git a/spec/integration/transaction/report.rb b/spec/integration/transaction/report.rb index 48e59f203..6bbd5eb10 100755 --- a/spec/integration/transaction/report.rb +++ b/spec/integration/transaction/report.rb @@ -7,7 +7,7 @@ require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Transaction::Report do describe "when using the indirector" do - after { Puppet::Transaction::Report.indirection.clear_cache } + after { Puppet::Util::Cacher.invalidate } it "should be able to delegate to the :processor terminus" do Puppet::Transaction::Report.indirection.stubs(:terminus_class).returns :processor diff --git a/spec/shared_behaviours/file_server_terminus.rb b/spec/shared_behaviours/file_server_terminus.rb index de08f29fc..883db58f5 100644 --- a/spec/shared_behaviours/file_server_terminus.rb +++ b/spec/shared_behaviours/file_server_terminus.rb @@ -7,7 +7,7 @@ describe "Puppet::Indirector::FileServerTerminus", :shared => true do # This only works if the shared behaviour is included before # the 'before' block in the including context. before do - Puppet::FileServing::Configuration.clear_cache + Puppet::Util::Cacher.invalidate FileTest.stubs(:exists?).with(Puppet[:fileserverconfig]).returns(true) FileTest.stubs(:exists?).with("/my/mount/path").returns(true) FileTest.stubs(:directory?).with("/my/mount/path").returns(true) diff --git a/spec/unit/file_serving/configuration.rb b/spec/unit/file_serving/configuration.rb index a0710e20d..d51fa70b2 100755 --- a/spec/unit/file_serving/configuration.rb +++ b/spec/unit/file_serving/configuration.rb @@ -15,12 +15,12 @@ describe Puppet::FileServing::Configuration do it "should have a method for removing the current configuration instance" do old = Puppet::FileServing::Configuration.create - Puppet::FileServing::Configuration.clear_cache + Puppet::Util::Cacher.invalidate Puppet::FileServing::Configuration.create.should_not equal(old) end after do - Puppet::FileServing::Configuration.clear_cache + Puppet::Util::Cacher.invalidate end end @@ -32,7 +32,7 @@ describe Puppet::FileServing::Configuration do end after :each do - Puppet::FileServing::Configuration.clear_cache + Puppet::Util::Cacher.invalidate end describe Puppet::FileServing::Configuration, " when initializing" do diff --git a/spec/unit/file_serving/mount.rb b/spec/unit/file_serving/mount.rb index ebe058301..6f491e3b2 100755 --- a/spec/unit/file_serving/mount.rb +++ b/spec/unit/file_serving/mount.rb @@ -12,9 +12,9 @@ end describe Puppet::FileServing::Mount do it "should provide a method for clearing its cached host information" do - Puppet::FileServing::Mount.new("whatever").send(:localmap) - Puppet::FileServing::Mount.clear_cache - Puppet::FileServing::Mount.send(:class_variable_get, "@@localmap").should be_nil + old = Puppet::FileServing::Mount.localmap + Puppet::Util::Cacher.invalidate + Puppet::FileServing::Mount.localmap.should_not equal(old) end end @@ -106,7 +106,7 @@ describe Puppet::FileServing::Mount, " when finding files" do end after do - Puppet::FileServing::Mount.clear_cache + Puppet::Util::Cacher.invalidate end end diff --git a/spec/unit/indirector/certificate/ca.rb b/spec/unit/indirector/certificate/ca.rb new file mode 100755 index 000000000..0158a8d90 --- /dev/null +++ b/spec/unit/indirector/certificate/ca.rb @@ -0,0 +1,28 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2008-3-7. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/certificate/ca' + +describe Puppet::SSL::Certificate::Ca do + it "should have documentation" do + Puppet::SSL::Certificate::Ca.doc.should be_instance_of(String) + end + + it "should use the :signeddir as the collection directory" do + Puppet.settings.expects(:value).with(:signeddir).returns "/cert/dir" + Puppet::SSL::Certificate::Ca.collection_directory.should == "/cert/dir" + end + + it "should store the ca certificate at the :cacert location" do + Puppet.settings.stubs(:use) + Puppet.settings.stubs(:value).returns "whatever" + Puppet.settings.stubs(:value).with(:cacert).returns "/ca/cert" + file = Puppet::SSL::Certificate::Ca.new + file.stubs(:ca?).returns true + file.path("whatever").should == "/ca/cert" + end +end diff --git a/spec/unit/indirector/certificate/file.rb b/spec/unit/indirector/certificate/file.rb new file mode 100755 index 000000000..6fb00f1ca --- /dev/null +++ b/spec/unit/indirector/certificate/file.rb @@ -0,0 +1,28 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2008-3-7. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/certificate/file' + +describe Puppet::SSL::Certificate::File do + it "should have documentation" do + Puppet::SSL::Certificate::File.doc.should be_instance_of(String) + end + + it "should use the :certdir as the collection directory" do + Puppet.settings.expects(:value).with(:certdir).returns "/cert/dir" + Puppet::SSL::Certificate::File.collection_directory.should == "/cert/dir" + end + + it "should store the ca certificate at the :localcacert location" do + Puppet.settings.stubs(:use) + Puppet.settings.stubs(:value).returns "whatever" + Puppet.settings.stubs(:value).with(:localcacert).returns "/ca/cert" + file = Puppet::SSL::Certificate::File.new + file.stubs(:ca?).returns true + file.path("whatever").should == "/ca/cert" + end +end diff --git a/spec/unit/indirector/certificate/rest.rb b/spec/unit/indirector/certificate/rest.rb new file mode 100755 index 000000000..62b1ef543 --- /dev/null +++ b/spec/unit/indirector/certificate/rest.rb @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/certificate/rest' + +describe Puppet::SSL::Certificate::Rest do + before do + @searcher = Puppet::SSL::Certificate::Rest.new + end + + it "should be a sublcass of Puppet::Indirector::REST" do + Puppet::SSL::Certificate::Rest.superclass.should equal(Puppet::Indirector::REST) + end +end diff --git a/spec/unit/indirector/certificate_request/ca.rb b/spec/unit/indirector/certificate_request/ca.rb new file mode 100755 index 000000000..b35d8b474 --- /dev/null +++ b/spec/unit/indirector/certificate_request/ca.rb @@ -0,0 +1,19 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2008-3-7. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/certificate_request/ca' + +describe Puppet::SSL::CertificateRequest::Ca do + it "should have documentation" do + Puppet::SSL::CertificateRequest::Ca.doc.should be_instance_of(String) + end + + it "should use the :csrdir as the collection directory" do + Puppet.settings.expects(:value).with(:csrdir).returns "/request/dir" + Puppet::SSL::CertificateRequest::Ca.collection_directory.should == "/request/dir" + end +end diff --git a/spec/unit/indirector/certificate_request/file.rb b/spec/unit/indirector/certificate_request/file.rb new file mode 100755 index 000000000..e1f442e2a --- /dev/null +++ b/spec/unit/indirector/certificate_request/file.rb @@ -0,0 +1,19 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2008-3-7. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/certificate_request/file' + +describe Puppet::SSL::CertificateRequest::File do + it "should have documentation" do + Puppet::SSL::CertificateRequest::File.doc.should be_instance_of(String) + end + + it "should use the :requestdir as the collection directory" do + Puppet.settings.expects(:value).with(:requestdir).returns "/request/dir" + Puppet::SSL::CertificateRequest::File.collection_directory.should == "/request/dir" + end +end diff --git a/spec/unit/indirector/certificate_request/rest.rb b/spec/unit/indirector/certificate_request/rest.rb new file mode 100755 index 000000000..cbd525f17 --- /dev/null +++ b/spec/unit/indirector/certificate_request/rest.rb @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/certificate_request/rest' + +describe Puppet::SSL::CertificateRequest::Rest do + before do + @searcher = Puppet::SSL::CertificateRequest::Rest.new + end + + it "should be a sublcass of Puppet::Indirector::REST" do + Puppet::SSL::CertificateRequest::Rest.superclass.should equal(Puppet::Indirector::REST) + end +end diff --git a/spec/unit/indirector/certificate_revocation_list/ca.rb b/spec/unit/indirector/certificate_revocation_list/ca.rb new file mode 100755 index 000000000..f6da86861 --- /dev/null +++ b/spec/unit/indirector/certificate_revocation_list/ca.rb @@ -0,0 +1,21 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2008-3-7. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/certificate_revocation_list/ca' + +describe Puppet::SSL::CertificateRevocationList::Ca do + it "should have documentation" do + Puppet::SSL::CertificateRevocationList::Ca.doc.should be_instance_of(String) + end + + it "should use the :cacrl setting as the crl location" do + Puppet.settings.stubs(:value).returns "whatever" + Puppet.settings.stubs(:use) + Puppet.settings.stubs(:value).with(:cacrl).returns "/request/dir" + Puppet::SSL::CertificateRevocationList::Ca.new.path("whatever").should == "/request/dir" + end +end diff --git a/spec/unit/indirector/certificate_revocation_list/file.rb b/spec/unit/indirector/certificate_revocation_list/file.rb new file mode 100755 index 000000000..5db4f8c06 --- /dev/null +++ b/spec/unit/indirector/certificate_revocation_list/file.rb @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2008-3-7. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/certificate_revocation_list/file' + +describe Puppet::SSL::CertificateRevocationList::File do + it "should have documentation" do + Puppet::SSL::CertificateRevocationList::File.doc.should be_instance_of(String) + end + + it "should always store the file to :hostcrl location" do + Puppet.settings.expects(:value).with(:hostcrl).returns "/host/crl" + Puppet.settings.stubs(:use) + Puppet::SSL::CertificateRevocationList::File.file_location.should == "/host/crl" + end +end diff --git a/spec/unit/indirector/certificate_revocation_list/rest.rb b/spec/unit/indirector/certificate_revocation_list/rest.rb new file mode 100755 index 000000000..f6f493c37 --- /dev/null +++ b/spec/unit/indirector/certificate_revocation_list/rest.rb @@ -0,0 +1,15 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/certificate_revocation_list/rest' + +describe Puppet::SSL::CertificateRevocationList::Rest do + before do + @searcher = Puppet::SSL::CertificateRevocationList::Rest.new + end + + it "should be a sublcass of Puppet::Indirector::REST" do + Puppet::SSL::CertificateRevocationList::Rest.superclass.should equal(Puppet::Indirector::REST) + end +end diff --git a/spec/unit/indirector/indirection.rb b/spec/unit/indirector/indirection.rb index cefd0557e..e4799d3ff 100755 --- a/spec/unit/indirector/indirection.rb +++ b/spec/unit/indirector/indirection.rb @@ -87,6 +87,9 @@ describe "Delegation Authorizer", :shared => true do end describe Puppet::Indirector::Indirection do + after do + Puppet::Util::Cacher.invalidate + end describe "when initializing" do # (LAK) I've no idea how to test this, really. it "should store a reference to itself before it consumes its options" do @@ -494,7 +497,7 @@ describe Puppet::Indirector::Indirection do after :each do @indirection.delete - Puppet::Indirector::Indirection.clear_cache + Puppet::Util::Cacher.invalidate end end @@ -615,15 +618,6 @@ describe Puppet::Indirector::Indirection do @indirection.terminus(:foo).should equal(@terminus) end - it "should allow the clearance of cached terminus instances" do - terminus1 = mock 'terminus1' - terminus2 = mock 'terminus2' - @terminus_class.stubs(:new).returns(terminus1, terminus2, ArgumentError) - @indirection.terminus(:foo).should equal(terminus1) - @indirection.class.clear_cache - @indirection.terminus(:foo).should equal(terminus2) - end - # Make sure it caches the terminus. it "should return the same terminus instance each time for a given name" do @terminus_class.stubs(:new).returns(@terminus) @@ -638,7 +632,6 @@ describe Puppet::Indirector::Indirection do after do @indirection.delete - Puppet::Indirector::Indirection.clear_cache end end @@ -675,7 +668,6 @@ describe Puppet::Indirector::Indirection do after do @indirection.delete - Puppet::Indirector::Indirection.clear_cache end end @@ -706,15 +698,6 @@ describe Puppet::Indirector::Indirection do @indirection.cache.should equal(@cache) @indirection.cache.should equal(@cache) end - - it "should remove the cache terminus when all other terminus instances are cleared" do - cache2 = mock 'cache2' - @cache_class.stubs(:new).returns(@cache, cache2) - @indirection.cache_class = :cache_terminus - @indirection.cache.should equal(@cache) - @indirection.clear_cache - @indirection.cache.should equal(cache2) - end end describe "and saving" do @@ -725,7 +708,6 @@ describe Puppet::Indirector::Indirection do after :each do @indirection.delete - Puppet::Indirector::Indirection.clear_cache end end end diff --git a/spec/unit/indirector/key/ca.rb b/spec/unit/indirector/key/ca.rb new file mode 100755 index 000000000..579617f71 --- /dev/null +++ b/spec/unit/indirector/key/ca.rb @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2008-3-7. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/key/ca' + +describe Puppet::SSL::Key::Ca do + it "should have documentation" do + Puppet::SSL::Key::Ca.doc.should be_instance_of(String) + end + + it "should store the ca key at the :cakey location" do + Puppet.settings.stubs(:use) + Puppet.settings.stubs(:value).returns "whatever" + Puppet.settings.stubs(:value).with(:cakey).returns "/ca/key" + file = Puppet::SSL::Key::Ca.new + file.stubs(:ca?).returns true + file.path("whatever").should == "/ca/key" + end + + describe "when choosing the path for the public key" do + it "should fail if the key is not for the CA" do + Puppet.settings.stubs(:use) + Puppet.settings.stubs(:value).returns "whatever" + Puppet.settings.stubs(:value).with(:cakey).returns "/ca/key" + file = Puppet::SSL::Key::Ca.new + file.stubs(:ca?).returns false + lambda { file.path("whatever") }.should raise_error(ArgumentError) + end + end +end diff --git a/spec/unit/indirector/key/file.rb b/spec/unit/indirector/key/file.rb new file mode 100755 index 000000000..8a1cb04bd --- /dev/null +++ b/spec/unit/indirector/key/file.rb @@ -0,0 +1,104 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2008-3-7. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/key/file' + +describe Puppet::SSL::Key::File do + it "should have documentation" do + Puppet::SSL::Key::File.doc.should be_instance_of(String) + end + + it "should use the :privatekeydir as the collection directory" do + Puppet.settings.expects(:value).with(:privatekeydir).returns "/key/dir" + Puppet::SSL::Key::File.collection_directory.should == "/key/dir" + end + + it "should store the ca key at the :cakey location" do + Puppet.settings.stubs(:use) + Puppet.settings.stubs(:value).returns "whatever" + Puppet.settings.stubs(:value).with(:cakey).returns "/ca/key" + file = Puppet::SSL::Key::File.new + file.stubs(:ca?).returns true + file.path("whatever").should == "/ca/key" + end + + describe "when choosing the path for the public key" do + it "should use the :capub setting location if the key is for the certificate authority" do + Puppet.settings.stubs(:value).returns "/fake/dir" + Puppet.settings.stubs(:value).with(:capub).returns "/ca/pubkey" + Puppet.settings.stubs(:use) + + @searcher = Puppet::SSL::Key::File.new + @searcher.stubs(:ca?).returns true + @searcher.public_key_path("whatever").should == "/ca/pubkey" + end + + it "should use the host name plus '.pem' in :publickeydir for normal hosts" do + Puppet.settings.stubs(:value).with(:privatekeydir).returns "/private/key/dir" + Puppet.settings.stubs(:value).with(:publickeydir).returns "/public/key/dir" + Puppet.settings.stubs(:use) + + @searcher = Puppet::SSL::Key::File.new + @searcher.stubs(:ca?).returns false + @searcher.public_key_path("whatever").should == "/public/key/dir/whatever.pem" + end + end + + describe "when managing private keys" do + before do + @searcher = Puppet::SSL::Key::File.new + + @private_key_path = File.join("/fake/key/path") + @public_key_path = File.join("/other/fake/key/path") + + @searcher.stubs(:public_key_path).returns @public_key_path + @searcher.stubs(:path).returns @private_key_path + + FileTest.stubs(:directory?).returns true + FileTest.stubs(:writable?).returns true + + @public_key = stub 'public_key' + @real_key = stub 'sslkey', :public_key => @public_key + + @key = stub 'key', :name => "myname", :content => @real_key + + @request = stub 'request', :key => "myname", :instance => @key + end + + it "should save the public key when saving the private key" do + File.stubs(:open).with(@private_key_path, "w") + + fh = mock 'filehandle' + + File.expects(:open).with(@public_key_path, "w").yields fh + @public_key.expects(:to_pem).returns "my pem" + + fh.expects(:print).with "my pem" + + @searcher.save(@request) + end + + it "should destroy the public key when destroying the private key" do + File.stubs(:unlink).with(@private_key_path) + FileTest.stubs(:exist?).with(@private_key_path).returns true + FileTest.expects(:exist?).with(@public_key_path).returns true + File.expects(:unlink).with(@public_key_path) + + @searcher.destroy(@request) + end + + it "should not fail if the public key does not exist when deleting the private key" do + File.stubs(:unlink).with(@private_key_path) + + FileTest.stubs(:exist?).with(@private_key_path).returns true + FileTest.expects(:exist?).with(@public_key_path).returns false + File.expects(:unlink).with(@public_key_path).never + + @searcher.destroy(@request) + end + end +end diff --git a/spec/unit/indirector/rest.rb b/spec/unit/indirector/rest.rb index 1a1064491..1f802b603 100755 --- a/spec/unit/indirector/rest.rb +++ b/spec/unit/indirector/rest.rb @@ -3,6 +3,39 @@ require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/indirector/rest' +describe "a REST http call", :shared => true do + it "should accept a path" do + lambda { @search.send(@method, *@arguments) }.should_not raise_error(ArgumentError) + end + + it "should require a path" do + lambda { @searcher.send(@method) }.should raise_error(ArgumentError) + end + + it "should use the Http Pool with the remote server and port looked up from the REST terminus" do + @searcher.expects(:rest_connection_details).returns(@details) + + conn = mock 'connection' + result = stub 'result', :body => "body" + conn.stubs(:put).returns result + conn.stubs(:delete).returns result + conn.stubs(:get).returns result + Puppet::Network::HttpPool.expects(:http_instance).with(@details[:host], @details[:port]).returns conn + @searcher.send(@method, *@arguments) + end + + it "should return the results of the request" do + conn = mock 'connection' + result = stub 'result', :body => "result" + conn.stubs(:put).returns result + conn.stubs(:delete).returns result + conn.stubs(:get).returns result + Puppet::Network::HttpPool.stubs(:http_instance).returns conn + + @searcher.send(@method, *@arguments).should == 'result' + end +end + describe Puppet::Indirector::REST do before do Puppet::Indirector::Terminus.stubs(:register_terminus_class) @@ -19,354 +52,280 @@ describe Puppet::Indirector::REST do @searcher = @rest_class.new end - - describe "when locating the REST connection" do - before do - Puppet.settings.stubs(:value).returns("whatever") - end - - it "should return the :server setting as the host" do - Puppet.settings.expects(:value).with(:server).returns "myserver" - @searcher.rest_connection_details[:host].should == "myserver" - end - - it "should return the :masterport (as an Integer) as the port" do - Puppet.settings.expects(:value).with(:masterport).returns "1234" - @searcher.rest_connection_details[:port].should == 1234 - end + + describe "when configuring the REST http call" do + before do + Puppet.settings.stubs(:value).returns("rest_testing") + end + + it "should return the :server setting as the host" do + Puppet.settings.expects(:value).with(:server).returns "myserver" + @searcher.rest_connection_details[:host].should == "myserver" + end + + it "should return the :masterport (as an Integer) as the port" do + Puppet.settings.expects(:value).with(:masterport).returns "1234" + @searcher.rest_connection_details[:port].should == 1234 + end end - + describe "when doing a network fetch" do - before :each do - Net::HTTP.stubs(:start).returns('result') - @details = { :host => '127.0.0.1', :port => 34343 } - @searcher.stubs(:rest_connection_details).returns(@details) - end - - it "should accept a path" do - lambda { @search.network_fetch('foo') }.should_not raise_error(ArgumentError) - end - - it "should require a path" do - lambda { @searcher.network_fetch }.should raise_error(ArgumentError) - end - - it "should look up connection details" do - @searcher.expects(:rest_connection_details).returns(@details) - @searcher.network_fetch('foo') - end - - it "should use the GET http method" do - @mock_result = stub('mock result', :body => 'result') - @mock_connection = mock('mock http connection', :get => @mock_result) - @searcher.stubs(:network).yields(@mock_connection) - @searcher.network_fetch('foo') - end - - it "should use the appropriate remote server" do - Net::HTTP.expects(:start).with {|host, port| host == @details[:host] } - @searcher.network_fetch('foo') - end - - it "should use the appropriate remote port" do - Net::HTTP.expects(:start).with {|host, port| port == @details[:port] } - @searcher.network_fetch('foo') - end - - it "should use the provided path" do - @mock_result = stub('mock result', :body => 'result') - @mock_connection = stub('mock http connection') - @mock_connection.expects(:get).with('/foo').returns(@mock_result) - @searcher.stubs(:network).yields(@mock_connection) - @searcher.network_fetch('foo') - end - - it "should return the results of the GET request" do - @searcher.network_fetch('foo').should == 'result' - end + before :each do + Net::HTTP.stubs(:start).returns('result') + @details = { :host => '127.0.0.1', :port => 34343 } + @searcher.stubs(:rest_connection_details).returns(@details) + + @method = :network_fetch + @arguments = "foo" + end + + it_should_behave_like "a REST http call" + + it "should use the GET http method" do + @mock_result = stub('mock result', :body => 'result') + @mock_connection = mock('mock http connection', :get => @mock_result) + @searcher.stubs(:network).returns(@mock_connection) + @searcher.network_fetch('foo') + end + + it "should use the provided path" do + @mock_result = stub('mock result', :body => 'result') + @mock_connection = stub('mock http connection') + @mock_connection.expects(:get).with('/foo').returns(@mock_result) + @searcher.stubs(:network).returns(@mock_connection) + @searcher.network_fetch('foo') + end end describe "when doing a network delete" do - before :each do - Net::HTTP.stubs(:start).returns('result') - @details = { :host => '127.0.0.1', :port => 34343 } - @searcher.stubs(:rest_connection_details).returns(@details) - end - - it "should accept a path" do - lambda { @search.network_delete('foo') }.should_not raise_error(ArgumentError) - end - - it "should require a path" do - lambda { @searcher.network_delete }.should raise_error(ArgumentError) - end - - it "should look up connection details" do - @searcher.expects(:rest_connection_details).returns(@details) - @searcher.network_delete('foo') - end - - it "should use the DELETE http method" do - @mock_result = stub('mock result', :body => 'result') - @mock_connection = mock('mock http connection', :delete => @mock_result) - @searcher.stubs(:network).yields(@mock_connection) - @searcher.network_delete('foo') - end - - it "should use the appropriate remote server" do - Net::HTTP.expects(:start).with {|host, port| host == @details[:host] } - @searcher.network_delete('foo') - end - - it "should use the appropriate remote port" do - Net::HTTP.expects(:start).with {|host, port| port == @details[:port] } - @searcher.network_delete('foo') - end - - it "should use the provided path" do - @mock_result = stub('mock result', :body => 'result') - @mock_connection = stub('mock http connection') - @mock_connection.expects(:delete).with('/foo').returns(@mock_result) - @searcher.stubs(:network).yields(@mock_connection) - @searcher.network_delete('foo') - end - - it "should return the results of the DELETE request" do - @searcher.network_delete('foo').should == 'result' - end + before :each do + Net::HTTP.stubs(:start).returns('result') + @details = { :host => '127.0.0.1', :port => 34343 } + @searcher.stubs(:rest_connection_details).returns(@details) + + @method = :network_delete + @arguments = "foo" + end + + it_should_behave_like "a REST http call" + + it "should use the DELETE http method" do + @mock_result = stub('mock result', :body => 'result') + @mock_connection = mock('mock http connection', :delete => @mock_result) + @searcher.stubs(:network).returns(@mock_connection) + @searcher.network_delete('foo') + end end - + describe "when doing a network put" do - before :each do - Net::HTTP.stubs(:start).returns('result') - @details = { :host => '127.0.0.1', :port => 34343 } - @data = { :foo => 'bar' } - @searcher.stubs(:rest_connection_details).returns(@details) - end - - it "should accept a path and data" do - lambda { @search.network_put('foo', @data) }.should_not raise_error(ArgumentError) - end - - it "should require a path and data" do - lambda { @searcher.network_put('foo') }.should raise_error(ArgumentError) - end - - it "should look up connection details" do - @searcher.expects(:rest_connection_details).returns(@details) - @searcher.network_put('foo', @data) - end - - it "should use the appropriate remote server" do - Net::HTTP.expects(:start).with {|host, port| host == @details[:host] } - @searcher.network_put('foo', @data) - end - - it "should use the appropriate remote port" do - Net::HTTP.expects(:start).with {|host, port| port == @details[:port] } - @searcher.network_put('foo', @data) - end - - it "should use the PUT http method" do - @mock_result = stub('mock result', :body => 'result') - @mock_connection = mock('mock http connection', :put => @mock_result) - @searcher.stubs(:network).yields(@mock_connection) - @searcher.network_put('foo', @data) - end - - it "should use the provided path" do - @mock_result = stub('mock result', :body => 'result') - @mock_connection = stub('mock http connection') - @mock_connection.expects(:put).with {|path, data| path == '/foo' }.returns(@mock_result) - @searcher.stubs(:network).yields(@mock_connection) - @searcher.network_put('foo', @data) - end - - it "should use the provided data" do - @mock_result = stub('mock result', :body => 'result') - @mock_connection = stub('mock http connection') - @mock_connection.expects(:put).with {|path, data| data == @data }.returns(@mock_result) - @searcher.stubs(:network).yields(@mock_connection) - @searcher.network_put('foo', @data) - end - - it "should return the results of the PUT request" do - @searcher.network_put('foo', @data).should == 'result' - end + before :each do + Net::HTTP.stubs(:start).returns('result') + @details = { :host => '127.0.0.1', :port => 34343 } + @data = { :foo => 'bar' } + @searcher.stubs(:rest_connection_details).returns(@details) + + @method = :network_put + @arguments = ["foo", @data] + end + + it_should_behave_like "a REST http call" + + it "should use the PUT http method" do + @mock_result = stub('mock result', :body => 'result') + @mock_connection = mock('mock http connection', :put => @mock_result) + @searcher.stubs(:network).returns(@mock_connection) + @searcher.network_put('foo', @data) + end + + it "should use the provided path" do + @mock_result = stub('mock result', :body => 'result') + @mock_connection = stub('mock http connection') + @mock_connection.expects(:put).with {|path, data| path == '/foo' }.returns(@mock_result) + @searcher.stubs(:network).returns(@mock_connection) + @searcher.network_put('foo', @data) + end + + it "should use the provided data" do + @mock_result = stub('mock result', :body => 'result') + @mock_connection = stub('mock http connection') + @mock_connection.expects(:put).with {|path, data| data == @data }.returns(@mock_result) + @searcher.stubs(:network).returns(@mock_connection) + @searcher.network_put('foo', @data) + end end describe "when doing a find" do - before :each do - @result = { :foo => 'bar'}.to_yaml - @searcher.stubs(:network_fetch).returns(@result) # neuter the network connection - @model.stubs(:from_yaml).returns(@instance) - - @request = stub 'request', :key => 'foo' - end - - it "should look up the model instance over the network" do - @searcher.expects(:network_fetch).returns(@result) - @searcher.find(@request) - end - - it "should look up the model instance using the named indirection" do - @searcher.expects(:network_fetch).with {|path| path =~ %r{^#{@indirection.name.to_s}/} }.returns(@result) - @searcher.find(@request) - end - - it "should look up the model instance using the provided key" do - @searcher.expects(:network_fetch).with {|path| path =~ %r{/foo$} }.returns(@result) - @searcher.find(@request) - end - - it "should deserialize result data to a Model instance" do - @model.expects(:from_yaml) - @searcher.find(@request) - end - - it "should return the deserialized Model instance" do - @searcher.find(@request).should == @instance - end - - it "should return nil when deserialized model instance is nil" do - @model.stubs(:from_yaml).returns(nil) - @searcher.find(@request).should be_nil - end - - it "should generate an error when result data deserializes improperly" do - @model.stubs(:from_yaml).raises(ArgumentError) - lambda { @searcher.find(@request) }.should raise_error(ArgumentError) - end - - it "should generate an error when result data specifies an error" do - @searcher.stubs(:network_fetch).returns(RuntimeError.new("bogus").to_yaml) - lambda { @searcher.find(@request) }.should raise_error(RuntimeError) - end + before :each do + @result = { :foo => 'bar'}.to_yaml + @searcher.stubs(:network_fetch).returns(@result) # neuter the network connection + @model.stubs(:from_yaml).returns(@instance) + + @request = stub 'request', :key => 'foo' + end + + it "should look up the model instance over the network" do + @searcher.expects(:network_fetch).returns(@result) + @searcher.find(@request) + end + + it "should look up the model instance using the named indirection" do + @searcher.expects(:network_fetch).with {|path| path =~ %r{^#{@indirection.name.to_s}/} }.returns(@result) + @searcher.find(@request) + end + + it "should look up the model instance using the provided key" do + @searcher.expects(:network_fetch).with {|path| path =~ %r{/foo$} }.returns(@result) + @searcher.find(@request) + end + + it "should deserialize result data to a Model instance" do + @model.expects(:from_yaml) + @searcher.find(@request) + end + + it "should return the deserialized Model instance" do + @searcher.find(@request).should == @instance + end + + it "should return nil when deserialized model instance is nil" do + @model.stubs(:from_yaml).returns(nil) + @searcher.find(@request).should be_nil + end + + it "should generate an error when result data deserializes improperly" do + @model.stubs(:from_yaml).raises(ArgumentError) + lambda { @searcher.find(@request) }.should raise_error(ArgumentError) + end + + it "should generate an error when result data specifies an error" do + @searcher.stubs(:network_fetch).returns(RuntimeError.new("bogus").to_yaml) + lambda { @searcher.find(@request) }.should raise_error(RuntimeError) + end end describe "when doing a search" do - before :each do - @result = [1, 2].to_yaml - @searcher.stubs(:network_fetch).returns(@result) - @model.stubs(:from_yaml).returns(@instance) - - @request = stub 'request', :key => 'foo' - end - - it "should look up the model data over the network" do - @searcher.expects(:network_fetch).returns(@result) - @searcher.search(@request) - end - - it "should look up the model instance using the named indirection" do - @searcher.expects(:network_fetch).with {|path| path =~ %r{^#{@indirection.name.to_s}s/} }.returns(@result) - @searcher.search(@request) - end - - it "should look up the model instance using the provided key" do - @searcher.expects(:network_fetch).with {|path| path =~ %r{/foo$} }.returns(@result) - @searcher.search(@request) - end - - it "should deserialize result data into a list of Model instances" do - @model.expects(:from_yaml).at_least(2) - @searcher.search(@request) - end - - it "should generate an error when result data deserializes improperly" do - @model.stubs(:from_yaml).raises(ArgumentError) - lambda { @searcher.search(@request) }.should raise_error(ArgumentError) - end - - it "should generate an error when result data specifies an error" do - @searcher.stubs(:network_fetch).returns(RuntimeError.new("bogus").to_yaml) - lambda { @searcher.search(@request) }.should raise_error(RuntimeError) - end - end - + before :each do + @result = [1, 2].to_yaml + @searcher.stubs(:network_fetch).returns(@result) + @model.stubs(:from_yaml).returns(@instance) + + @request = stub 'request', :key => 'foo' + end + + it "should look up the model data over the network" do + @searcher.expects(:network_fetch).returns(@result) + @searcher.search(@request) + end + + it "should look up the model instance using the plural of the named indirection" do + @searcher.expects(:network_fetch).with {|path| path =~ %r{^#{@indirection.name.to_s}s/} }.returns(@result) + @searcher.search(@request) + end + + it "should look up the model instance using the provided key" do + @searcher.expects(:network_fetch).with {|path| path =~ %r{/foo$} }.returns(@result) + @searcher.search(@request) + end + + it "should deserialize result data into a list of Model instances" do + @model.expects(:from_yaml).at_least(2) + @searcher.search(@request) + end + + it "should generate an error when result data deserializes improperly" do + @model.stubs(:from_yaml).raises(ArgumentError) + lambda { @searcher.search(@request) }.should raise_error(ArgumentError) + end + + it "should generate an error when result data specifies an error" do + @searcher.stubs(:network_fetch).returns(RuntimeError.new("bogus").to_yaml) + lambda { @searcher.search(@request) }.should raise_error(RuntimeError) + end + end + describe "when doing a destroy" do - before :each do - @result = true.to_yaml - @searcher.stubs(:network_delete).returns(@result) # neuter the network connection - @model.stubs(:from_yaml).returns(@instance) - - @request = stub 'request', :key => 'foo' - end - - it "should look up the model instance over the network" do - @searcher.expects(:network_delete).returns(@result) - @searcher.destroy(@request) - end - - it "should look up the model instance using the named indirection" do - @searcher.expects(:network_delete).with {|path| path =~ %r{^#{@indirection.name.to_s}/} }.returns(@result) - @searcher.destroy(@request) - end - - it "should look up the model instance using the provided key" do - @searcher.expects(:network_delete).with {|path| path =~ %r{/foo$} }.returns(@result) - @searcher.destroy(@request) - end - - it "should deserialize result data" do - YAML.expects(:load).with(@result) - @searcher.destroy(@request) - end - - it "should return deserialized result data" do - @searcher.destroy(@request).should == true - end - - it "should generate an error when result data specifies an error" do - @searcher.stubs(:network_delete).returns(RuntimeError.new("bogus").to_yaml) - lambda { @searcher.destroy(@request) }.should raise_error(RuntimeError) - end + before :each do + @result = true.to_yaml + @searcher.stubs(:network_delete).returns(@result) # neuter the network connection + @model.stubs(:from_yaml).returns(@instance) + + @request = stub 'request', :key => 'foo' + end + + it "should look up the model instance over the network" do + @searcher.expects(:network_delete).returns(@result) + @searcher.destroy(@request) + end + + it "should look up the model instance using the named indirection" do + @searcher.expects(:network_delete).with {|path| path =~ %r{^#{@indirection.name.to_s}/} }.returns(@result) + @searcher.destroy(@request) + end + + it "should look up the model instance using the provided key" do + @searcher.expects(:network_delete).with {|path| path =~ %r{/foo$} }.returns(@result) + @searcher.destroy(@request) + end + + it "should deserialize result data" do + YAML.expects(:load).with(@result) + @searcher.destroy(@request) + end + + it "should return deserialized result data" do + @searcher.destroy(@request).should == true + end + + it "should generate an error when result data specifies an error" do + @searcher.stubs(:network_delete).returns(RuntimeError.new("bogus").to_yaml) + lambda { @searcher.destroy(@request) }.should raise_error(RuntimeError) + end end describe "when doing a save" do - before :each do - @result = { :foo => 'bar'}.to_yaml - @searcher.stubs(:network_put).returns(@result) # neuter the network connection - @model.stubs(:from_yaml).returns(@instance) - - @request = stub 'request', :instance => @instance - end - - it "should save the model instance over the network" do - @searcher.expects(:network_put).returns(@result) - @searcher.save(@request) - end - - it "should save the model instance using the named indirection" do - @searcher.expects(:network_put).with do |path, data| - path =~ %r{^#{@indirection.name.to_s}/} and - data == @instance.to_yaml - end.returns(@result) - @searcher.save(@request) - end - - it "should deserialize result data to a Model instance" do - @model.expects(:from_yaml) - @searcher.save(@request) - end - - it "should return the resulting deserialized Model instance" do - @searcher.save(@request).should == @instance - end - - it "should return nil when deserialized model instance is nil" do - @model.stubs(:from_yaml).returns(nil) - @searcher.save(@request).should be_nil - end - - it "should generate an error when result data deserializes improperly" do - @model.stubs(:from_yaml).raises(ArgumentError) - lambda { @searcher.save(@request) }.should raise_error(ArgumentError) - end - - it "should generate an error when result data specifies an error" do - @searcher.stubs(:network_put).returns(RuntimeError.new("bogus").to_yaml) - lambda { @searcher.save(@request) }.should raise_error(RuntimeError) - end + before :each do + @result = { :foo => 'bar'}.to_yaml + @searcher.stubs(:network_put).returns(@result) # neuter the network connection + @model.stubs(:from_yaml).returns(@instance) + + @request = stub 'request', :instance => @instance + end + + it "should save the model instance over the network" do + @searcher.expects(:network_put).returns(@result) + @searcher.save(@request) + end + + it "should save the model instance using the named indirection" do + @searcher.expects(:network_put).with do |path, data| + path =~ %r{^#{@indirection.name.to_s}/} and + data == @instance.to_yaml + end.returns(@result) + @searcher.save(@request) + end + + it "should deserialize result data to a Model instance" do + @model.expects(:from_yaml) + @searcher.save(@request) + end + + it "should return the resulting deserialized Model instance" do + @searcher.save(@request).should == @instance + end + + it "should return nil when deserialized model instance is nil" do + @model.stubs(:from_yaml).returns(nil) + @searcher.save(@request).should be_nil + end + + it "should generate an error when result data deserializes improperly" do + @model.stubs(:from_yaml).raises(ArgumentError) + lambda { @searcher.save(@request) }.should raise_error(ArgumentError) + end + + it "should generate an error when result data specifies an error" do + @searcher.stubs(:network_put).returns(RuntimeError.new("bogus").to_yaml) + lambda { @searcher.save(@request) }.should raise_error(RuntimeError) + end end end diff --git a/spec/unit/indirector/ssl_file.rb b/spec/unit/indirector/ssl_file.rb new file mode 100755 index 000000000..864ba1420 --- /dev/null +++ b/spec/unit/indirector/ssl_file.rb @@ -0,0 +1,244 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2008-3-10. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/indirector/ssl_file' + +describe Puppet::Indirector::SslFile do + before do + @indirection = stub 'indirection', :name => :testing + Puppet::Indirector::Indirection.expects(:instance).with(:testing).returns(@indirection) + @file_class = Class.new(Puppet::Indirector::SslFile) do + def self.to_s + "Testing::Mytype" + end + end + + @setting = :mydir + @file_class.store_in @setting + @path = "/my/directory" + Puppet.settings.stubs(:value).returns "stubbed_setting" + Puppet.settings.stubs(:value).with(@setting).returns(@path) + Puppet.settings.stubs(:value).with(:trace).returns(false) + end + + it "should use :main and :ssl upon initialization" do + Puppet.settings.expects(:use).with(:main, :ssl) + @file_class.new + end + + it "should return a nil collection directory if no directory setting has been provided" do + @file_class.store_in nil + @file_class.collection_directory.should be_nil + end + + it "should return a nil file location if no location has been provided" do + @file_class.store_at nil + @file_class.file_location.should be_nil + end + + it "should fail if no store directory or file location has been set" do + @file_class.store_in nil + @file_class.store_at nil + lambda { @file_class.new }.should raise_error(Puppet::DevError) + end + + describe "when managing ssl files" do + before do + Puppet.settings.stubs(:use) + @searcher = @file_class.new + + @cert = stub 'certificate', :name => "myname" + @certpath = File.join(@path, "myname" + ".pem") + + @request = stub 'request', :key => @cert.name, :instance => @cert + end + + it "should consider the file a ca file if the name is equal to what the SSL::Host class says is the CA name" do + Puppet::SSL::Host.expects(:ca_name).returns "amaca" + @searcher.should be_ca("amaca") + end + + describe "when choosing the location for certificates" do + it "should set them at the ca setting's path if a ca setting is available and the name resolves to the CA name" do + @file_class.store_in nil + @file_class.store_at :mysetting + @file_class.store_ca_at :casetting + + Puppet.settings.stubs(:value).with(:casetting).returns "/ca/file" + + @searcher.expects(:ca?).with(@cert.name).returns true + @searcher.path(@cert.name).should == "/ca/file" + end + + it "should set them at the file location if a file setting is available" do + @file_class.store_in nil + @file_class.store_at :mysetting + + Puppet.settings.stubs(:value).with(:mysetting).returns "/some/file" + + @searcher.path(@cert.name).should == "/some/file" + end + + it "should set them in the setting directory, with the certificate name plus '.pem', if a directory setting is available" do + @searcher.path(@cert.name).should == @certpath + end + end + + describe "when finding certificates on disk" do + describe "and no certificate is present" do + before do + FileTest.expects(:exist?).with(@certpath).returns false + end + + it "should return nil" do + @searcher.find(@request).should be_nil + end + end + + describe "and a certificate is present" do + before do + FileTest.expects(:exist?).with(@certpath).returns true + end + + it "should return an instance of the model, which it should use to read the certificate" do + cert = mock 'cert' + model = mock 'model' + @file_class.stubs(:model).returns model + + model.expects(:new).with("myname").returns cert + cert.expects(:read).with(@certpath) + @searcher.find(@request).should equal(cert) + end + end + end + + describe "when saving certificates to disk" do + before do + FileTest.stubs(:directory?).returns true + FileTest.stubs(:writable?).returns true + end + + it "should fail if the directory is absent" do + FileTest.expects(:directory?).with(File.dirname(@certpath)).returns false + lambda { @searcher.save(@request) }.should raise_error(Puppet::Error) + end + + it "should fail if the directory is not writeable" do + FileTest.stubs(:directory?).returns true + FileTest.expects(:writable?).with(File.dirname(@certpath)).returns false + lambda { @searcher.save(@request) }.should raise_error(Puppet::Error) + end + + it "should save to the path the output of converting the certificate to a string" do + fh = mock 'filehandle' + fh.expects(:print).with("mycert") + + @searcher.stubs(:write).yields fh + @cert.expects(:to_s).returns "mycert" + + @searcher.save(@request) + end + + describe "and a directory setting is set" do + it "should open the file in write mode" do + @searcher.class.store_in @setting + fh = mock 'filehandle' + fh.stubs :print + File.expects(:open).with(@certpath, "w").yields(fh) + + @searcher.save(@request) + end + end + + describe "and a file location is set" do + it "should use the filehandle provided by the Settings" do + @searcher.class.store_at @setting + + fh = mock 'filehandle' + fh.stubs :print + Puppet.settings.expects(:write).with(@setting).yields fh + @searcher.save(@request) + end + end + + describe "and the name is the CA name and a ca setting is set" do + it "should use the filehandle provided by the Settings" do + @searcher.class.store_at @setting + @searcher.class.store_ca_at :castuff + + fh = mock 'filehandle' + fh.stubs :print + Puppet.settings.expects(:write).with(:castuff).yields fh + @searcher.stubs(:ca?).returns true + @searcher.save(@request) + end + end + end + + describe "when destroying certificates" do + describe "that do not exist" do + before do + FileTest.expects(:exist?).with(@certpath).returns false + end + + it "should return false" do + @searcher.destroy(@request).should be_false + end + end + + describe "that exist" do + before do + FileTest.expects(:exist?).with(@certpath).returns true + end + + it "should unlink the certificate file" do + File.expects(:unlink).with(@certpath) + @searcher.destroy(@request) + end + end + end + + describe "when searching for certificates" do + before do + @model = mock 'model' + @file_class.stubs(:model).returns @model + end + it "should return a certificate instance for all files that exist" do + Dir.expects(:entries).with(@path).returns %w{one.pem two.pem} + + one = stub 'one', :read => nil + two = stub 'two', :read => nil + + @model.expects(:new).with("one").returns one + @model.expects(:new).with("two").returns two + + @searcher.search(@request).should == [one, two] + end + + it "should read each certificate in using the model's :read method" do + Dir.expects(:entries).with(@path).returns %w{one.pem} + + one = stub 'one' + one.expects(:read).with(File.join(@path, "one.pem")) + + @model.expects(:new).with("one").returns one + + @searcher.search(@request) + end + + it "should skip any files that do not match /\.pem$/" do + Dir.expects(:entries).with(@path).returns %w{. .. one.pem} + + one = stub 'one', :read => nil + + @model.expects(:new).with("one").returns one + + @searcher.search(@request) + end + end + end +end diff --git a/spec/unit/indirector/ssl_rsa/file.rb b/spec/unit/indirector/ssl_rsa/file.rb deleted file mode 100755 index 76e5e3a94..000000000 --- a/spec/unit/indirector/ssl_rsa/file.rb +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env ruby -# -# Created by Luke Kanies on 2007-9-22. -# Copyright (c) 2007. All rights reserved. - -require File.dirname(__FILE__) + '/../../../spec_helper' - -require 'puppet/sslcertificates/monkey_patch' -require 'puppet/indirector/ssl_rsa/file' - - -describe Puppet::Indirector::SslRsa::File do - - it "should be a subclass of the File terminus class" do - Puppet::Indirector::SslRsa::File.superclass.should equal(Puppet::Indirector::File) - end - - it "should have documentation" do - Puppet::Indirector::SslRsa::File.doc.should be_instance_of(String) - end -end - -describe Puppet::Indirector::SslRsa::File, " when choosing a path for a ca key" do - before do - @file = Puppet::Indirector::SslRsa::File.new - @name = :ca - end - - it "should use the cadir" do - Puppet.settings.stubs(:value).with(:cadir).returns("/dir") - @file.path(@name).should =~ /^\/dir/ - end - - it "should use 'ca_key.pem' as the file name" do - @file.path(@name).should =~ /ca_key\.pem$/ - end -end - -describe Puppet::Indirector::SslRsa::File, " when choosing a path for a non-ca key" do - before do - @file = Puppet::Indirector::SslRsa::File.new - @name = :publickey - end - - it "should use the publickeydir" do - Puppet.settings.stubs(:value).with(:publickeydir).returns("/dir") - @file.path(@name).should =~ /^\/dir/ - end - - it "should use the key name with the pem file extension" do - @file.path(@name).should =~ /#{@name}\.pem$/ - end -end - -describe Puppet::Indirector::SslRsa::File, " when saving" do - before do - @file = Puppet::Indirector::SslRsa::File.new - - Puppet.settings.stubs(:value).with(:publickeydir).returns("/dir") - @key = stub "key", :name => "foo" - end - - it "should store the rsa key to disk in pem format" do - @key.expects(:to_pem).returns(:data) - @path = "/dir/foo.pem" - filehandle = mock "filehandle" - File.expects(:open).with(@path, "w").yields(filehandle) - filehandle.expects(:print).with(:data) - @file.save(@key) - end -end - -describe Puppet::Indirector::SslRsa::File, " when finding a key by name" do - before do - @file = Puppet::Indirector::SslRsa::File.new - - Puppet.settings.stubs(:value).with(:publickeydir).returns("/dir") - @name = "foo" - end - - it "should return the key as a key object on success" do - @path = "/dir/foo.pem" - FileTest.stubs(:exists?).with(@path).returns(true) - File.stubs(:read).with(@path).returns(:data) - OpenSSL::PKey::RSA.expects(:new).with(:data).returns(:mykey) - @file.find(@name).should == :mykey - end - - it "should return 'nil' on failure" do - @path = "/dir/foo.pem" - FileTest.stubs(:exists?).with(@path).returns(false) - @file.find(@name).should == nil - end -end - -describe Puppet::Indirector::SslRsa::File, " when removing a key" do - before do - @file = Puppet::Indirector::SslRsa::File.new - - Puppet.settings.stubs(:value).with(:publickeydir).returns("/dir") - @name = "foo" - end - - it "should remove the key from disk and return true" do - @path = "/dir/foo.pem" - FileTest.stubs(:exists?).with(@path).returns(true) - File.stubs(:unlink).with(@path).returns(true) - @file.destroy(@name).should == true - end - - it "should return an exception on failure" do - @path = "/dir/foo.pem" - FileTest.stubs(:exists?).with(@path).returns(false) - @file.destroy(@name).should == nil - end -end diff --git a/spec/unit/network/http/mongrel.rb b/spec/unit/network/http/mongrel.rb index ccfca2f55..1f87fd943 100644 --- a/spec/unit/network/http/mongrel.rb +++ b/spec/unit/network/http/mongrel.rb @@ -23,7 +23,14 @@ describe Puppet::Network::HTTP::Mongrel, "when turning on listening" do @mock_mongrel.stubs(:run) @mock_mongrel.stubs(:register) Mongrel::HttpServer.stubs(:new).returns(@mock_mongrel) - @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :catalog ], :protocols => [ :rest ] } + + @mock_puppet_mongrel = mock('puppet_mongrel') + Puppet::Network::HTTPServer::Mongrel.stubs(:new).returns(@mock_puppet_mongrel) + + @listen_params = { :address => "127.0.0.1", :port => 31337, + :handlers => [ :node, :catalog ], :protocols => [ :rest, :xmlrpc ], + :xmlrpc_handlers => [ :status, :fileserver ] + } end it "should fail if already listening" do @@ -63,18 +70,38 @@ describe Puppet::Network::HTTP::Mongrel, "when turning on listening" do @server.should be_listening end - it "should instantiate a handler for each protocol+handler pair to configure web server routing" do - @listen_params[:protocols].each do |protocol| - @listen_params[:handlers].each do |handler| - @mock_mongrel.expects(:register) + describe "when providing REST services" do + it "should instantiate a handler for each protocol+handler pair to configure web server routing" do + @listen_params[:protocols].each do |protocol| + @listen_params[:handlers].each do |handler| + @mock_mongrel.expects(:register) + end end + @server.listen(@listen_params) + end + + it "should use a Mongrel + REST class to configure Mongrel when REST services are requested" do + @server.expects(:class_for_protocol).with(:rest).at_least_once.returns(Puppet::Network::HTTP::MongrelREST) + @server.listen(@listen_params) end - @server.listen(@listen_params) end - - it "should use a Mongrel + REST class to configure Mongrel when REST services are requested" do - @server.expects(:class_for_protocol).with(:rest).at_least_once.returns(Puppet::Network::HTTP::MongrelREST) - @server.listen(@listen_params) + + describe "when providing XMLRPC services" do + it "should do nothing if no xmlrpc handlers have been provided" do + Puppet::Network::HTTPServer::Mongrel.expects(:new).never + @server.listen(@listen_params.merge(:xmlrpc_handlers => [])) + end + + it "should create an instance of the existing Mongrel http server with the right handlers" do + Puppet::Network::HTTPServer::Mongrel.expects(:new).with([:status, :master]).returns(@mock_puppet_mongrel) + @server.listen(@listen_params.merge(:xmlrpc_handlers => [:status, :master])) + end + + it "should register the Mongrel server instance at /RPC2" do + @mock_mongrel.expects(:register).with("/RPC2", @mock_puppet_mongrel) + + @server.listen(@listen_params.merge(:xmlrpc_handlers => [:status, :master])) + end end it "should fail if services from an unknown protocol are requested" do diff --git a/spec/unit/network/http/webrick.rb b/spec/unit/network/http/webrick.rb index 05ed2f0e2..6d006992c 100644 --- a/spec/unit/network/http/webrick.rb +++ b/spec/unit/network/http/webrick.rb @@ -18,18 +18,21 @@ describe Puppet::Network::HTTP::WEBrick, "when turning on listening" do [:mount, :start, :shutdown].each {|meth| @mock_webrick.stubs(meth)} WEBrick::HTTPServer.stubs(:new).returns(@mock_webrick) @server = Puppet::Network::HTTP::WEBrick.new - @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :catalog ], :protocols => [ :rest ] } + [:setup_logger, :setup_ssl].each {|meth| @server.stubs(meth).returns({})} # the empty hash is required because of how we're merging + @listen_params = { :address => "127.0.0.1", :port => 31337, + :handlers => [ :node, :catalog ], :xmlrpc_handlers => [], :protocols => [ :rest ] + } end - + it "should fail if already listening" do @server.listen(@listen_params) Proc.new { @server.listen(@listen_params) }.should raise_error(RuntimeError) end - + it "should require at least one handler" do Proc.new { @server.listen(@listen_params.delete_if {|k,v| :handlers == k}) }.should raise_error(ArgumentError) end - + it "should require at least one protocol" do Proc.new { @server.listen(@listen_params.delete_if {|k,v| :protocols == k}) }.should raise_error(ArgumentError) end @@ -37,7 +40,7 @@ describe Puppet::Network::HTTP::WEBrick, "when turning on listening" do it "should require a listening address to be specified" do Proc.new { @server.listen(@listen_params.delete_if {|k,v| :address == k})}.should raise_error(ArgumentError) end - + it "should require a listening port to be specified" do Proc.new { @server.listen(@listen_params.delete_if {|k,v| :port == k})}.should raise_error(ArgumentError) end @@ -46,35 +49,107 @@ describe Puppet::Network::HTTP::WEBrick, "when turning on listening" do @mock_webrick.expects(:start) @server.listen(@listen_params) end - + it "should tell webrick to listen on the specified address and port" do WEBrick::HTTPServer.expects(:new).with {|args| args[:Port] == 31337 and args[:BindAddress] == "127.0.0.1" }.returns(@mock_webrick) @server.listen(@listen_params) end - + + it "should configure a logger for webrick" do + @server.expects(:setup_logger).returns(:Logger => :mylogger) + + WEBrick::HTTPServer.expects(:new).with {|args| + args[:Logger] == :mylogger + }.returns(@mock_webrick) + + @server.listen(@listen_params) + end + + it "should configure SSL for webrick" do + @server.expects(:setup_ssl).returns(:Ssl => :testing, :Other => :yay) + + WEBrick::HTTPServer.expects(:new).with {|args| + args[:Ssl] == :testing and args[:Other] == :yay + }.returns(@mock_webrick) + + @server.listen(@listen_params) + end + it "should be listening" do @server.listen(@listen_params) @server.should be_listening end - - it "should instantiate a handler for each protocol+handler pair to configure web server routing" do - @listen_params[:protocols].each do |protocol| - mock_handler = mock("handler instance for [#{protocol}]") - mock_handler_class = mock("handler class for [#{protocol}]") - @listen_params[:handlers].each do |handler| - @mock_webrick.expects(:mount) + + describe "when the REST protocol is requested" do + it "should use a WEBrick + REST class to configure WEBrick" do + Puppet::Network::HTTP::WEBrick.expects(:class_for_protocol).with(:rest).at_least_once + @server.listen(@listen_params.merge(:protocols => [:rest])) + end + + it "should instantiate a handler for each protocol+handler pair to configure web server routing" do + @listen_params[:protocols].each do |protocol| + @listen_params[:handlers].each do |handler| + @mock_webrick.expects(:mount) + end end + @server.listen(@listen_params) end - @server.listen(@listen_params) end - it "should use a WEBrick + REST class to configure WEBrick when REST services are requested" do - Puppet::Network::HTTP::WEBrick.expects(:class_for_protocol).with(:rest).at_least_once - @server.listen(@listen_params.merge(:protocols => [:rest])) + describe "when the XMLRPC protocol is requested" do + before do + @servlet = mock 'servlet' + + Puppet::Network::XMLRPC::WEBrickServlet.stubs(:new).returns @servlet + + @master_handler = mock('master_handler') + @file_handler = mock('file_handler') + + @master = mock 'master' + @file = mock 'file' + @master_handler.stubs(:new).returns @master + @file_handler.stubs(:new).returns @file + + Puppet::Network::Handler.stubs(:handler).with(:master).returns @master_handler + Puppet::Network::Handler.stubs(:handler).with(:fileserver).returns @file_handler + end + + it "should do nothing if no xmlrpc handlers have been specified" do + Puppet::Network::Handler.expects(:handler).never + + @server.listen(@listen_params.merge(:protocols => [:xmlrpc], :xmlrpc_handlers => [])) + end + + it "should look the handler classes up via their base class" do + Puppet::Network::Handler.expects(:handler).with(:master).returns @master_handler + Puppet::Network::Handler.expects(:handler).with(:fileserver).returns @file_handler + + @server.listen(@listen_params.merge(:protocols => [:xmlrpc], :xmlrpc_handlers => [:master, :fileserver])) + end + + it "should create an instance for each requested xmlrpc handler" do + @master_handler.expects(:new).returns @master + @file_handler.expects(:new).returns @file + + @server.listen(@listen_params.merge(:protocols => [:xmlrpc], :xmlrpc_handlers => [:master, :fileserver])) + end + + it "should create a webrick servlet with the xmlrpc handler instances" do + Puppet::Network::XMLRPC::WEBrickServlet.expects(:new).with([@master, @file]).returns @servlet + + @server.listen(@listen_params.merge(:protocols => [:xmlrpc], :xmlrpc_handlers => [:master, :fileserver])) + end + + it "should mount the webrick servlet at /RPC2" do + @mock_webrick.stubs(:mount) + @mock_webrick.expects(:mount).with("/RPC2", @servlet) + + @server.listen(@listen_params.merge(:protocols => [:xmlrpc], :xmlrpc_handlers => [:master, :fileserver])) + end end - + it "should fail if services from an unknown protocol are requested" do Proc.new { @server.listen(@listen_params.merge(:protocols => [ :foo ]))}.should raise_error end @@ -82,21 +157,21 @@ end describe Puppet::Network::HTTP::WEBrick, "when looking up the class to handle a protocol" do - it "should require a protocol" do - lambda { Puppet::Network::HTTP::WEBrick.class_for_protocol }.should raise_error(ArgumentError) - end - - it "should accept a protocol" do - lambda { Puppet::Network::HTTP::WEBrick.class_for_protocol("bob") }.should_not raise_error(ArgumentError) - end - - it "should use a WEBrick + REST class when a REST protocol is specified" do - Puppet::Network::HTTP::WEBrick.class_for_protocol("rest").should == Puppet::Network::HTTP::WEBrickREST - end - - it "should fail when an unknown protocol is specified" do - lambda { Puppet::Network::HTTP::WEBrick.class_for_protocol("abcdefg") }.should raise_error - end + it "should require a protocol" do + lambda { Puppet::Network::HTTP::WEBrick.class_for_protocol }.should raise_error(ArgumentError) + end + + it "should accept a protocol" do + lambda { Puppet::Network::HTTP::WEBrick.class_for_protocol("bob") }.should_not raise_error(ArgumentError) + end + + it "should use a WEBrick + REST class when a REST protocol is specified" do + Puppet::Network::HTTP::WEBrick.class_for_protocol("rest").should == Puppet::Network::HTTP::WEBrickREST + end + + it "should fail when an unknown protocol is specified" do + lambda { Puppet::Network::HTTP::WEBrick.class_for_protocol("abcdefg") }.should raise_error + end end describe Puppet::Network::HTTP::WEBrick, "when turning off listening" do @@ -105,22 +180,182 @@ describe Puppet::Network::HTTP::WEBrick, "when turning off listening" do [:mount, :start, :shutdown].each {|meth| @mock_webrick.stubs(meth)} WEBrick::HTTPServer.stubs(:new).returns(@mock_webrick) @server = Puppet::Network::HTTP::WEBrick.new + [:setup_logger, :setup_ssl].each {|meth| @server.stubs(meth).returns({})} # the empty hash is required because of how we're merging @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :catalog ], :protocols => [ :rest ] } end - + it "should fail unless listening" do Proc.new { @server.unlisten }.should raise_error(RuntimeError) end - + it "should order webrick server to stop" do @mock_webrick.expects(:shutdown) @server.listen(@listen_params) @server.unlisten end - + it "should no longer be listening" do @server.listen(@listen_params) @server.unlisten @server.should_not be_listening end end + +describe Puppet::Network::HTTP::WEBrick do + before do + @mock_webrick = stub('webrick', :[] => {}) + [:mount, :start, :shutdown].each {|meth| @mock_webrick.stubs(meth)} + WEBrick::HTTPServer.stubs(:new).returns(@mock_webrick) + @server = Puppet::Network::HTTP::WEBrick.new + end + + describe "when configuring an http logger" do + before do + Puppet.settings.stubs(:value).returns "something" + Puppet.settings.stubs(:use) + @filehandle = stub 'handle', :fcntl => nil, :sync => nil + + File.stubs(:open).returns @filehandle + end + + it "should use the settings for :main, :ssl, and the process name" do + Puppet.settings.stubs(:value).with(:name).returns "myname" + Puppet.settings.expects(:use).with(:main, :ssl, "myname") + + @server.setup_logger + end + + it "should use the masterlog if the process name is 'puppetmasterd'" do + Puppet.settings.stubs(:value).with(:name).returns "puppetmasterd" + Puppet.settings.expects(:value).with(:masterhttplog).returns "/master/log" + + File.expects(:open).with("/master/log", "a+").returns @filehandle + + @server.setup_logger + end + + it "should use the httplog if the process name is not 'puppetmasterd'" do + Puppet.settings.stubs(:value).with(:name).returns "other" + Puppet.settings.expects(:value).with(:httplog).returns "/other/log" + + File.expects(:open).with("/other/log", "a+").returns @filehandle + + @server.setup_logger + end + + describe "and creating the logging filehandle" do + it "should set fcntl to 'Fcntl::F_SETFD, Fcntl::FD_CLOEXEC'" do + @filehandle.expects(:fcntl).with(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) + + @server.setup_logger + end + + it "should sync the filehandle" do + @filehandle.expects(:sync) + + @server.setup_logger + end + end + + it "should create a new WEBrick::Log instance with the open filehandle" do + WEBrick::Log.expects(:new).with(@filehandle) + + @server.setup_logger + end + + it "should set debugging if the current loglevel is :debug" do + Puppet::Util::Log.expects(:level).returns :debug + + WEBrick::Log.expects(:new).with { |handle, debug| debug == WEBrick::Log::DEBUG } + + @server.setup_logger + end + + it "should return the logger as the main log" do + logger = mock 'logger' + WEBrick::Log.expects(:new).returns logger + + @server.setup_logger[:Logger].should == logger + end + + it "should return the logger as the access log using both the Common and Referer log format" do + logger = mock 'logger' + WEBrick::Log.expects(:new).returns logger + + @server.setup_logger[:AccessLog].should == [ + [logger, WEBrick::AccessLog::COMMON_LOG_FORMAT], + [logger, WEBrick::AccessLog::REFERER_LOG_FORMAT] + ] + end + end + + describe "when configuring ssl" do + before do + @key = stub 'key', :content => "mykey" + @cert = stub 'cert', :content => "mycert" + @host = stub 'host', :key => @key, :certificate => @cert, :name => "yay", :ssl_store => "mystore" + + Puppet::SSL::Certificate.stubs(:find).with('ca').returns @cert + + Puppet::SSL::Host.stubs(:new).returns @host + end + + it "should use the key from an SSL::Host instance created with the default name" do + Puppet::SSL::Host.expects(:new).returns @host + @host.expects(:key).returns @key + + @server.setup_ssl[:SSLPrivateKey].should == "mykey" + end + + it "should generate its files if no certificate can be found" do + @host.expects(:certificate).times(2).returns(nil).then.returns(@cert) + + @host.expects(:generate) + + @server.setup_ssl + end + + it "should configure the certificate" do + @server.setup_ssl[:SSLCertificate].should == "mycert" + end + + it "should fail if no CA certificate can be found" do + Puppet::SSL::Certificate.stubs(:find).with('ca').returns nil + + lambda { @server.setup_ssl }.should raise_error(Puppet::Error) + end + + it "should specify the path to the CA certificate" do + Puppet.settings.stubs(:value).returns "whatever" + Puppet.settings.stubs(:value).with(:hostcrl).returns 'false' + Puppet.settings.stubs(:value).with(:localcacert).returns '/ca/crt' + + @server.setup_ssl[:SSLCACertificateFile].should == "/ca/crt" + end + + it "should start ssl immediately" do + @server.setup_ssl[:SSLStartImmediately].should be_true + end + + it "should enable ssl" do + @server.setup_ssl[:SSLEnable].should be_true + end + + it "should configure the verification method as 'OpenSSL::SSL::VERIFY_PEER'" do + @server.setup_ssl[:SSLVerifyClient].should == OpenSSL::SSL::VERIFY_PEER + end + + it "should add an x509 store" do + Puppet.settings.stubs(:value).returns "whatever" + Puppet.settings.stubs(:value).with(:hostcrl).returns '/my/crl' + + @host.expects(:ssl_store).returns "mystore" + + @server.setup_ssl[:SSLCertificateStore].should == "mystore" + end + + it "should set the certificate name to 'nil'" do + @server.setup_ssl[:SSLCertName].should be_nil + end + end +end diff --git a/spec/unit/network/http_pool.rb b/spec/unit/network/http_pool.rb index 3c52c8613..dd8bed54c 100755 --- a/spec/unit/network/http_pool.rb +++ b/spec/unit/network/http_pool.rb @@ -6,105 +6,28 @@ require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/network/http_pool' -describe Puppet::Network::HttpPool, " when adding certificate information to http instances" do - before do - @http = mock 'http' - [:cert_store=, :verify_mode=, :ca_file=, :cert=, :key=].each { |m| @http.stubs(m) } - @store = stub 'store' - [:add_file,:purpose=].each { |m| @store.stubs(m) } +describe Puppet::Network::HttpPool do + after do + Puppet::Util::Cacher.invalidate + Puppet::Network::HttpPool.clear_http_instances + Puppet::Network::HttpPool.instance_variable_set("@ssl_host", nil) end it "should have keep-alive disabled" do Puppet::Network::HttpPool::HTTP_KEEP_ALIVE.should be_false end - it "should do nothing if no certificate is available" do - Puppet::Network::HttpPool.expects(:read_cert).returns(false) - @http.expects(:cert=).never - Puppet::Network::HttpPool.cert_setup(@http) - end - - it "should add a certificate store" do - Puppet::Network::HttpPool.stubs(:read_cert).returns(true) - Puppet::Network::HttpPool.stubs(:key).returns(:mykey) - OpenSSL::X509::Store.expects(:new).returns(@store) - @http.expects(:cert_store=).with(@store) - - Puppet::Network::HttpPool.cert_setup(@http) - end - - it "should add the local CA cert to the certificate store" do - Puppet::Network::HttpPool.stubs(:read_cert).returns(true) - OpenSSL::X509::Store.expects(:new).returns(@store) - Puppet.settings.stubs(:value).with(:localcacert).returns("/some/file") - Puppet.settings.stubs(:value).with(:localcacert).returns("/some/file") - @store.expects(:add_file).with("/some/file") - - Puppet::Network::HttpPool.stubs(:key).returns(:whatever) - - Puppet::Network::HttpPool.cert_setup(@http) - end - - it "should set the purpose of the cert store to OpenSSL::X509::PURPOSE_SSL_CLIENT" do - Puppet::Network::HttpPool.stubs(:read_cert).returns(true) - Puppet::Network::HttpPool.stubs(:key).returns(:mykey) - OpenSSL::X509::Store.expects(:new).returns(@store) - - @store.expects(:purpose=).with(OpenSSL::X509::PURPOSE_SSL_CLIENT) - - Puppet::Network::HttpPool.cert_setup(@http) - end - - it "should add the client certificate" do - Puppet::Network::HttpPool.stubs(:read_cert).returns(true) - Puppet::Network::HttpPool.stubs(:cert).returns(:mycert) - Puppet::Network::HttpPool.stubs(:key).returns(:mykey) - OpenSSL::X509::Store.expects(:new).returns(@store) - - @http.expects(:cert=).with(:mycert) - - Puppet::Network::HttpPool.cert_setup(@http) - end - - it "should add the client key" do - Puppet::Network::HttpPool.stubs(:read_cert).returns(true) - Puppet::Network::HttpPool.stubs(:key).returns(:mykey) - OpenSSL::X509::Store.expects(:new).returns(@store) - - @http.expects(:key=).with(:mykey) - - Puppet::Network::HttpPool.cert_setup(@http) + it "should use an SSL::Host instance to get its certificate information" do + host = mock 'host' + Puppet::SSL::Host.expects(:new).with().returns host + Puppet::Network::HttpPool.ssl_host.should equal(host) end - it "should set the verify mode to OpenSSL::SSL::VERIFY_PEER" do - Puppet::Network::HttpPool.stubs(:read_cert).returns(true) - Puppet::Network::HttpPool.stubs(:key).returns(:mykey) - OpenSSL::X509::Store.expects(:new).returns(@store) - - @http.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER) - - Puppet::Network::HttpPool.cert_setup(@http) - end - - it "should set the ca file" do - Puppet::Network::HttpPool.stubs(:read_cert).returns(true) - Puppet.settings.stubs(:value).with(:localcacert).returns("/some/file") - OpenSSL::X509::Store.expects(:new).returns(@store) - - @http.expects(:ca_file=).with("/some/file") - - Puppet::Network::HttpPool.stubs(:key).returns(:whatever) - - Puppet::Network::HttpPool.cert_setup(@http) - end - - it "should set up certificate information when creating http instances" do - Puppet::Network::HttpPool.expects(:cert_setup).with { |i| i.is_a?(Net::HTTP) } - Puppet::Network::HttpPool.http_instance("one", "two") - end - - after do - Puppet::Network::HttpPool.clear_http_instances + it "should reuse the same host instance" do + host = mock 'host' + Puppet::SSL::Host.expects(:new).with().once.returns host + Puppet::Network::HttpPool.ssl_host.should equal(host) + Puppet::Network::HttpPool.ssl_host.should equal(host) end describe "when managing http instances" do @@ -115,7 +38,7 @@ describe Puppet::Network::HttpPool, " when adding certificate information to htt end before do - # All of hte cert stuff is tested elsewhere + # All of the cert stuff is tested elsewhere Puppet::Network::HttpPool.stubs(:cert_setup) end @@ -152,7 +75,7 @@ describe Puppet::Network::HttpPool, " when adding certificate information to htt Puppet::Network::HttpPool.http_instance("me", 54321).open_timeout.should == 120 end - describe "when http keep-alive is enabled" do + describe "and http keep-alive is enabled" do before do Puppet::Network::HttpPool.stubs(:keep_alive?).returns true end @@ -201,9 +124,19 @@ describe Puppet::Network::HttpPool, " when adding certificate information to htt one.expects(:finish).never Puppet::Network::HttpPool.clear_http_instances end + + it "should reset its ssl host when clearing the cache" do + stub_settings :http_proxy_host => "myhost", :http_proxy_port => 432, :configtimeout => 120, :http_enable_post_connection_check => true, :certname => "a" + one = Puppet::Network::HttpPool.http_instance("me", 54321) + one.expects(:started?).returns(false) + one.expects(:finish).never + id = Puppet::Network::HttpPool.ssl_host.object_id + Puppet::Network::HttpPool.clear_http_instances + Puppet::Network::HttpPool.ssl_host.object_id.should_not == id + end end - describe "when http keep-alive is disabled" do + describe "and http keep-alive is disabled" do before do Puppet::Network::HttpPool.stubs(:keep_alive?).returns false end @@ -215,26 +148,73 @@ describe Puppet::Network::HttpPool, " when adding certificate information to htt end end - # We mostly have to do this for testing, since in real life people - # won't change certs within a single process. - it "should remove its loaded certificate when clearing the cache" do - Puppet::Network::HttpPool.instance_variable_set("@cert", :yay) + after do Puppet::Network::HttpPool.clear_http_instances - # Can't use the accessor, because it will read the cert in - Puppet::Network::HttpPool.instance_variable_get("@cert").should be_nil end + end - # We mostly have to do this for testing, since in real life people - # won't change certs within a single process. - it "should remove its loaded key when clearing the cache" do - Puppet::Network::HttpPool.instance_variable_set("@key", :yay) - Puppet::Network::HttpPool.clear_http_instances - # Can't use the accessor, because it will read the cert in - Puppet::Network::HttpPool.instance_variable_get("@key").should be_nil + describe "when adding certificate information to http instances" do + before do + @http = mock 'http' + [:cert_store=, :verify_mode=, :ca_file=, :cert=, :key=].each { |m| @http.stubs(m) } + @store = stub 'store' + + @cert = stub 'cert', :content => "real_cert" + @key = stub 'key', :content => "real_key" + @host = stub 'host', :certificate => @cert, :key => @key, :ssl_store => @store + + Puppet[:confdir] = "/sometthing/else" + Puppet.settings.stubs(:value).returns "/some/file" + Puppet.settings.stubs(:value).with(:hostcert).returns "/host/cert" + + FileTest.stubs(:exist?).with("/host/cert").returns true + + Puppet::Network::HttpPool.stubs(:ssl_host).returns @host end - after do - Puppet::Network::HttpPool.clear_http_instances + it "should do nothing if no certificate is on disk" do + FileTest.expects(:exist?).with("/host/cert").returns false + @http.expects(:cert=).never + Puppet::Network::HttpPool.cert_setup(@http) + end + + it "should add a certificate store from the ssl host" do + @http.expects(:cert_store=).with(@store) + + Puppet::Network::HttpPool.cert_setup(@http) + end + + it "should add the client certificate" do + @http.expects(:cert=).with("real_cert") + + Puppet::Network::HttpPool.cert_setup(@http) + end + + it "should add the client key" do + @http.expects(:key=).with("real_key") + + Puppet::Network::HttpPool.cert_setup(@http) + end + + it "should set the verify mode to OpenSSL::SSL::VERIFY_PEER" do + @http.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER) + + Puppet::Network::HttpPool.cert_setup(@http) + end + + it "should set the ca file" do + Puppet.settings.stubs(:value).returns "/some/file" + FileTest.stubs(:exist?).with(Puppet[:hostcert]).returns true + + Puppet.settings.stubs(:value).with(:localcacert).returns "/ca/cert/file" + @http.expects(:ca_file=).with("/ca/cert/file") + + Puppet::Network::HttpPool.cert_setup(@http) + end + + it "should set up certificate information when creating http instances" do + Puppet::Network::HttpPool.expects(:cert_setup).with { |i| i.is_a?(Net::HTTP) } + Puppet::Network::HttpPool.http_instance("one", "two") end end end diff --git a/spec/unit/network/server.rb b/spec/unit/network/server.rb index 4e47c22fd..5332964c6 100644 --- a/spec/unit/network/server.rb +++ b/spec/unit/network/server.rb @@ -6,311 +6,527 @@ require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/network/server' -describe Puppet::Network::Server, "when initializing" do +describe Puppet::Network::Server do before do @mock_http_server_class = mock('http server class') - Puppet.stubs(:[]).with(:servertype).returns(:suparserver) + Puppet.settings.stubs(:use) + Puppet.settings.stubs(:value).with(:name).returns("me") + Puppet.settings.stubs(:value).with(:servertype).returns(:suparserver) Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class) - end - - it "should allow specifying a listening address" do - Puppet.stubs(:[]).with(:masterport).returns('') - @server = Puppet::Network::Server.new(:address => "127.0.0.1") - @server.address.should == "127.0.0.1" + Puppet.settings.stubs(:value).with(:servertype).returns(:suparserver) + @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) end - it "should allow specifying a listening port" do - Puppet.stubs(:[]).with(:bindaddress).returns('') - @server = Puppet::Network::Server.new(:port => 31337) - @server.port.should == 31337 - end - - it "should use the Puppet configurator to find a default listening address" do - Puppet.stubs(:[]).with(:masterport).returns('') - Puppet.expects(:[]).with(:bindaddress).returns("10.0.0.1") - @server = Puppet::Network::Server.new - @server.address.should == "10.0.0.1" - end + describe "when initializing" do + before do + Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') + Puppet::Network::Handler.stubs(:handler).returns mock('xmlrpc_handler') + end - it "should use the Puppet configurator to find a default listening port" do - Puppet.stubs(:[]).with(:bindaddress).returns('') - Puppet.expects(:[]).with(:masterport).returns(6667) - @server = Puppet::Network::Server.new - @server.port.should == 6667 - end + it "should allow specifying a listening address" do + Puppet.settings.stubs(:value).with(:masterport).returns('') + @server = Puppet::Network::Server.new(:address => "127.0.0.1") + @server.address.should == "127.0.0.1" + end - it "should fail to initialize if no listening address can be found" do - Puppet.stubs(:[]).with(:masterport).returns(6667) - Puppet.stubs(:[]).with(:bindaddress).returns(nil) - Proc.new { Puppet::Network::Server.new }.should raise_error(ArgumentError) - end - - it "should fail to initialize if no listening port can be found" do - Puppet.stubs(:[]).with(:bindaddress).returns("127.0.0.1") - Puppet.stubs(:[]).with(:masterport).returns(nil) - Proc.new { Puppet::Network::Server.new }.should raise_error(ArgumentError) - end + it "should allow specifying a listening port" do + Puppet.settings.stubs(:value).with(:bindaddress).returns('') + @server = Puppet::Network::Server.new(:port => 31337) + @server.port.should == 31337 + end - it "should use the Puppet configurator to determine which HTTP server will be used to provide access to clients" do - Puppet.expects(:[]).with(:servertype).returns(:suparserver) - @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) - @server.server_type.should == :suparserver - end - - it "should fail to initialize if there is no HTTP server known to the Puppet configurator" do - Puppet.expects(:[]).with(:servertype).returns(nil) - Proc.new { Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) }.should raise_error - end - - it "should ask the Puppet::Network::HTTP class to fetch the proper HTTP server class" do - Puppet::Network::HTTP.expects(:server_class_by_type).with(:suparserver).returns(@mock_http_server_class) - @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) - end - - it "should fail if the HTTP server class is unknown" do - Puppet::Network::HTTP.stubs(:server_class_by_type).returns(nil) - Proc.new { Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) }.should raise_error(ArgumentError) - end - - it "should allow registering indirections" do - @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337, :handlers => [ :foo, :bar, :baz]) - Proc.new { @server.unregister(:foo, :bar, :baz) }.should_not raise_error - end - - it "should not be listening after initialization" do - Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337).should_not be_listening - end -end + it "should use the Puppet configurator to find a default listening address" do + Puppet.settings.stubs(:value).with(:masterport).returns('') + Puppet.settings.expects(:value).with(:bindaddress).returns("10.0.0.1") + @server = Puppet::Network::Server.new + @server.address.should == "10.0.0.1" + end -describe Puppet::Network::Server, "in general" do - before do - @mock_http_server_class = mock('http server class') - Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class) - Puppet.stubs(:[]).with(:servertype).returns(:suparserver) - @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) - end - - it "should allow registering an indirection for client access by specifying its indirection name" do - Proc.new { @server.register(:foo) }.should_not raise_error - end - - it "should require at least one indirection name when registering indirections for client access" do - Proc.new { @server.register }.should raise_error(ArgumentError) - end - - it "should allow for numerous indirections to be registered at once for client access" do - Proc.new { @server.register(:foo, :bar, :baz) }.should_not raise_error - end + it "should use the Puppet configurator to find a default listening port" do + Puppet.settings.stubs(:value).with(:bindaddress).returns('') + Puppet.settings.expects(:value).with(:masterport).returns(6667) + @server = Puppet::Network::Server.new + @server.port.should == 6667 + end + + it "should fail to initialize if no listening address can be found" do + Puppet.settings.stubs(:value).with(:masterport).returns(6667) + Puppet.settings.stubs(:value).with(:bindaddress).returns(nil) + lambda { Puppet::Network::Server.new }.should raise_error(ArgumentError) + end + + it "should fail to initialize if no listening port can be found" do + Puppet.settings.stubs(:value).with(:bindaddress).returns("127.0.0.1") + Puppet.settings.stubs(:value).with(:masterport).returns(nil) + lambda { Puppet::Network::Server.new }.should raise_error(ArgumentError) + end + + it "should use the Puppet configurator to determine which HTTP server will be used to provide access to clients" do + Puppet.settings.expects(:value).with(:servertype).returns(:suparserver) + @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) + @server.server_type.should == :suparserver + end - it "should allow the use of indirection names to specify which indirections are to be no longer accessible to clients" do - @server.register(:foo) - Proc.new { @server.unregister(:foo) }.should_not raise_error + it "should fail to initialize if there is no HTTP server known to the Puppet configurator" do + Puppet.settings.expects(:value).with(:servertype).returns(nil) + lambda { Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) }.should raise_error + end + + it "should ask the Puppet::Network::HTTP class to fetch the proper HTTP server class" do + Puppet::Network::HTTP.expects(:server_class_by_type).with(:suparserver).returns(@mock_http_server_class) + @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) + end + + it "should fail if the HTTP server class is unknown" do + Puppet::Network::HTTP.stubs(:server_class_by_type).returns(nil) + lambda { Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) }.should raise_error(ArgumentError) + end + + it "should allow registering REST handlers" do + @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337, :handlers => [ :foo, :bar, :baz]) + lambda { @server.unregister(:foo, :bar, :baz) }.should_not raise_error + end + + it "should allow registering XMLRPC handlers" do + @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337, :xmlrpc_handlers => [ :foo, :bar, :baz]) + lambda { @server.unregister_xmlrpc(:foo, :bar, :baz) }.should_not raise_error + end + + it "should not be listening after initialization" do + Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337).should_not be_listening + end + + it "should use the :main setting section" do + Puppet.settings.expects(:use).with { |args| args.include?(:main) } + @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337, :xmlrpc_handlers => [ :foo, :bar, :baz]) + end + + it "should use the Puppet[:name] setting section" do + Puppet.settings.expects(:value).with(:name).returns "me" + Puppet.settings.expects(:use).with { |args| args.include?("me") } + + @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337, :xmlrpc_handlers => [ :foo, :bar, :baz]) + end end - it "should leave other indirections accessible to clients when turning off indirections" do - @server.register(:foo, :bar) - @server.unregister(:foo) - Proc.new { @server.unregister(:bar)}.should_not raise_error + # We don't test the method, because it's too much of a Unix-y pain. + it "should be able to daemonize" do + @server.should respond_to(:daemonize) end - - it "should allow specifying numerous indirections which are to be no longer accessible to clients" do - @server.register(:foo, :bar) - Proc.new { @server.unregister(:foo, :bar) }.should_not raise_error + + describe "when being started" do + before do + @server.stubs(:listen) + @server.stubs(:create_pidfile) + end + + it "should listen" do + @server.expects(:listen) + @server.start + end + + it "should create its PID file" do + @server.expects(:create_pidfile) + @server.start + end end - - it "should not turn off any indirections if given unknown indirection names to turn off" do - @server.register(:foo, :bar) - Proc.new { @server.unregister(:foo, :bar, :baz) }.should raise_error(ArgumentError) - Proc.new { @server.unregister(:foo, :bar) }.should_not raise_error + + describe "when being stopped" do + before do + @server.stubs(:unlisten) + @server.stubs(:remove_pidfile) + end + + it "should unlisten" do + @server.expects(:unlisten) + @server.stop + end + + it "should remove its PID file" do + @server.expects(:remove_pidfile) + @server.stop + end end - - it "should not allow turning off unknown indirection names" do - @server.register(:foo, :bar) - Proc.new { @server.unregister(:baz) }.should raise_error(ArgumentError) + + describe "when creating its pidfile" do + it "should use an exclusive mutex" do + Puppet.settings.expects(:value).with(:name).returns "me" + + sync = mock 'sync' + Puppet::Util.expects(:sync).with("me").returns sync + + sync.expects(:synchronize).with(Sync::EX) + @server.create_pidfile + end + + it "should lock the pidfile using the Pidlock class" do + pidfile = mock 'pidfile' + + Puppet.settings.stubs(:value).with(:name).returns "eh" + Puppet.settings.expects(:value).with(:pidfile).returns "/my/file" + + Puppet::Util::Pidlock.expects(:new).with("/my/file").returns pidfile + + pidfile.expects(:lock).returns true + @server.create_pidfile + end + + it "should fail if it cannot lock" do + pidfile = mock 'pidfile' + + Puppet.settings.stubs(:value).with(:name).returns "eh" + Puppet.settings.stubs(:value).with(:pidfile).returns "/my/file" + + Puppet::Util::Pidlock.expects(:new).with("/my/file").returns pidfile + + pidfile.expects(:lock).returns false + + lambda { @server.create_pidfile }.should raise_error + end end - - it "should disable client access immediately when turning off indirections" do - @server.register(:foo, :bar) - @server.unregister(:foo) - Proc.new { @server.unregister(:foo) }.should raise_error(ArgumentError) + + describe "when removing its pidfile" do + it "should use an exclusive mutex" do + Puppet.settings.expects(:value).with(:name).returns "me" + + sync = mock 'sync' + Puppet::Util.expects(:sync).with("me").returns sync + + sync.expects(:synchronize).with(Sync::EX) + @server.remove_pidfile + end + + it "should do nothing if the pidfile is not present" do + pidfile = mock 'pidfile', :locked? => false + Puppet::Util::Pidlock.expects(:new).with("/my/file").returns pidfile + + Puppet.settings.stubs(:value).with(:name).returns "eh" + Puppet.settings.stubs(:value).with(:pidfile).returns "/my/file" + + pidfile.expects(:unlock).never + @server.remove_pidfile + end + + it "should unlock the pidfile using the Pidlock class" do + pidfile = mock 'pidfile', :locked? => true + Puppet::Util::Pidlock.expects(:new).with("/my/file").returns pidfile + pidfile.expects(:unlock).returns true + + Puppet.settings.stubs(:value).with(:name).returns "eh" + Puppet.settings.stubs(:value).with(:pidfile).returns "/my/file" + + @server.remove_pidfile + end + + it "should warn if it cannot remove the pidfile" do + pidfile = mock 'pidfile', :locked? => true + Puppet::Util::Pidlock.expects(:new).with("/my/file").returns pidfile + pidfile.expects(:unlock).returns false + + Puppet.settings.stubs(:value).with(:name).returns "eh" + Puppet.settings.stubs(:value).with(:pidfile).returns "/my/file" + + Puppet.expects :err + @server.remove_pidfile + end end - - it "should allow turning off all indirections at once" do - @server.register(:foo, :bar) - @server.unregister - [ :foo, :bar, :baz].each do |indirection| - Proc.new { @server.unregister(indirection) }.should raise_error(ArgumentError) + + describe "when managing indirection registrations" do + before do + Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') + end + + it "should allow registering an indirection for client access by specifying its indirection name" do + lambda { @server.register(:foo) }.should_not raise_error + end + + it "should require that the indirection be valid" do + Puppet::Indirector::Indirection.expects(:model).with(:foo).returns nil + lambda { @server.register(:foo) }.should raise_error(ArgumentError) + end + + it "should require at least one indirection name when registering indirections for client access" do + lambda { @server.register }.should raise_error(ArgumentError) + end + + it "should allow for numerous indirections to be registered at once for client access" do + lambda { @server.register(:foo, :bar, :baz) }.should_not raise_error + end + + it "should allow the use of indirection names to specify which indirections are to be no longer accessible to clients" do + @server.register(:foo) + lambda { @server.unregister(:foo) }.should_not raise_error + end + + it "should leave other indirections accessible to clients when turning off indirections" do + @server.register(:foo, :bar) + @server.unregister(:foo) + lambda { @server.unregister(:bar)}.should_not raise_error + end + + it "should allow specifying numerous indirections which are to be no longer accessible to clients" do + @server.register(:foo, :bar) + lambda { @server.unregister(:foo, :bar) }.should_not raise_error + end + + it "should not turn off any indirections if given unknown indirection names to turn off" do + @server.register(:foo, :bar) + lambda { @server.unregister(:foo, :bar, :baz) }.should raise_error(ArgumentError) + lambda { @server.unregister(:foo, :bar) }.should_not raise_error + end + + it "should not allow turning off unknown indirection names" do + @server.register(:foo, :bar) + lambda { @server.unregister(:baz) }.should raise_error(ArgumentError) + end + + it "should disable client access immediately when turning off indirections" do + @server.register(:foo, :bar) + @server.unregister(:foo) + lambda { @server.unregister(:foo) }.should raise_error(ArgumentError) + end + + it "should allow turning off all indirections at once" do + @server.register(:foo, :bar) + @server.unregister + [ :foo, :bar, :baz].each do |indirection| + lambda { @server.unregister(indirection) }.should raise_error(ArgumentError) + end end end - + it "should provide a means of determining whether it is listening" do @server.should respond_to(:listening?) end - + it "should provide a means of determining which HTTP server will be used to provide access to clients" do @server.server_type.should == :suparserver end - - it "should allow for multiple configurations, each handling different indirections" do - @server2 = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) - @server.register(:foo, :bar) - @server2.register(:foo, :xyzzy) - @server.unregister(:foo, :bar) - @server2.unregister(:foo, :xyzzy) - Proc.new { @server.unregister(:xyzzy) }.should raise_error(ArgumentError) - Proc.new { @server2.unregister(:bar) }.should raise_error(ArgumentError) - end it "should provide a means of determining which protocols are in use" do @server.should respond_to(:protocols) end - - it "should only support the REST protocol at this time" do - @server.protocols.should == [ :rest ] + + it "should set the protocols to :rest and :xmlrpc" do + @server.protocols.should == [ :rest, :xmlrpc ] end - + it "should provide a means of determining the listening address" do @server.address.should == "127.0.0.1" end - + it "should provide a means of determining the listening port" do @server.port.should == 31337 end -end -describe Puppet::Network::Server, "when listening is off" do - before do - @mock_http_server_class = mock('http server class') - Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class) - Puppet.stubs(:[]).with(:servertype).returns(:suparserver) - @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) - @mock_http_server = mock('http server') - @mock_http_server.stubs(:listen) - @server.stubs(:http_server).returns(@mock_http_server) - end + it "should allow for multiple configurations, each handling different indirections" do + Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') - it "should indicate that it is not listening" do - @server.should_not be_listening + @server2 = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) + @server.register(:foo, :bar) + @server2.register(:foo, :xyzzy) + @server.unregister(:foo, :bar) + @server2.unregister(:foo, :xyzzy) + lambda { @server.unregister(:xyzzy) }.should raise_error(ArgumentError) + lambda { @server2.unregister(:bar) }.should raise_error(ArgumentError) end - - it "should not allow listening to be turned off" do - Proc.new { @server.unlisten }.should raise_error(RuntimeError) - end - - it "should allow listening to be turned on" do - Proc.new { @server.listen }.should_not raise_error - end - -end -describe Puppet::Network::Server, "when listening is on" do - before do - @mock_http_server_class = mock('http server class') - Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class) - Puppet.stubs(:[]).with(:servertype).returns(:suparserver) - @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) - @mock_http_server = mock('http server') - @mock_http_server.stubs(:listen) - @mock_http_server.stubs(:unlisten) - @server.stubs(:http_server).returns(@mock_http_server) - @server.listen - end - - it "should indicate that listening is turned off" do - @server.should be_listening - end - - it "should not allow listening to be turned on" do - Proc.new { @server.listen }.should raise_error(RuntimeError) - end - - it "should allow listening to be turned off" do - Proc.new { @server.unlisten }.should_not raise_error - end -end - -describe Puppet::Network::Server, "when listening is being turned on" do - before do - @mock_http_server_class = mock('http server class') - Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class) - Puppet.stubs(:[]).with(:servertype).returns(:suparserver) - @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337, :handlers => [:node]) - @mock_http_server = mock('http server') - @mock_http_server.stubs(:listen) - end + describe "when managing xmlrpc registrations" do + before do + Puppet::Network::Handler.stubs(:handler).returns mock('xmlrpc_handler') + end - it "should fetch an instance of an HTTP server" do - @server.stubs(:http_server_class).returns(@mock_http_server_class) - @mock_http_server_class.expects(:new).returns(@mock_http_server) - @server.listen - end + it "should allow registering an xmlrpc handler by specifying its namespace" do + lambda { @server.register_xmlrpc(:foo) }.should_not raise_error + end - it "should cause the HTTP server to listen" do - @server.stubs(:http_server).returns(@mock_http_server) - @mock_http_server.expects(:listen) - @server.listen - end - - it "should pass the listening address to the HTTP server" do - @server.stubs(:http_server).returns(@mock_http_server) - @mock_http_server.expects(:listen).with do |args| - args[:address] == '127.0.0.1' - end - @server.listen - end - - it "should pass the listening port to the HTTP server" do - @server.stubs(:http_server).returns(@mock_http_server) - @mock_http_server.expects(:listen).with do |args| - args[:port] == 31337 - end - @server.listen - end - - it "should pass a list of handlers to the HTTP server" do - @server.stubs(:http_server).returns(@mock_http_server) - @mock_http_server.expects(:listen).with do |args| - args[:handlers] == [ :node ] - end - @server.listen + it "should require that the xmlrpc namespace be valid" do + Puppet::Network::Handler.stubs(:handler).returns nil + + lambda { @server.register_xmlrpc(:foo) }.should raise_error(ArgumentError) + end + + it "should require at least one namespace" do + lambda { @server.register_xmlrpc() }.should raise_error(ArgumentError) + end + + it "should allow multiple namespaces to be registered at once" do + lambda { @server.register_xmlrpc(:foo, :bar) }.should_not raise_error + end + + it "should allow the use of namespaces to specify which are no longer accessible to clients" do + @server.register_xmlrpc(:foo, :bar) + end + + it "should leave other namespaces accessible to clients when turning off xmlrpc namespaces" do + @server.register_xmlrpc(:foo, :bar) + @server.unregister_xmlrpc(:foo) + lambda { @server.unregister_xmlrpc(:bar)}.should_not raise_error + end + + it "should allow specifying numerous namespaces which are to be no longer accessible to clients" do + @server.register_xmlrpc(:foo, :bar) + lambda { @server.unregister_xmlrpc(:foo, :bar) }.should_not raise_error + end + + it "should not turn off any indirections if given unknown namespaces to turn off" do + @server.register_xmlrpc(:foo, :bar) + lambda { @server.unregister_xmlrpc(:foo, :bar, :baz) }.should raise_error(ArgumentError) + lambda { @server.unregister_xmlrpc(:foo, :bar) }.should_not raise_error + end + + it "should not allow turning off unknown namespaces" do + @server.register_xmlrpc(:foo, :bar) + lambda { @server.unregister_xmlrpc(:baz) }.should raise_error(ArgumentError) + end + + it "should disable client access immediately when turning off namespaces" do + @server.register_xmlrpc(:foo, :bar) + @server.unregister_xmlrpc(:foo) + lambda { @server.unregister_xmlrpc(:foo) }.should raise_error(ArgumentError) + end + + it "should allow turning off all namespaces at once" do + @server.register_xmlrpc(:foo, :bar) + @server.unregister_xmlrpc + [ :foo, :bar, :baz].each do |indirection| + lambda { @server.unregister_xmlrpc(indirection) }.should raise_error(ArgumentError) + end + end end - - it "should pass a list of protocols to the HTTP server" do - @server.stubs(:http_server).returns(@mock_http_server) - @mock_http_server.expects(:listen).with do |args| - args[:protocols] == [ :rest ] - end - @server.listen + + describe "when listening is off" do + before do + @mock_http_server = mock('http server') + @mock_http_server.stubs(:listen) + @server.stubs(:http_server).returns(@mock_http_server) + end + + it "should indicate that it is not listening" do + @server.should_not be_listening + end + + it "should not allow listening to be turned off" do + lambda { @server.unlisten }.should raise_error(RuntimeError) + end + + it "should allow listening to be turned on" do + lambda { @server.listen }.should_not raise_error + end + end -end -describe Puppet::Network::Server, "when listening is being turned off" do - before do - @mock_http_server_class = mock('http server class') - Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class) - Puppet.stubs(:[]).with(:servertype).returns(:suparserver) - @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) - @mock_http_server = mock('http server') - @mock_http_server.stubs(:listen) - @server.stubs(:http_server).returns(@mock_http_server) - @server.listen + describe "when listening is on" do + before do + @mock_http_server = mock('http server') + @mock_http_server.stubs(:listen) + @mock_http_server.stubs(:unlisten) + @server.stubs(:http_server).returns(@mock_http_server) + @server.listen + end + + it "should indicate that it is listening" do + @server.should be_listening + end + + it "should not allow listening to be turned on" do + lambda { @server.listen }.should raise_error(RuntimeError) + end + + it "should allow listening to be turned off" do + lambda { @server.unlisten }.should_not raise_error + end end - it "should cause the HTTP server to stop listening when listening is turned off" do - @mock_http_server.expects(:unlisten) - @server.unlisten + describe "when listening is being turned on" do + before do + Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') + Puppet::Network::Handler.stubs(:handler).returns mock('xmlrpc_handler') + + @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337, :handlers => [:node], :xmlrpc_handlers => [:master]) + @mock_http_server = mock('http server') + @mock_http_server.stubs(:listen) + end + + it "should fetch an instance of an HTTP server" do + @server.stubs(:http_server_class).returns(@mock_http_server_class) + @mock_http_server_class.expects(:new).returns(@mock_http_server) + @server.listen + end + + it "should cause the HTTP server to listen" do + @server.stubs(:http_server).returns(@mock_http_server) + @mock_http_server.expects(:listen) + @server.listen + end + + it "should pass the listening address to the HTTP server" do + @server.stubs(:http_server).returns(@mock_http_server) + @mock_http_server.expects(:listen).with do |args| + args[:address] == '127.0.0.1' + end + @server.listen + end + + it "should pass the listening port to the HTTP server" do + @server.stubs(:http_server).returns(@mock_http_server) + @mock_http_server.expects(:listen).with do |args| + args[:port] == 31337 + end + @server.listen + end + + it "should pass a list of REST handlers to the HTTP server" do + @server.stubs(:http_server).returns(@mock_http_server) + @mock_http_server.expects(:listen).with do |args| + args[:handlers] == [ :node ] + end + @server.listen + end + + it "should pass a list of XMLRPC handlers to the HTTP server" do + @server.stubs(:http_server).returns(@mock_http_server) + @mock_http_server.expects(:listen).with do |args| + args[:xmlrpc_handlers] == [ :master ] + end + @server.listen + end + + it "should pass a list of protocols to the HTTP server" do + @server.stubs(:http_server).returns(@mock_http_server) + @mock_http_server.expects(:listen).with do |args| + args[:protocols] == [ :rest, :xmlrpc ] + end + @server.listen + end end - it "should not allow for indirections to be turned off" do - @server.register(:foo) - Proc.new { @server.unregister(:foo) }.should raise_error(RuntimeError) + describe "when listening is being turned off" do + before do + @mock_http_server = mock('http server') + @mock_http_server.stubs(:listen) + @server.stubs(:http_server).returns(@mock_http_server) + @server.listen + end + + it "should cause the HTTP server to stop listening" do + @mock_http_server.expects(:unlisten) + @server.unlisten + end + + it "should not allow for indirections to be turned off" do + Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') + + @server.register(:foo) + lambda { @server.unregister(:foo) }.should raise_error(RuntimeError) + end end -end - -describe Class.new, "put these somewhere" do - it "should have the ability to use a class-level from_ hook (from_yaml, from_text, etc.) that can be called, based on content-type header, to allow for different deserializations of an object" - it "should allow from_* on the inbound :data packet (look at its content_type) when doing a PUT/.new.save" - it "should prepend a rest version number on the path (w00t)" - it "should ... on server side, .save should from_yaml, then foo.save(args) instead of just Foo.new.save(args)" + + describe Class.new, "put these somewhere" do + it "should have the ability to use a class-level from_ hook (from_yaml, from_text, etc.) that can be called, based on content-type header, to allow for different deserializations of an object" + it "should allow from_* on the inbound :data packet (look at its content_type) when doing a PUT/.new.save" + it "should prepend a rest version number on the path (w00t)" + it "should ... on server side, .save should from_yaml, then foo.save(args) instead of just Foo.new.save(args)" + end end diff --git a/spec/unit/node.rb b/spec/unit/node.rb index 421fcd635..08afc5183 100755 --- a/spec/unit/node.rb +++ b/spec/unit/node.rb @@ -113,6 +113,22 @@ describe Puppet::Node, " when merging facts" do @node.merge "two" => "three" @node.parameters["two"].should == "three" end + + it "should add the environment to the list of parameters" do + Puppet.settings.stubs(:value).with(:environments).returns("one,two") + Puppet.settings.stubs(:value).with(:environment).returns("one") + @node = Puppet::Node.new("testnode", :environment => "one") + @node.merge "two" => "three" + @node.parameters["environment"].should == "one" + end + + it "should not set the environment if it is already set in the parameters" do + Puppet.settings.stubs(:value).with(:environments).returns("one,two") + Puppet.settings.stubs(:value).with(:environment).returns("one") + @node = Puppet::Node.new("testnode", :environment => "one") + @node.merge "environment" => "two" + @node.parameters["environment"].should == "two" + end end describe Puppet::Node, " when indirecting" do @@ -132,7 +148,7 @@ describe Puppet::Node, " when indirecting" do end after do - Puppet::Indirector::Indirection.clear_cache + Puppet::Util::Cacher.invalidate end end diff --git a/spec/unit/node/catalog.rb b/spec/unit/node/catalog.rb index 8178f953a..59c70b45e 100755 --- a/spec/unit/node/catalog.rb +++ b/spec/unit/node/catalog.rb @@ -310,8 +310,6 @@ describe Puppet::Node::Catalog, " when converting to a RAL catalog" do newconfig = nil - Puppet::Type.allclear - proc { @catalog = config.to_ral }.should_not raise_error @catalog.resource("Test[changer2]").should equal(resource) end @@ -325,9 +323,9 @@ end describe Puppet::Node::Catalog, " when functioning as a resource container" do before do @catalog = Puppet::Node::Catalog.new("host") - @one = stub 'resource1', :ref => "Me[one]", :catalog= => nil - @two = stub 'resource2', :ref => "Me[two]", :catalog= => nil - @dupe = stub 'resource3', :ref => "Me[one]", :catalog= => nil + @one = stub 'resource1', :ref => "Me[one]", :catalog= => nil, :title => "one", :[] => "one" + @two = stub 'resource2', :ref => "Me[two]", :catalog= => nil, :title => "two", :[] => "two" + @dupe = stub 'resource3', :ref => "Me[one]", :catalog= => nil, :title => "one", :[] => "one" end it "should provide a method to add one or more resources" do @@ -445,6 +443,28 @@ describe Puppet::Node::Catalog, " when functioning as a resource container" do @catalog.resource("me", "other").should equal(@one) end + it "should ignore conflicting aliases that point to the aliased resource" do + @catalog.alias(@one, "other") + lambda { @catalog.alias(@one, "other") }.should_not raise_error + end + + it "should create aliases for resources isomorphic resources whose names do not match their titles" do + resource = Puppet::Type::File.create(:title => "testing", :path => "/something") + + @catalog.add_resource(resource) + + @catalog.resource(:file, "/something").should equal(resource) + end + + it "should not create aliases for resources non-isomorphic resources whose names do not match their titles" do + resource = Puppet::Type.type(:exec).create(:title => "testing", :command => "echo", :path => %w{/bin /usr/bin /usr/local/bin}) + + @catalog.add_resource(resource) + + # Yay, I've already got a 'should' method + @catalog.resource(:exec, "echo").object_id.should == nil.object_id + end + # This test is the same as the previous, but the behaviour should be explicit. it "should alias using the class name from the resource reference, not the resource class name" do @catalog.add_resource @one @@ -452,7 +472,7 @@ describe Puppet::Node::Catalog, " when functioning as a resource container" do @catalog.resource("me", "other").should equal(@one) end - it "should fail to add an alias if the aliased name already exists" do + it "should fail to add an alias if the aliased name already exists as a resource" do @catalog.add_resource @one proc { @catalog.alias @two, "one" }.should raise_error(ArgumentError) end @@ -494,10 +514,6 @@ describe Puppet::Node::Catalog, " when functioning as a resource container" do raise "Aliased non-isomorphic resource" end end - - after do - Puppet::Type.allclear - end end describe Puppet::Node::Catalog do @@ -628,13 +644,14 @@ describe Puppet::Node::Catalog, " when creating a relationship graph" do @file = Puppet::Type.type(:file) @one = @file.create :path => "/one" @two = @file.create :path => "/two" + @sub = @file.create :path => "/two/subdir" @catalog.add_edge @compone, @one @catalog.add_edge @comptwo, @two @three = @file.create :path => "/three" @four = @file.create :path => "/four", :require => ["file", "/three"] @five = @file.create :path => "/five" - @catalog.add_resource @compone, @comptwo, @one, @two, @three, @four, @five + @catalog.add_resource @compone, @comptwo, @one, @two, @three, @four, @five, @sub @relationships = @catalog.relationship_graph end @@ -669,6 +686,10 @@ describe Puppet::Node::Catalog, " when creating a relationship graph" do @relationships.edge?(@one, @two).should be_true end + it "should add automatic relationships to the relationship graph" do + @relationships.edge?(@two, @sub).should be_true + end + it "should get removed when the catalog is cleaned up" do @relationships.expects(:clear).with(false) @catalog.clear @@ -682,14 +703,14 @@ describe Puppet::Node::Catalog, " when creating a relationship graph" do end it "should look up resources in the relationship graph if not found in the main catalog" do - five = stub 'five', :ref => "File[five]", :catalog= => nil + five = stub 'five', :ref => "File[five]", :catalog= => nil, :title => "five", :[] => "five" @relationships.add_resource five @catalog.resource(five.ref).should equal(five) end it "should provide a method to create additional resources that also registers the resource" do args = {:name => "/yay", :ensure => :file} - resource = stub 'file', :ref => "File[/yay]", :catalog= => @catalog + resource = stub 'file', :ref => "File[/yay]", :catalog= => @catalog, :title => "/yay", :[] => "/yay" Puppet::Type.type(:file).expects(:create).with(args).returns(resource) @catalog.create_resource :file, args @catalog.resource("File[/yay]").should equal(resource) @@ -697,7 +718,7 @@ describe Puppet::Node::Catalog, " when creating a relationship graph" do it "should provide a mechanism for creating implicit resources" do args = {:name => "/yay", :ensure => :file} - resource = stub 'file', :ref => "File[/yay]", :catalog= => @catalog + resource = stub 'file', :ref => "File[/yay]", :catalog= => @catalog, :title => "/yay", :[] => "/yay" Puppet::Type.type(:file).expects(:create).with(args).returns(resource) resource.expects(:implicit=).with(true) @catalog.create_implicit_resource :file, args @@ -706,7 +727,7 @@ describe Puppet::Node::Catalog, " when creating a relationship graph" do it "should add implicit resources to the relationship graph if there is one" do args = {:name => "/yay", :ensure => :file} - resource = stub 'file', :ref => "File[/yay]", :catalog= => @catalog + resource = stub 'file', :ref => "File[/yay]", :catalog= => @catalog, :title => "/yay", :[] => "/yay" resource.expects(:implicit=).with(true) Puppet::Type.type(:file).expects(:create).with(args).returns(resource) # build the graph @@ -718,7 +739,7 @@ describe Puppet::Node::Catalog, " when creating a relationship graph" do it "should remove resources created mid-transaction" do args = {:name => "/yay", :ensure => :file} - resource = stub 'file', :ref => "File[/yay]", :catalog= => @catalog + resource = stub 'file', :ref => "File[/yay]", :catalog= => @catalog, :title => "/yay", :[] => "/yay" @transaction = mock 'transaction' Puppet::Transaction.stubs(:new).returns(@transaction) @transaction.stubs(:evaluate) @@ -737,10 +758,6 @@ describe Puppet::Node::Catalog, " when creating a relationship graph" do @catalog.remove_resource(@one) @catalog.relationship_graph.vertex?(@one).should be_false end - - after do - Puppet::Type.allclear - end end describe Puppet::Node::Catalog, " when writing dot files" do @@ -780,7 +797,7 @@ describe Puppet::Node::Catalog, " when indirecting" do before do @indirection = stub 'indirection', :name => :catalog - Puppet::Indirector::Indirection.clear_cache + Puppet::Util::Cacher.invalidate end it "should redirect to the indirection for retrieval" do @@ -794,8 +811,7 @@ describe Puppet::Node::Catalog, " when indirecting" do end after do - mocha_verify - Puppet::Indirector::Indirection.clear_cache + Puppet::Util::Cacher.invalidate end end diff --git a/spec/unit/node/facts.rb b/spec/unit/node/facts.rb index 1bfccd32e..69b8e4483 100755 --- a/spec/unit/node/facts.rb +++ b/spec/unit/node/facts.rb @@ -10,7 +10,8 @@ describe Puppet::Node::Facts, " when indirecting" do # We have to clear the cache so that the facts ask for our indirection stub, # instead of anything that might be cached. - Puppet::Indirector::Indirection.clear_cache + Puppet::Util::Cacher.invalidate + @facts = Puppet::Node::Facts.new("me", "one" => "two") end @@ -29,11 +30,6 @@ describe Puppet::Node::Facts, " when indirecting" do it "should default to the 'facter' terminus" do Puppet::Node::Facts.indirection.terminus_class.should == :facter end - - after do - mocha_verify - Puppet::Indirector::Indirection.clear_cache - end end describe Puppet::Node::Facts, " when storing and retrieving" do diff --git a/spec/unit/other/transbucket.rb b/spec/unit/other/transbucket.rb index 4494f2abb..e447c78a2 100755 --- a/spec/unit/other/transbucket.rb +++ b/spec/unit/other/transbucket.rb @@ -91,10 +91,6 @@ describe Puppet::TransBucket, " when generating a catalog" do @fakes = %w{Fake[bottom] Fake[middle] Fake[top]} end - after do - Puppet::Type.allclear - end - it "should convert all transportable objects to RAL resources" do @catalog = @top.to_catalog @users.each do |name| diff --git a/spec/unit/provider/mount/parsed.rb b/spec/unit/provider/mount/parsed.rb index ba65b70a4..8d043f97f 100755 --- a/spec/unit/provider/mount/parsed.rb +++ b/spec/unit/provider/mount/parsed.rb @@ -167,10 +167,6 @@ describe provider_class do @provider_class.prefetch("/" => @mount) @mount.provider.should be_mounted end - - after do - Puppet::Type.allclear - end end describe provider_class, " when mounting and unmounting" do diff --git a/spec/unit/rails.rb b/spec/unit/rails.rb index bb2f991ca..4a4667543 100755 --- a/spec/unit/rails.rb +++ b/spec/unit/rails.rb @@ -7,7 +7,7 @@ describe Puppet::Rails, "when initializing any connection" do confine Puppet.features.rails? => "Cannot test without ActiveRecord" before do - @logger = mock 'logger' + @logger = stub 'logger', :level= => nil @logger.stub_everything Logger.stubs(:new).returns(@logger) diff --git a/spec/unit/resource_reference.rb b/spec/unit/resource_reference.rb index cbbd6ef51..ee71a5077 100755 --- a/spec/unit/resource_reference.rb +++ b/spec/unit/resource_reference.rb @@ -48,18 +48,6 @@ describe Puppet::ResourceReference do end end -describe Puppet::ResourceReference, "when resolving resources without a catalog" do - it "should be able to resolve builtin resources from their types" do - Puppet::Type.type(:file).expects(:[]).with("myfile").returns(:myfile) - Puppet::ResourceReference.new(:file, "myfile").resolve.should == :myfile - end - - it "should be able to resolve defined resources from Components" do - Puppet::Type.type(:component).expects(:[]).with("Foo::Bar[yay]").returns(:mything) - Puppet::ResourceReference.new("foo::bar", "yay").resolve.should == :mything - end -end - describe Puppet::ResourceReference, "when resolving resources with a catalog" do it "should resolve all resources using the catalog" do config = mock 'catalog' diff --git a/spec/unit/ssl/certificate.rb b/spec/unit/ssl/certificate.rb new file mode 100755 index 000000000..1cb164d3f --- /dev/null +++ b/spec/unit/ssl/certificate.rb @@ -0,0 +1,82 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/ssl/certificate' + +describe Puppet::SSL::Certificate do + before do + @class = Puppet::SSL::Certificate + end + + after do + @class.instance_variable_set("@ca_location", nil) + end + + it "should be extended with the Indirector module" do + @class.metaclass.should be_include(Puppet::Indirector) + end + + it "should indirect certificate" do + @class.indirection.name.should == :certificate + end + + it "should default to the :file terminus" do + @class.indirection.terminus_class.should == :file + end + + describe "when managing instances" do + before do + @certificate = @class.new("myname") + end + + it "should have a name attribute" do + @certificate.name.should == "myname" + end + + it "should have a content attribute" do + @certificate.should respond_to(:content) + end + + it "should return a nil expiration if there is no actual certificate" do + @certificate.stubs(:content).returns nil + + @certificate.expiration.should be_nil + end + + it "should use the expiration of the certificate as its expiration date" do + cert = stub 'cert' + @certificate.stubs(:content).returns cert + + cert.expects(:not_after).returns "sometime" + + @certificate.expiration.should == "sometime" + end + + it "should be able to read certificates from disk" do + path = "/my/path" + File.expects(:read).with(path).returns("my certificate") + certificate = mock 'certificate' + OpenSSL::X509::Certificate.expects(:new).with("my certificate").returns(certificate) + @certificate.read(path).should equal(certificate) + @certificate.content.should equal(certificate) + end + + it "should return an empty string when converted to a string with no certificate" do + @certificate.to_s.should == "" + end + + it "should convert the certificate to pem format when converted to a string" do + certificate = mock 'certificate', :to_pem => "pem" + @certificate.content = certificate + @certificate.to_s.should == "pem" + end + + it "should have a :to_text method that it delegates to the actual key" do + real_certificate = mock 'certificate' + real_certificate.expects(:to_text).returns "certificatetext" + @certificate.content = real_certificate + @certificate.to_text.should == "certificatetext" + end + end +end diff --git a/spec/unit/ssl/certificate_authority.rb b/spec/unit/ssl/certificate_authority.rb new file mode 100755 index 000000000..ff629fec1 --- /dev/null +++ b/spec/unit/ssl/certificate_authority.rb @@ -0,0 +1,737 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/ssl/certificate_authority' + +describe Puppet::SSL::CertificateAuthority do + after do + # Clear out the var, yay unit tests. + Puppet::SSL::CertificateAuthority.instance_variable_set("@instance", nil) + Puppet.settings.clearused + end + + it "should have a class method for returning a singleton instance" do + Puppet::SSL::CertificateAuthority.should respond_to(:instance) + end + + describe "when finding an existing instance" do + describe "and the host is a CA host and the proces name is 'puppetmasterd'" do + before do + Puppet.settings.stubs(:value).with(:ca).returns true + Puppet.settings.stubs(:value).with(:name).returns "puppetmasterd" + + @ca = mock('ca') + Puppet::SSL::CertificateAuthority.stubs(:new).returns @ca + end + + after do + # Clear out the var, yay unit tests. + Puppet::SSL::CertificateAuthority.instance_variable_set("@instance", nil) + end + + it "should return an instance" do + Puppet::SSL::CertificateAuthority.instance.should equal(@ca) + end + + it "should always return the same instance" do + Puppet::SSL::CertificateAuthority.instance.should equal(Puppet::SSL::CertificateAuthority.instance) + end + end + + describe "and the host is not a CA host" do + it "should return nil" do + Puppet.settings.stubs(:value).with(:ca).returns false + Puppet.settings.stubs(:value).with(:name).returns "puppetmasterd" + + ca = mock('ca') + Puppet::SSL::CertificateAuthority.expects(:new).never + Puppet::SSL::CertificateAuthority.instance.should be_nil + end + end + + describe "and the process name is not 'puppetmasterd'" do + it "should return nil" do + Puppet.settings.stubs(:value).with(:ca).returns true + Puppet.settings.stubs(:value).with(:name).returns "puppetd" + + ca = mock('ca') + Puppet::SSL::CertificateAuthority.expects(:new).never + Puppet::SSL::CertificateAuthority.instance.should be_nil + end + end + end + + describe "when initializing" do + before do + Puppet.settings.stubs(:use) + Puppet.settings.stubs(:value).returns "ca_testing" + + Puppet::SSL::CertificateAuthority.any_instance.stubs(:setup) + end + + it "should always set its name to the value of :certname" do + Puppet.settings.expects(:value).with(:certname).returns "ca_testing" + + Puppet::SSL::CertificateAuthority.new.name.should == "ca_testing" + end + + it "should create an SSL::Host instance whose name is the 'ca_name'" do + Puppet::SSL::Host.expects(:ca_name).returns "caname" + + host = stub 'host' + Puppet::SSL::Host.expects(:new).with("caname").returns host + + Puppet::SSL::CertificateAuthority.new + end + + it "should use the :main, :ca, and :ssl settings sections" do + Puppet.settings.expects(:use).with(:main, :ssl, :ca) + Puppet::SSL::CertificateAuthority.new + end + + it "should create an inventory instance" do + Puppet::SSL::Inventory.expects(:new).returns "inventory" + + Puppet::SSL::CertificateAuthority.new.inventory.should == "inventory" + end + + it "should make sure the CA is set up" do + Puppet::SSL::CertificateAuthority.any_instance.expects(:setup) + + Puppet::SSL::CertificateAuthority.new + end + end + + describe "when setting itself up" do + it "should generate the CA certificate if it does not have one" do + Puppet.settings.stubs :use + + host = stub 'host' + Puppet::SSL::Host.stubs(:new).returns host + + host.expects(:certificate).returns nil + + Puppet::SSL::CertificateAuthority.any_instance.expects(:generate_ca_certificate) + Puppet::SSL::CertificateAuthority.new + end + end + + describe "when retrieving the certificate revocation list" do + before do + Puppet.settings.stubs(:use) + Puppet.settings.stubs(:value).returns "ca_testing" + Puppet.settings.stubs(:value).with(:cacrl).returns "/my/crl" + + cert = stub("certificate", :content => "real_cert") + key = stub("key", :content => "real_key") + @host = stub 'host', :certificate => cert, :name => "hostname", :key => key + + Puppet::SSL::CertificateAuthority.any_instance.stubs(:setup) + @ca = Puppet::SSL::CertificateAuthority.new + + @ca.stubs(:host).returns @host + end + + it "should return any found CRL instance" do + crl = mock 'crl' + Puppet::SSL::CertificateRevocationList.expects(:find).returns crl + @ca.crl.should equal(crl) + end + + it "should create, generate, and save a new CRL instance of no CRL can be found" do + crl = mock 'crl' + Puppet::SSL::CertificateRevocationList.expects(:find).returns nil + + Puppet::SSL::CertificateRevocationList.expects(:new).returns crl + + crl.expects(:generate).with(@ca.host.certificate.content, @ca.host.key.content) + crl.expects(:save) + + @ca.crl.should equal(crl) + end + end + + describe "when generating a self-signed CA certificate" do + before do + Puppet.settings.stubs(:use) + Puppet.settings.stubs(:value).returns "ca_testing" + + Puppet::SSL::CertificateAuthority.any_instance.stubs(:setup) + Puppet::SSL::CertificateAuthority.any_instance.stubs(:crl) + @ca = Puppet::SSL::CertificateAuthority.new + + @host = stub 'host', :key => mock("key"), :name => "hostname", :certificate => mock('certificate') + + Puppet::SSL::CertificateRequest.any_instance.stubs(:generate) + + @ca.stubs(:host).returns @host + end + + it "should create and store a password at :capass" do + Puppet.settings.expects(:value).with(:capass).returns "/path/to/pass" + + FileTest.expects(:exist?).with("/path/to/pass").returns false + + fh = mock 'filehandle' + Puppet.settings.expects(:write).with(:capass).yields fh + + fh.expects(:print).with { |s| s.length > 18 } + + @ca.stubs(:sign) + + @ca.generate_ca_certificate + end + + it "should generate a key if one does not exist" do + @ca.stubs :generate_password + @ca.stubs :sign + + @ca.host.expects(:key).returns nil + @ca.host.expects(:generate_key) + + @ca.generate_ca_certificate + end + + it "should create and sign a self-signed cert using the CA name" do + request = mock 'request' + Puppet::SSL::CertificateRequest.expects(:new).with(@ca.host.name).returns request + request.expects(:generate).with(@ca.host.key) + + @ca.expects(:sign).with(@host.name, :ca, request) + + @ca.stubs :generate_password + + @ca.generate_ca_certificate + end + + it "should generate its CRL" do + @ca.stubs :generate_password + @ca.stubs :sign + + @ca.host.expects(:key).returns nil + @ca.host.expects(:generate_key) + + @ca.expects(:crl) + + @ca.generate_ca_certificate + end + end + + describe "when signing" do + before do + Puppet.settings.stubs(:use) + + Puppet::SSL::CertificateAuthority.any_instance.stubs(:password?).returns true + + # Set up the CA + @key = mock 'key' + @key.stubs(:content).returns "cakey" + Puppet::SSL::CertificateAuthority.any_instance.stubs(:key).returns @key + @cacert = mock 'certificate' + @cacert.stubs(:content).returns "cacertificate" + @ca = Puppet::SSL::CertificateAuthority.new + + @ca.host.stubs(:certificate).returns @cacert + @ca.host.stubs(:key).returns @key + + @name = "myhost" + @real_cert = stub 'realcert', :sign => nil + @cert = stub 'certificate', :content => @real_cert + Puppet::SSL::Certificate.stubs(:new).returns @cert + + @cert.stubs(:content=) + @cert.stubs(:save) + + # Stub out the factory + @factory = stub 'factory', :result => "my real cert" + Puppet::SSL::CertificateFactory.stubs(:new).returns @factory + + @request = stub 'request', :content => "myrequest", :name => @name + + # And the inventory + @inventory = stub 'inventory', :add => nil + @ca.stubs(:inventory).returns @inventory + + Puppet::SSL::CertificateRequest.stubs(:destroy) + end + + describe "and calculating the next certificate serial number" do + before do + @path = "/path/to/serial" + Puppet.settings.stubs(:value).with(:serial).returns @path + + @filehandle = stub 'filehandle', :<< => @filehandle + Puppet.settings.stubs(:readwritelock).with(:serial).yields @filehandle + end + + it "should default to 0x0 for the first serial number" do + @ca.next_serial.should == 0x0 + end + + it "should return the current content of the serial file" do + FileTest.stubs(:exist?).with(@path).returns true + File.expects(:read).with(@path).returns "0002" + + @ca.next_serial.should == 2 + end + + it "should write the next serial number to the serial file as hex" do + @filehandle.expects(:<<).with("0001") + + @ca.next_serial + end + + it "should lock the serial file while writing" do + Puppet.settings.expects(:readwritelock).with(:serial) + + @ca.next_serial + end + end + + describe "its own certificate" do + before do + @serial = 10 + @ca.stubs(:next_serial).returns @serial + end + + it "should not look up a certificate request for the host" do + Puppet::SSL::CertificateRequest.expects(:find).never + + @ca.sign(@name, :ca, @request) + end + + it "should use a certificate type of :ca" do + Puppet::SSL::CertificateFactory.expects(:new).with do |*args| + args[0] == :ca + end.returns @factory + @ca.sign(@name, :ca, @request) + end + + it "should pass the provided CSR as the CSR" do + Puppet::SSL::CertificateFactory.expects(:new).with do |*args| + args[1] == "myrequest" + end.returns @factory + @ca.sign(@name, :ca, @request) + end + + it "should use the provided CSR's content as the issuer" do + Puppet::SSL::CertificateFactory.expects(:new).with do |*args| + args[2] == "myrequest" + end.returns @factory + @ca.sign(@name, :ca, @request) + end + + it "should pass the next serial as the serial number" do + Puppet::SSL::CertificateFactory.expects(:new).with do |*args| + args[3] == @serial + end.returns @factory + @ca.sign(@name, :ca, @request) + end + + it "should save the resulting certificate" do + @cert.expects(:save) + + @ca.sign(@name, :ca, @request) + end + end + + describe "another host's certificate" do + before do + @serial = 10 + @ca.stubs(:next_serial).returns @serial + + Puppet::SSL::CertificateRequest.stubs(:find).with(@name).returns @request + @cert.stubs :save + end + + it "should use a certificate type of :server" do + Puppet::SSL::CertificateFactory.expects(:new).with do |*args| + args[0] == :server + end.returns @factory + + @ca.sign(@name) + end + + it "should use look up a CSR for the host in the :ca_file terminus" do + Puppet::SSL::CertificateRequest.expects(:find).with(@name).returns @request + + @ca.sign(@name) + end + + it "should fail if no CSR can be found for the host" do + Puppet::SSL::CertificateRequest.expects(:find).with(@name).returns nil + + lambda { @ca.sign(@name) }.should raise_error(ArgumentError) + end + + it "should use the CA certificate as the issuer" do + Puppet::SSL::CertificateFactory.expects(:new).with do |*args| + args[2] == @cacert.content + end.returns @factory + @ca.sign(@name) + end + + it "should pass the next serial as the serial number" do + Puppet::SSL::CertificateFactory.expects(:new).with do |*args| + args[3] == @serial + end.returns @factory + @ca.sign(@name) + end + + it "should sign the resulting certificate using its real key and a digest" do + digest = mock 'digest' + OpenSSL::Digest::SHA1.expects(:new).returns digest + + key = stub 'key', :content => "real_key" + @ca.host.stubs(:key).returns key + + @cert.content.expects(:sign).with("real_key", digest) + @ca.sign(@name) + end + + it "should save the resulting certificate" do + @cert.expects(:save) + @ca.sign(@name) + end + + it "should remove the host's certificate request" do + Puppet::SSL::CertificateRequest.expects(:destroy).with(@name) + + @ca.sign(@name) + end + end + + it "should create a certificate instance with the content set to the newly signed x509 certificate" do + @serial = 10 + @ca.stubs(:next_serial).returns @serial + + Puppet::SSL::CertificateRequest.stubs(:find).with(@name).returns @request + @cert.stubs :save + Puppet::SSL::Certificate.expects(:new).with(@name).returns @cert + + @ca.sign(@name) + end + + it "should return the certificate instance" do + @ca.stubs(:next_serial).returns @serial + Puppet::SSL::CertificateRequest.stubs(:find).with(@name).returns @request + @cert.stubs :save + @ca.sign(@name).should equal(@cert) + end + + it "should add the certificate to its inventory" do + @ca.stubs(:next_serial).returns @serial + @inventory.expects(:add).with(@cert) + + Puppet::SSL::CertificateRequest.stubs(:find).with(@name).returns @request + @cert.stubs :save + @ca.sign(@name) + end + + it "should have a method for triggering autosigning of available CSRs" do + @ca.should respond_to(:autosign) + end + + describe "when autosigning certificates" do + it "should do nothing if autosign is disabled" do + Puppet.settings.expects(:value).with(:autosign).returns 'false' + + Puppet::SSL::CertificateRequest.expects(:search).never + @ca.autosign + end + + it "should do nothing if no autosign.conf exists" do + Puppet.settings.expects(:value).with(:autosign).returns '/auto/sign' + FileTest.expects(:exist?).with("/auto/sign").returns false + + Puppet::SSL::CertificateRequest.expects(:search).never + @ca.autosign + end + + describe "and autosign is enabled and the autosign.conf file exists" do + before do + Puppet.settings.stubs(:value).with(:autosign).returns '/auto/sign' + FileTest.stubs(:exist?).with("/auto/sign").returns true + File.stubs(:readlines).with("/auto/sign").returns ["one\n", "two\n"] + + Puppet::SSL::CertificateRequest.stubs(:search).returns [] + + @store = stub 'store', :allow => nil + Puppet::Network::AuthStore.stubs(:new).returns @store + end + + describe "when creating the AuthStore instance to verify autosigning" do + it "should create an AuthStore with each line in the configuration file allowed to be autosigned" do + Puppet::Network::AuthStore.expects(:new).returns @store + + @store.expects(:allow).with("one") + @store.expects(:allow).with("two") + + @ca.autosign + end + + it "should reparse the autosign configuration on each call" do + Puppet::Network::AuthStore.expects(:new).times(2).returns @store + + @ca.autosign + @ca.autosign + end + + it "should ignore comments" do + File.stubs(:readlines).with("/auto/sign").returns ["one\n", "#two\n"] + + @store.expects(:allow).with("one") + @ca.autosign + end + + it "should ignore blank lines" do + File.stubs(:readlines).with("/auto/sign").returns ["one\n", "\n"] + + @store.expects(:allow).with("one") + @ca.autosign + end + end + + it "should sign all CSRs whose hostname matches the autosign configuration" do + csr1 = mock 'csr1' + csr2 = mock 'csr2' + Puppet::SSL::CertificateRequest.stubs(:search).returns [csr1, csr2] + end + + it "should not sign CSRs whose hostname does not match the autosign configuration" do + csr1 = mock 'csr1' + csr2 = mock 'csr2' + Puppet::SSL::CertificateRequest.stubs(:search).returns [csr1, csr2] + end + end + end + end + + describe "when managing certificate clients" do + before do + Puppet.settings.stubs(:use) + + Puppet::SSL::CertificateAuthority.any_instance.stubs(:password?).returns true + + # Set up the CA + @key = mock 'key' + @key.stubs(:content).returns "cakey" + @host = stub 'host', :key => @key + Puppet::SSL::CertificateAuthority.any_instance.stubs(:host).returns @host + + @cacert = mock 'certificate' + @cacert.stubs(:content).returns "cacertificate" + @ca = Puppet::SSL::CertificateAuthority.new + end + + it "should have a method for acting on the SSL files" do + @ca.should respond_to(:apply) + end + + describe "when applying a method to a set of hosts" do + it "should fail if no subjects have been specified" do + lambda { @ca.apply(:generate) }.should raise_error(ArgumentError) + end + + it "should create an Interface instance with the specified method and the subjects" do + Puppet::SSL::CertificateAuthority::Interface.expects(:new).with(:generate, :hosts).returns(stub('applier', :apply => nil)) + @ca.apply(:generate, :to => :hosts) + end + + it "should apply the Interface with itself as the argument" do + applier = stub('applier') + applier.expects(:apply).with(@ca) + Puppet::SSL::CertificateAuthority::Interface.expects(:new).returns applier + @ca.apply(:generate, :to => :ca_testing) + end + end + + it "should be able to list waiting certificate requests" do + req1 = stub 'req1', :name => "one" + req2 = stub 'req2', :name => "two" + Puppet::SSL::CertificateRequest.expects(:search).with("*").returns [req1, req2] + + @ca.waiting?.should == %w{one two} + end + + it "should delegate removing hosts to the Host class" do + Puppet::SSL::Host.expects(:destroy).with("myhost") + + @ca.destroy("myhost") + end + + it "should be able to verify certificates" do + @ca.should respond_to(:verify) + end + + it "should list certificates as the sorted list of all existing signed certificates" do + cert1 = stub 'cert1', :name => "cert1" + cert2 = stub 'cert2', :name => "cert2" + Puppet::SSL::Certificate.expects(:search).with("*").returns [cert1, cert2] + @ca.list.should == %w{cert1 cert2} + end + + describe "and printing certificates" do + it "should return nil if the certificate cannot be found" do + Puppet::SSL::Certificate.expects(:find).with("myhost").returns nil + @ca.print("myhost").should be_nil + end + + it "should print certificates by calling :to_text on the host's certificate" do + cert1 = stub 'cert1', :name => "cert1", :to_text => "mytext" + Puppet::SSL::Certificate.expects(:find).with("myhost").returns cert1 + @ca.print("myhost").should == "mytext" + end + end + + describe "and verifying certificates" do + before do + @store = stub 'store', :verify => true, :add_file => nil, :purpose= => nil, :add_crl => true + + OpenSSL::X509::Store.stubs(:new).returns @store + + Puppet.settings.stubs(:value).returns "crtstuff" + + @cert = stub 'cert', :content => "mycert" + Puppet::SSL::Certificate.stubs(:find).returns @cert + + @crl = stub('crl', :content => "mycrl") + + @ca.stubs(:crl).returns @crl + end + + it "should fail if the host's certificate cannot be found" do + Puppet::SSL::Certificate.expects(:find).with("me").returns(nil) + + lambda { @ca.verify("me") }.should raise_error(ArgumentError) + end + + it "should create an SSL Store to verify" do + OpenSSL::X509::Store.expects(:new).returns @store + + @ca.verify("me") + end + + it "should add the CA Certificate to the store" do + Puppet.settings.stubs(:value).with(:cacert).returns "/ca/cert" + @store.expects(:add_file).with "/ca/cert" + + @ca.verify("me") + end + + it "should add the CRL to the store if the crl is enabled" do + @store.expects(:add_crl).with "mycrl" + + @ca.verify("me") + end + + it "should set the store purpose to OpenSSL::X509::PURPOSE_SSL_CLIENT" do + Puppet.settings.stubs(:value).with(:cacert).returns "/ca/cert" + @store.expects(:add_file).with "/ca/cert" + + @ca.verify("me") + end + + it "should use the store to verify the certificate" do + @cert.expects(:content).returns "mycert" + + @store.expects(:verify).with("mycert").returns true + + @ca.verify("me") + end + + it "should fail if the verification returns false" do + @cert.expects(:content).returns "mycert" + + @store.expects(:verify).with("mycert").returns false + + lambda { @ca.verify("me") }.should raise_error + end + end + + describe "and revoking certificates" do + before do + @crl = mock 'crl' + @ca.stubs(:crl).returns @crl + + @ca.stubs(:next_serial).returns 10 + + @real_cert = stub 'real_cert', :serial => 15 + @cert = stub 'cert', :content => @real_cert + Puppet::SSL::Certificate.stubs(:find).returns @cert + + end + + it "should fail if the certificate revocation list is disabled" do + @ca.stubs(:crl).returns false + + lambda { @ca.revoke('ca_testing') }.should raise_error(ArgumentError) + + end + + it "should delegate the revocation to its CRL" do + @ca.crl.expects(:revoke) + + @ca.revoke('host') + end + + it "should get the serial number from the local certificate if it exists" do + @ca.crl.expects(:revoke).with { |serial, key| serial == 15 } + + Puppet::SSL::Certificate.expects(:find).with("host").returns @cert + + @ca.revoke('host') + end + + it "should get the serial number from inventory if no local certificate exists" do + real_cert = stub 'real_cert', :serial => 15 + cert = stub 'cert', :content => real_cert + Puppet::SSL::Certificate.expects(:find).with("host").returns nil + + @ca.inventory.expects(:serial).with("host").returns 16 + + @ca.crl.expects(:revoke).with { |serial, key| serial == 16 } + @ca.revoke('host') + end + end + + it "should be able to generate a complete new SSL host" do + @ca.should respond_to(:generate) + end + + describe "and generating certificates" do + before do + @host = stub 'host', :generate_certificate_request => nil + Puppet::SSL::Host.stubs(:new).returns @host + Puppet::SSL::Certificate.stubs(:find).returns nil + + @ca.stubs(:sign) + end + + it "should fail if a certificate already exists for the host" do + Puppet::SSL::Certificate.expects(:find).with("him").returns "something" + + lambda { @ca.generate("him") }.should raise_error(ArgumentError) + end + + it "should create a new Host instance with the correct name" do + Puppet::SSL::Host.expects(:new).with("him").returns @host + + @ca.generate("him") + end + + it "should use the Host to generate the certificate request" do + @host.expects :generate_certificate_request + + @ca.generate("him") + end + + it "should sign the generated request" do + @ca.expects(:sign).with("him") + + @ca.generate("him") + end + end + end +end diff --git a/spec/unit/ssl/certificate_authority/interface.rb b/spec/unit/ssl/certificate_authority/interface.rb new file mode 100755 index 000000000..617cfa6ba --- /dev/null +++ b/spec/unit/ssl/certificate_authority/interface.rb @@ -0,0 +1,265 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/ssl/certificate_authority' + +describe "a normal interface method", :shared => true do + it "should call the method on the CA for each host specified if an array was provided" do + @ca.expects(@method).with("host1") + @ca.expects(@method).with("host2") + + @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, %w{host1 host2}) + + @applier.apply(@ca) + end + + it "should call the method on the CA for all existing certificates if :all was provided" do + @ca.expects(:list).returns %w{host1 host2} + + @ca.expects(@method).with("host1") + @ca.expects(@method).with("host2") + + @applier = Puppet::SSL::CertificateAuthority::Interface.new(@method, :all) + + @applier.apply(@ca) + end +end + +describe Puppet::SSL::CertificateAuthority::Interface do + before do + @class = Puppet::SSL::CertificateAuthority::Interface + end + describe "when initializing" do + it "should set its method using its settor" do + @class.any_instance.expects(:method=).with(:generate) + @class.new(:generate, :all) + end + + it "should set its subjects using the settor" do + @class.any_instance.expects(:subjects=).with(:all) + @class.new(:generate, :all) + end + end + + describe "when setting the method" do + it "should set the method" do + @class.new(:generate, :all).method.should == :generate + end + + it "should fail if the method isn't a member of the INTERFACE_METHODS array" do + Puppet::SSL::CertificateAuthority::Interface::INTERFACE_METHODS.expects(:include?).with(:thing).returns false + + lambda { @class.new(:thing, :all) }.should raise_error(ArgumentError) + end + end + + describe "when setting the subjects" do + it "should set the subjects" do + @class.new(:generate, :all).subjects.should == :all + end + + it "should fail if the subjects setting isn't :all or an array" do + lambda { @class.new(:generate, "other") }.should raise_error(ArgumentError) + end + end + + it "should have a method for triggering the application" do + @class.new(:generate, :all).should respond_to(:apply) + end + + describe "when applying" do + before do + # We use a real object here, because :verify can't be stubbed, apparently. + @ca = Object.new + end + + it "should raise InterfaceErrors" do + @applier = @class.new(:revoke, :all) + + @ca.expects(:list).raises Puppet::SSL::CertificateAuthority::Interface::InterfaceError + + lambda { @applier.apply(@ca) }.should raise_error(Puppet::SSL::CertificateAuthority::Interface::InterfaceError) + end + + it "should log non-Interface failures rather than failing" do + @applier = @class.new(:revoke, :all) + + @ca.expects(:list).raises ArgumentError + + Puppet.expects(:err) + + lambda { @applier.apply(@ca) }.should_not raise_error + end + + describe "with an empty array specified and the method is not list" do + it "should fail" do + @applier = @class.new(:sign, []) + lambda { @applier.apply(@ca) }.should raise_error(ArgumentError) + end + end + + describe ":generate" do + it "should fail if :all was specified" do + @applier = @class.new(:generate, :all) + lambda { @applier.apply(@ca) }.should raise_error(ArgumentError) + end + + it "should call :generate on the CA for each host specified" do + @applier = @class.new(:generate, %w{host1 host2}) + + @ca.expects(:generate).with("host1") + @ca.expects(:generate).with("host2") + + @applier.apply(@ca) + end + end + + describe ":verify" do + before { @method = :verify } + #it_should_behave_like "a normal interface method" + + it "should call the method on the CA for each host specified if an array was provided" do + # LAK:NOTE Mocha apparently doesn't allow you to mock :verify, but I'm confident this works in real life. + end + + it "should call the method on the CA for all existing certificates if :all was provided" do + # LAK:NOTE Mocha apparently doesn't allow you to mock :verify, but I'm confident this works in real life. + end + end + + describe ":destroy" do + before { @method = :destroy } + it_should_behave_like "a normal interface method" + end + + describe ":revoke" do + before { @method = :revoke } + it_should_behave_like "a normal interface method" + end + + describe ":sign" do + describe "and an array of names was provided" do + before do + @applier = @class.new(:sign, %w{host1 host2}) + end + + it "should sign the specified waiting certificate requests" do + @ca.expects(:sign).with("host1") + @ca.expects(:sign).with("host2") + + @applier.apply(@ca) + end + end + + describe "and :all was provided" do + it "should sign all waiting certificate requests" do + @ca.stubs(:waiting?).returns(%w{cert1 cert2}) + + @ca.expects(:sign).with("cert1") + @ca.expects(:sign).with("cert2") + + @applier = @class.new(:sign, :all) + @applier.apply(@ca) + end + + it "should fail if there are no waiting certificate requests" do + @ca.stubs(:waiting?).returns([]) + + @applier = @class.new(:sign, :all) + lambda { @applier.apply(@ca) }.should raise_error(Puppet::SSL::CertificateAuthority::Interface::InterfaceError) + end + end + end + + describe ":list" do + describe "and an empty array was provided" do + it "should print a string containing all certificate requests" do + @ca.expects(:waiting?).returns %w{host1 host2} + + @applier = @class.new(:list, []) + + @applier.expects(:puts).with "host1\nhost2" + + @applier.apply(@ca) + end + end + + describe "and :all was provided" do + it "should print a string containing all certificate requests and certificates" do + @ca.expects(:waiting?).returns %w{host1 host2} + @ca.expects(:list).returns %w{host3 host4} + + @applier = @class.new(:list, :all) + + @applier.expects(:puts).with "host1" + @applier.expects(:puts).with "host2" + @applier.expects(:puts).with "+ host3" + @applier.expects(:puts).with "+ host4" + + @applier.apply(@ca) + end + end + + describe "and an array of names was provided" do + it "should print a string of all named hosts that have a waiting request" do + @ca.expects(:waiting?).returns %w{host1 host2} + @ca.expects(:list).returns %w{host3 host4} + + @applier = @class.new(:list, %w{host1 host2 host3 host4}) + + @applier.expects(:puts).with "host1" + @applier.expects(:puts).with "host2" + @applier.expects(:puts).with "+ host3" + @applier.expects(:puts).with "+ host4" + + @applier.apply(@ca) + end + end + end + + describe ":print" do + describe "and :all was provided" do + it "should print all certificates" do + @ca.expects(:list).returns %w{host1 host2} + + @applier = @class.new(:print, :all) + + @ca.expects(:print).with("host1").returns "h1" + @applier.expects(:puts).with "h1" + + @ca.expects(:print).with("host2").returns "h2" + @applier.expects(:puts).with "h2" + + @applier.apply(@ca) + end + end + + describe "and an array of names was provided" do + it "should print each named certificate if found" do + @applier = @class.new(:print, %w{host1 host2}) + + @ca.expects(:print).with("host1").returns "h1" + @applier.expects(:puts).with "h1" + + @ca.expects(:print).with("host2").returns "h2" + @applier.expects(:puts).with "h2" + + @applier.apply(@ca) + end + + it "should log any named but not found certificates" do + @applier = @class.new(:print, %w{host1 host2}) + + @ca.expects(:print).with("host1").returns "h1" + @applier.expects(:puts).with "h1" + + @ca.expects(:print).with("host2").returns nil + Puppet.expects(:err).with { |msg| msg.include?("host2") } + + @applier.apply(@ca) + end + end + end + end +end diff --git a/spec/unit/ssl/certificate_factory.rb b/spec/unit/ssl/certificate_factory.rb new file mode 100755 index 000000000..822b330f2 --- /dev/null +++ b/spec/unit/ssl/certificate_factory.rb @@ -0,0 +1,107 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/ssl/certificate_factory' + +describe Puppet::SSL::CertificateFactory do + before do + @cert_type = mock 'cert_type' + @name = mock 'name' + @csr = stub 'csr', :subject => @name + @issuer = mock 'issuer' + @serial = mock 'serial' + + @factory = Puppet::SSL::CertificateFactory.new(@cert_type, @csr, @issuer, @serial) + end + + describe "when initializing" do + it "should set its :cert_type to its first argument" do + @factory.cert_type.should equal(@cert_type) + end + + it "should set its :csr to its second argument" do + @factory.csr.should equal(@csr) + end + + it "should set its :issuer to its third argument" do + @factory.issuer.should equal(@issuer) + end + + it "should set its :serial to its fourth argument" do + @factory.serial.should equal(@serial) + end + + it "should set its name to the subject of the csr" do + @factory.name.should equal(@name) + end + end + + describe "when generating the certificate" do + before do + @cert = mock 'cert' + + @cert.stub_everything + + @factory.stubs :build_extensions + + @factory.stubs :set_ttl + + @issuer_name = mock 'issuer_name' + @issuer.stubs(:subject).returns @issuer_name + + @public_key = mock 'public_key' + @csr.stubs(:public_key).returns @public_key + + OpenSSL::X509::Certificate.stubs(:new).returns @cert + end + + it "should return a new X509 certificate" do + OpenSSL::X509::Certificate.expects(:new).returns @cert + @factory.result.should equal(@cert) + end + + it "should set the certificate's version to 2" do + @cert.expects(:version=).with 2 + @factory.result + end + + it "should set the certificate's subject to the CSR's subject" do + @cert.expects(:subject=).with @name + @factory.result + end + + it "should set the certificate's issuer to the Issuer's subject" do + @cert.expects(:issuer=).with @issuer_name + @factory.result + end + + it "should set the certificate's public key to the CSR's public key" do + @cert.expects(:public_key=).with @public_key + @factory.result + end + + it "should set the certificate's serial number to the provided serial number" do + @cert.expects(:serial=).with @serial + @factory.result + end + + it "should build extensions for the certificate" do + @factory.expects(:build_extensions) + @factory.result + end + + it "should set the ttl of the certificate" do + @factory.expects(:set_ttl) + @factory.result + end + end + + describe "when building extensions" do + it "should have tests" + end + + describe "when setting the ttl" do + it "should have tests" + end +end diff --git a/spec/unit/ssl/certificate_request.rb b/spec/unit/ssl/certificate_request.rb new file mode 100755 index 000000000..351e5bbd5 --- /dev/null +++ b/spec/unit/ssl/certificate_request.rb @@ -0,0 +1,167 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/ssl/certificate_request' +require 'puppet/ssl/key' + +describe Puppet::SSL::CertificateRequest do + before do + @class = Puppet::SSL::CertificateRequest + end + + it "should be extended with the Indirector module" do + @class.metaclass.should be_include(Puppet::Indirector) + end + + it "should indirect certificate_request" do + @class.indirection.name.should == :certificate_request + end + + it "should use any provided name as its name" do + @class.new("myname").name.should == "myname" + end + + it "should default to the :file terminus" do + @class.indirection.terminus_class.should == :file + end + + describe "when managing instances" do + before do + @request = @class.new("myname") + end + + it "should have a name attribute" do + @request.name.should == "myname" + end + + it "should have a content attribute" do + @request.should respond_to(:content) + end + + it "should be able to read requests from disk" do + path = "/my/path" + File.expects(:read).with(path).returns("my request") + request = mock 'request' + OpenSSL::X509::Request.expects(:new).with("my request").returns(request) + @request.read(path).should equal(request) + @request.content.should equal(request) + end + + it "should return an empty string when converted to a string with no request" do + @request.to_s.should == "" + end + + it "should convert the request to pem format when converted to a string" do + request = mock 'request', :to_pem => "pem" + @request.content = request + @request.to_s.should == "pem" + end + + it "should have a :to_text method that it delegates to the actual key" do + real_request = mock 'request' + real_request.expects(:to_text).returns "requesttext" + @request.content = real_request + @request.to_text.should == "requesttext" + end + end + + describe "when generating" do + before do + @instance = @class.new("myname") + + key = Puppet::SSL::Key.new("myname") + @key = key.generate + + @request = OpenSSL::X509::Request.new + OpenSSL::X509::Request.expects(:new).returns(@request) + + @request.stubs(:verify).returns(true) + end + + it "should use the content of the provided key if the key is a Puppet::SSL::Key instance" do + key = Puppet::SSL::Key.new("test") + key.expects(:content).returns @key + + @request.expects(:sign).with{ |key, digest| key == @key } + @instance.generate(key) + end + + it "should log that it is creating a new certificate request" do + Puppet.expects(:info) + @instance.generate(@key) + end + + it "should set the subject to [CN, name]" do + subject = mock 'subject' + OpenSSL::X509::Name.expects(:new).with([["CN", @instance.name]]).returns(subject) + @request.expects(:subject=).with(subject) + @instance.generate(@key) + end + + it "should set the version to 0" do + @request.expects(:version=).with(0) + @instance.generate(@key) + end + + it "should set the public key to the provided key's public key" do + # Yay, the private key extracts a new key each time. + pubkey = @key.public_key + @key.stubs(:public_key).returns pubkey + @request.expects(:public_key=).with(@key.public_key) + @instance.generate(@key) + end + + it "should sign the csr with the provided key and a digest" do + digest = mock 'digest' + OpenSSL::Digest::MD5.expects(:new).returns(digest) + @request.expects(:sign).with(@key, digest) + @instance.generate(@key) + end + + it "should verify the generated request using the public key" do + @request.expects(:verify).with(@key.public_key) + @instance.generate(@key) + end + + it "should fail if verification fails" do + @request.expects(:verify).returns false + + lambda { @instance.generate(@key) }.should raise_error(Puppet::Error) + end + + it "should return the generated request" do + @instance.generate(@key).should equal(@request) + end + + it "should set its content to the generated request" do + @instance.generate(@key) + @instance.content.should equal(@request) + end + end + + describe "when a CSR is saved" do + describe "and a CA is available" do + it "should save the CSR and trigger autosigning" do + ca = mock 'ca', :autosign + Puppet::SSL::CertificateAuthority.expects(:instance).returns ca + + csr = Puppet::SSL::CertificateRequest.new("me") + Puppet::SSL::CertificateRequest.indirection.expects(:save).with(csr) + + csr.save + end + end + + describe "and a CA is not available" do + it "should save the CSR" do + Puppet::SSL::CertificateAuthority.expects(:instance).returns nil + + csr = Puppet::SSL::CertificateRequest.new("me") + Puppet::SSL::CertificateRequest.indirection.expects(:save).with(csr) + + csr.save + end + end + end +end diff --git a/spec/unit/ssl/certificate_revocation_list.rb b/spec/unit/ssl/certificate_revocation_list.rb new file mode 100755 index 000000000..13febf744 --- /dev/null +++ b/spec/unit/ssl/certificate_revocation_list.rb @@ -0,0 +1,166 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/ssl/certificate_revocation_list' + +describe Puppet::SSL::CertificateRevocationList do + before do + @cert = stub 'cert', :subject => "mysubject" + @key = stub 'key', :private? => true + + @class = Puppet::SSL::CertificateRevocationList + end + + it "should default to the :file terminus" do + @class.indirection.terminus_class.should == :file + end + + describe "when an instance" do + before do + @class.any_instance.stubs(:read_or_generate) + + @crl = @class.new("whatever") + end + + it "should always use 'crl' for its name" do + @crl.name.should == "crl" + end + + it "should have a content attribute" do + @crl.should respond_to(:content) + end + end + + describe "when initializing" do + it "should fail if :cacrl is set to false" do + Puppet.settings.expects(:value).with(:cacrl).returns false + lambda { @class.new("crl") }.should raise_error(Puppet::Error) + end + + it "should fail if :cacrl is set to the string 'false'" do + Puppet.settings.expects(:value).with(:cacrl).returns "false" + lambda { @class.new("crl") }.should raise_error(Puppet::Error) + end + end + + describe "when generating the crl" do + before do + @real_crl = mock 'crl' + @real_crl.stub_everything + + OpenSSL::X509::CRL.stubs(:new).returns(@real_crl) + + @class.any_instance.stubs(:read_or_generate) + + @crl = @class.new("crl") + end + + it "should set its issuer to the subject of the passed certificate" do + @real_crl.expects(:issuer=).with(@cert.subject) + + @crl.generate(@cert, @key) + end + + it "should set its version to 1" do + @real_crl.expects(:version=).with(1) + + @crl.generate(@cert, @key) + end + + it "should create an instance of OpenSSL::X509::CRL" do + OpenSSL::X509::CRL.expects(:new).returns(@real_crl) + + @crl.generate(@cert, @key) + end + + # The next three tests aren't good, but at least they + # specify the behaviour. + it "should add an extension for the CRL number" do + @real_crl.expects(:extensions=) + @crl.generate(@cert, @key) + end + + it "should set the last update time" do + @real_crl.expects(:last_update=) + @crl.generate(@cert, @key) + end + + it "should set the next update time" do + @real_crl.expects(:next_update=) + @crl.generate(@cert, @key) + end + + it "should sign the CRL" do + @real_crl.expects(:sign).with { |key, digest| key == @key } + @crl.generate(@cert, @key) + end + + it "should set the content to the generated crl" do + @crl.generate(@cert, @key) + @crl.content.should equal(@real_crl) + end + + it "should return the generated crl" do + @crl.generate(@cert, @key).should equal(@real_crl) + end + end + + # This test suite isn't exactly complete, because the + # SSL stuff is very complicated. It just hits the high points. + describe "when revoking a certificate" do + before do + @class.wrapped_class.any_instance.stubs(:issuer=) + @class.wrapped_class.any_instance.stubs(:sign) + + @crl = @class.new("crl") + @crl.generate(@cert, @key) + @crl.content.stubs(:sign) + + @crl.stubs :save + + @key = mock 'key' + end + + it "should require a serial number and the CA's private key" do + lambda { @crl.revoke }.should raise_error(ArgumentError) + end + + it "should default to OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE as the revocation reason" do + # This makes it a bit more of an integration test than we'd normally like, but that's life + # with openssl. + reason = OpenSSL::ASN1::Enumerated(OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE) + OpenSSL::ASN1.expects(:Enumerated).with(OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE).returns reason + + @crl.revoke(1, @key) + end + + it "should mark the CRL as updated" do + time = Time.now + Time.stubs(:now).returns time + + @crl.content.expects(:last_update=).with(time) + + @crl.revoke(1, @key) + end + + it "should mark the CRL valid for five years" do + time = Time.now + Time.stubs(:now).returns time + + @crl.content.expects(:next_update=).with(time + (5 * 365*24*60*60)) + + @crl.revoke(1, @key) + end + + it "should sign the CRL with the CA's private key and a digest instance" do + @crl.content.expects(:sign).with { |key, digest| key == @key and digest.is_a?(OpenSSL::Digest::SHA1) } + @crl.revoke(1, @key) + end + + it "should save the CRL" do + @crl.expects :save + @crl.revoke(1, @key) + end + end +end diff --git a/spec/unit/ssl/host.rb b/spec/unit/ssl/host.rb new file mode 100755 index 000000000..b717da80e --- /dev/null +++ b/spec/unit/ssl/host.rb @@ -0,0 +1,431 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/ssl/host' + +describe Puppet::SSL::Host do + before do + @class = Puppet::SSL::Host + @host = @class.new("myname") + end + + it "should use any provided name as its name" do + @host.name.should == "myname" + end + + it "should retrieve its public key from its private key" do + realkey = mock 'realkey' + key = stub 'key', :content => realkey + Puppet::SSL::Key.stubs(:find).returns(key) + pubkey = mock 'public_key' + realkey.expects(:public_key).returns pubkey + + @host.public_key.should equal(pubkey) + end + + it "should default to being a non-ca host" do + @host.ca?.should be_false + end + + it "should be a ca host if its name matches the CA_NAME" do + Puppet::SSL::Host.stubs(:ca_name).returns "yayca" + Puppet::SSL::Host.new("yayca").should be_ca + end + + it "should have a method for determining the CA location" do + Puppet::SSL::Host.should respond_to(:ca_location) + end + + it "should have a method for specifying the CA location" do + Puppet::SSL::Host.should respond_to(:ca_location=) + end + + describe "when specifying the CA location" do + before do + [Puppet::SSL::Key, Puppet::SSL::Certificate, Puppet::SSL::CertificateRequest, Puppet::SSL::CertificateRevocationList].each do |klass| + klass.stubs(:terminus_class=) + klass.stubs(:cache_class=) + end + end + + it "should support the location ':local'" do + lambda { Puppet::SSL::Host.ca_location = :local }.should_not raise_error + end + + it "should support the location ':remote'" do + lambda { Puppet::SSL::Host.ca_location = :remote }.should_not raise_error + end + + it "should support the location ':none'" do + lambda { Puppet::SSL::Host.ca_location = :none }.should_not raise_error + end + + it "should not support other modes" do + lambda { Puppet::SSL::Host.ca_location = :whatever }.should raise_error(ArgumentError) + end + + describe "as 'local'" do + it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest as :file" do + Puppet::SSL::Certificate.expects(:cache_class=).with :file + Puppet::SSL::CertificateRequest.expects(:cache_class=).with :file + Puppet::SSL::CertificateRevocationList.expects(:cache_class=).with :file + + Puppet::SSL::Host.ca_location = :local + end + + it "should set the terminus class for Key as :file" do + Puppet::SSL::Key.expects(:terminus_class=).with :file + + Puppet::SSL::Host.ca_location = :local + end + + it "should set the terminus class for Certificate, CertificateRevocationList, and CertificateRequest as :ca" do + Puppet::SSL::Certificate.expects(:terminus_class=).with :ca + Puppet::SSL::CertificateRequest.expects(:terminus_class=).with :ca + Puppet::SSL::CertificateRevocationList.expects(:terminus_class=).with :ca + + Puppet::SSL::Host.ca_location = :local + end + end + + describe "as 'remote'" do + it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest as :file" do + Puppet::SSL::Certificate.expects(:cache_class=).with :file + Puppet::SSL::CertificateRequest.expects(:cache_class=).with :file + Puppet::SSL::CertificateRevocationList.expects(:cache_class=).with :file + + Puppet::SSL::Host.ca_location = :remote + end + + it "should set the terminus class for Key as :file" do + Puppet::SSL::Key.expects(:terminus_class=).with :file + + Puppet::SSL::Host.ca_location = :remote + end + + it "should set the terminus class for Certificate, CertificateRevocationList, and CertificateRequest as :rest" do + Puppet::SSL::Certificate.expects(:terminus_class=).with :rest + Puppet::SSL::CertificateRequest.expects(:terminus_class=).with :rest + Puppet::SSL::CertificateRevocationList.expects(:terminus_class=).with :rest + + Puppet::SSL::Host.ca_location = :remote + end + end + + describe "as 'none'" do + it "should set the terminus class for Key, Certificate, CertificateRevocationList, and CertificateRequest as :file" do + Puppet::SSL::Key.expects(:terminus_class=).with :file + Puppet::SSL::Certificate.expects(:terminus_class=).with :file + Puppet::SSL::CertificateRequest.expects(:terminus_class=).with :file + Puppet::SSL::CertificateRevocationList.expects(:terminus_class=).with :file + + Puppet::SSL::Host.ca_location = :none + end + end + end + + it "should have a class method for destroying all files related to a given host" do + Puppet::SSL::Host.should respond_to(:destroy) + end + + describe "when destroying a host's SSL files" do + before do + Puppet::SSL::Key.stubs(:destroy).returns false + Puppet::SSL::Certificate.stubs(:destroy).returns false + Puppet::SSL::CertificateRequest.stubs(:destroy).returns false + end + + it "should destroy its certificate, certificate request, and key" do + Puppet::SSL::Key.expects(:destroy).with("myhost") + Puppet::SSL::Certificate.expects(:destroy).with("myhost") + Puppet::SSL::CertificateRequest.expects(:destroy).with("myhost") + + Puppet::SSL::Host.destroy("myhost") + end + + it "should return true if any of the classes returned true" do + Puppet::SSL::Certificate.expects(:destroy).with("myhost").returns true + + Puppet::SSL::Host.destroy("myhost").should be_true + end + + it "should return false if none of the classes returned true" do + Puppet::SSL::Host.destroy("myhost").should be_false + end + end + + describe "when initializing" do + it "should default its name to the :certname setting" do + Puppet.settings.expects(:value).with(:certname).returns "myname" + + Puppet::SSL::Host.new.name.should == "myname" + end + + it "should indicate that it is a CA host if its name matches the ca_name constant" do + Puppet::SSL::Host.stubs(:ca_name).returns "myca" + Puppet::SSL::Host.new("myca").should be_ca + end + end + + describe "when managing its private key" do + before do + @realkey = "mykey" + @key = stub 'key', :content => @realkey + end + + it "should return nil if the key is not set and cannot be found" do + Puppet::SSL::Key.expects(:find).with("myname").returns(nil) + @host.key.should be_nil + end + + it "should find the key in the Key class and return the Puppet instance" do + Puppet::SSL::Key.expects(:find).with("myname").returns(@key) + @host.key.should equal(@key) + end + + it "should be able to generate and save a new key" do + Puppet::SSL::Key.expects(:new).with("myname").returns(@key) + + @key.expects(:generate) + @key.expects(:save) + + @host.generate_key.should be_true + @host.key.should equal(@key) + end + + it "should return any previously found key without requerying" do + Puppet::SSL::Key.expects(:find).with("myname").returns(@key).once + @host.key.should equal(@key) + @host.key.should equal(@key) + end + end + + describe "when managing its certificate request" do + before do + @realrequest = "real request" + @request = stub 'request', :content => @realrequest + end + + it "should return nil if the key is not set and cannot be found" do + Puppet::SSL::CertificateRequest.expects(:find).with("myname").returns(nil) + @host.certificate_request.should be_nil + end + + it "should find the request in the Key class and return it and return the Puppet SSL request" do + Puppet::SSL::CertificateRequest.expects(:find).with("myname").returns @request + + @host.certificate_request.should equal(@request) + end + + it "should generate a new key when generating the cert request if no key exists" do + Puppet::SSL::CertificateRequest.expects(:new).with("myname").returns @request + + key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" + + @host.expects(:key).times(2).returns(nil).then.returns(key) + @host.expects(:generate_key).returns(key) + + @request.stubs(:generate) + @request.stubs(:save) + + @host.generate_certificate_request + end + + it "should be able to generate and save a new request using the private key" do + Puppet::SSL::CertificateRequest.expects(:new).with("myname").returns @request + + key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" + @host.stubs(:key).returns(key) + @request.expects(:generate).with("mycontent") + @request.expects(:save) + + @host.generate_certificate_request.should be_true + @host.certificate_request.should equal(@request) + end + + it "should return any previously found request without requerying" do + Puppet::SSL::CertificateRequest.expects(:find).with("myname").returns(@request).once + + @host.certificate_request.should equal(@request) + @host.certificate_request.should equal(@request) + end + end + + describe "when managing its certificate" do + before do + @realcert = mock 'certificate' + @cert = stub 'cert', :content => @realcert + end + + it "should find the certificate in the Certificate class and return the Puppet certificate instance" do + Puppet::SSL::Certificate.expects(:find).with("myname").returns @cert + + @host.certificate.should equal(@cert) + end + + it "should return any previously found certificate" do + Puppet::SSL::Certificate.expects(:find).with("myname").returns(@cert).once + + @host.certificate.should equal(@cert) + @host.certificate.should equal(@cert) + end + end + + it "should have a method for listing certificate hosts" do + Puppet::SSL::Host.should respond_to(:search) + end + + describe "when listing certificate hosts" do + it "should default to listing all clients with any file types" do + Puppet::SSL::Key.expects(:search).returns [] + Puppet::SSL::Certificate.expects(:search).returns [] + Puppet::SSL::CertificateRequest.expects(:search).returns [] + Puppet::SSL::Host.search + end + + it "should be able to list only clients with a key" do + Puppet::SSL::Key.expects(:search).returns [] + Puppet::SSL::Certificate.expects(:search).never + Puppet::SSL::CertificateRequest.expects(:search).never + Puppet::SSL::Host.search :for => Puppet::SSL::Key + end + + it "should be able to list only clients with a certificate" do + Puppet::SSL::Key.expects(:search).never + Puppet::SSL::Certificate.expects(:search).returns [] + Puppet::SSL::CertificateRequest.expects(:search).never + Puppet::SSL::Host.search :for => Puppet::SSL::Certificate + end + + it "should be able to list only clients with a certificate request" do + Puppet::SSL::Key.expects(:search).never + Puppet::SSL::Certificate.expects(:search).never + Puppet::SSL::CertificateRequest.expects(:search).returns [] + Puppet::SSL::Host.search :for => Puppet::SSL::CertificateRequest + end + + it "should return a Host instance created with the name of each found instance" do + key = stub 'key', :name => "key" + cert = stub 'cert', :name => "cert" + csr = stub 'csr', :name => "csr" + + Puppet::SSL::Key.expects(:search).returns [key] + Puppet::SSL::Certificate.expects(:search).returns [cert] + Puppet::SSL::CertificateRequest.expects(:search).returns [csr] + + returned = [] + %w{key cert csr}.each do |name| + result = mock(name) + returned << result + Puppet::SSL::Host.expects(:new).with(name).returns result + end + + result = Puppet::SSL::Host.search + returned.each do |r| + result.should be_include(r) + end + end + end + + it "should have a method for generating all necessary files" do + Puppet::SSL::Host.new("me").should respond_to(:generate) + end + + describe "when generating files" do + before do + @host = Puppet::SSL::Host.new("me") + @host.stubs(:generate_key) + @host.stubs(:generate_certificate_request) + end + + it "should generate a key if one is not present" do + @host.expects(:key).returns nil + @host.expects(:generate_key) + + @host.generate + end + + it "should generate a certificate request if one is not present" do + @host.expects(:certificate_request).returns nil + @host.expects(:generate_certificate_request) + + @host.generate + end + + describe "and it can create a certificate authority" do + before do + @ca = mock 'ca' + Puppet::SSL::CertificateAuthority.stubs(:instance).returns @ca + end + + it "should use the CA to sign its certificate request if it does not have a certificate" do + @host.expects(:certificate).returns nil + + @ca.expects(:sign).with(@host.name) + + @host.generate + end + end + + describe "and it cannot create a certificate authority" do + before do + Puppet::SSL::CertificateAuthority.stubs(:instance).returns nil + end + + it "should seek its certificate" do + @host.expects(:certificate) + + @host.generate + end + end + end + + it "should have a method for creating an SSL store" do + Puppet::SSL::Host.new("me").should respond_to(:ssl_store) + end + + describe "when creating an SSL store" do + before do + @host = Puppet::SSL::Host.new("me") + @store = mock 'store' + @store.stub_everything + OpenSSL::X509::Store.stubs(:new).returns @store + + Puppet.settings.stubs(:value).returns "ssl_host_testing" + end + + it "should accept a purpose" do + @store.expects(:purpose=).with "my special purpose" + @host.ssl_store("my special purpose") + end + + it "should default to OpenSSL::X509::PURPOSE_ANY as the purpose" do + @store.expects(:purpose=).with OpenSSL::X509::PURPOSE_ANY + @host.ssl_store + end + + it "should add the local CA cert file" do + Puppet.settings.stubs(:value).with(:localcacert).returns "/ca/cert/file" + @store.expects(:add_file).with "/ca/cert/file" + @host.ssl_store + end + + describe "and a CRL is available" do + before do + @crl = stub 'crl', :content => "real_crl" + Puppet::SSL::CertificateRevocationList.stubs(:find).returns @crl + end + + it "should add the CRL" do + @store.expects(:add_crl).with "real_crl" + @host.ssl_store + end + + it "should set the flags to OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK" do + @store.expects(:flags=).with OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK + @host.ssl_store + end + end + end +end diff --git a/spec/unit/ssl/inventory.rb b/spec/unit/ssl/inventory.rb new file mode 100755 index 000000000..bf1dbfb48 --- /dev/null +++ b/spec/unit/ssl/inventory.rb @@ -0,0 +1,180 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/ssl/inventory' + +describe Puppet::SSL::Inventory do + before do + @class = Puppet::SSL::Inventory + end + + it "should use the :certinventory setting for the path to the inventory file" do + Puppet.settings.expects(:value).with(:cert_inventory).returns "/inven/tory" + + @class.any_instance.stubs(:rebuild) + + @class.new.path.should == "/inven/tory" + end + + describe "when initializing" do + it "should set its path to the inventory file" do + Puppet.settings.stubs(:value).with(:cert_inventory).returns "/inven/tory" + @class.new.path.should == "/inven/tory" + end + end + + describe "when managing an inventory" do + before do + Puppet.settings.stubs(:value).with(:cert_inventory).returns "/inven/tory" + + FileTest.stubs(:exist?).with("/inven/tory").returns true + + @inventory = @class.new + + @cert = mock 'cert' + end + + describe "and creating the inventory file" do + before do + Puppet.settings.stubs(:write) + FileTest.stubs(:exist?).with("/inven/tory").returns false + + Puppet::SSL::Certificate.stubs(:search).returns [] + end + + it "should log that it is building a new inventory file" do + Puppet.expects(:notice) + + @inventory.rebuild + end + + it "should use the Settings to write to the file" do + Puppet.settings.expects(:write).with(:cert_inventory) + + @inventory.rebuild + end + + it "should add a header to the file" do + fh = mock 'filehandle' + Puppet.settings.stubs(:write).yields fh + fh.expects(:print).with { |str| str =~ /^#/ } + + @inventory.rebuild + end + + it "should add formatted information on all existing certificates" do + cert1 = mock 'cert1' + cert2 = mock 'cert2' + + Puppet::SSL::Certificate.expects(:search).with("*").returns [cert1, cert2] + + @class.any_instance.expects(:add).with(cert1) + @class.any_instance.expects(:add).with(cert2) + + @inventory.rebuild + end + end + + describe "and adding a certificate" do + it "should build the inventory file if one does not exist" do + Puppet.settings.stubs(:value).with(:cert_inventory).returns "/inven/tory" + Puppet.settings.stubs(:write) + + FileTest.expects(:exist?).with("/inven/tory").returns false + + @inventory.expects(:rebuild) + + @inventory.add(@cert) + end + + it "should use the Settings to write to the file" do + Puppet.settings.expects(:write).with(:cert_inventory, "a") + + @inventory.add(@cert) + end + + it "should use the actual certificate if it was passed a Puppet certificate" do + cert = Puppet::SSL::Certificate.new("mycert") + cert.content = @cert + + fh = stub 'filehandle', :print => nil + Puppet.settings.stubs(:write).yields fh + + @inventory.expects(:format).with(@cert) + + @inventory.add(@cert) + end + + it "should add formatted certificate information to the end of the file" do + fh = mock 'filehandle' + + Puppet.settings.stubs(:write).yields fh + + @inventory.expects(:format).with(@cert).returns "myformat" + + fh.expects(:print).with("myformat") + + @inventory.add(@cert) + end + end + + describe "and formatting a certificate" do + before do + @cert = stub 'cert', :not_before => Time.now, :not_after => Time.now, :subject => "mycert", :serial => 15 + end + + it "should print the serial number as a 4 digit hex number in the first field" do + @inventory.format(@cert).split[0].should == "0x000f" # 15 in hex + end + + it "should print the not_before date in '%Y-%m-%dT%H:%M:%S%Z' format in the second field" do + @cert.not_before.expects(:strftime).with('%Y-%m-%dT%H:%M:%S%Z').returns "before_time" + + @inventory.format(@cert).split[1].should == "before_time" + end + + it "should print the not_after date in '%Y-%m-%dT%H:%M:%S%Z' format in the third field" do + @cert.not_after.expects(:strftime).with('%Y-%m-%dT%H:%M:%S%Z').returns "after_time" + + @inventory.format(@cert).split[2].should == "after_time" + end + + it "should print the subject in the fourth field" do + @inventory.format(@cert).split[3].should == "mycert" + end + + it "should add a carriage return" do + @inventory.format(@cert).should =~ /\n$/ + end + + it "should produce a line consisting of the serial number, start date, expiration date, and subject" do + # Just make sure our serial and subject bracket the lines. + @inventory.format(@cert).should =~ /^0x.+mycert$/ + end + end + + it "should be able to find a given host's serial number" do + @inventory.should respond_to(:serial) + end + + describe "and finding a serial number" do + it "should return nil if the inventory file is missing" do + FileTest.expects(:exist?).with("/inven/tory").returns false + @inventory.serial(:whatever).should be_nil + end + + it "should return the serial number from the line matching the provided name" do + File.expects(:readlines).with("/inven/tory").returns ["0x00f blah blah /CN=me\n", "0x001 blah blah /CN=you\n"] + + @inventory.serial("me").should == 15 + end + + it "should return the number as an integer" do + File.expects(:readlines).with("/inven/tory").returns ["0x00f blah blah /CN=me\n", "0x001 blah blah /CN=you\n"] + + @inventory.serial("me").should == 15 + end + end + end +end diff --git a/spec/unit/ssl/key.rb b/spec/unit/ssl/key.rb new file mode 100755 index 000000000..4cd8e856d --- /dev/null +++ b/spec/unit/ssl/key.rb @@ -0,0 +1,190 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/ssl/key' + +describe Puppet::SSL::Key do + before do + @class = Puppet::SSL::Key + end + + it "should be extended with the Indirector module" do + @class.metaclass.should be_include(Puppet::Indirector) + end + + it "should indirect key" do + @class.indirection.name.should == :key + end + + it "should default to the :file terminus" do + @class.indirection.terminus_class.should == :file + end + + it "should have a method for determining whether it's a CA key" do + @class.new("test").should respond_to(:ca?) + end + + it "should consider itself a ca key if its name matches the CA_NAME" do + @class.new(Puppet::SSL::Host.ca_name).should be_ca + end + + describe "when initializing" do + it "should set its password file to the :capass if it's a CA key" do + Puppet.settings.stubs(:value).returns "whatever" + Puppet.settings.stubs(:value).with(:capass).returns "/ca/pass" + + key = Puppet::SSL::Key.new(Puppet::SSL::Host.ca_name) + key.password_file.should == "/ca/pass" + end + + it "should set its password file to the default password file if it is not the CA key" do + Puppet.settings.stubs(:value).returns "whatever" + Puppet.settings.stubs(:value).with(:passfile).returns "/normal/pass" + + key = Puppet::SSL::Key.new("notca") + key.password_file.should == "/normal/pass" + end + end + + describe "when managing instances" do + before do + @key = @class.new("myname") + end + + it "should have a name attribute" do + @key.name.should == "myname" + end + + it "should have a content attribute" do + @key.should respond_to(:content) + end + + it "should be able to read keys from disk" do + path = "/my/path" + File.expects(:read).with(path).returns("my key") + key = mock 'key' + OpenSSL::PKey::RSA.expects(:new).returns(key) + @key.read(path).should equal(key) + @key.content.should equal(key) + end + + it "should not try to use the provided password file if the file does not exist" do + FileTest.stubs(:exist?).returns false + @key.password_file = "/path/to/password" + + path = "/my/path" + + File.stubs(:read).with(path).returns("my key") + OpenSSL::PKey::RSA.expects(:new).with("my key", nil).returns(mock('key')) + File.expects(:read).with("/path/to/password").never + + @key.read(path) + end + + it "should read the key with the password retrieved from the password file if one is provided" do + FileTest.stubs(:exist?).returns true + @key.password_file = "/path/to/password" + + path = "/my/path" + File.expects(:read).with(path).returns("my key") + File.expects(:read).with("/path/to/password").returns("my password") + + key = mock 'key' + OpenSSL::PKey::RSA.expects(:new).with("my key", "my password").returns(key) + @key.read(path).should equal(key) + @key.content.should equal(key) + end + + it "should return an empty string when converted to a string with no key" do + @key.to_s.should == "" + end + + it "should convert the key to pem format when converted to a string" do + key = mock 'key', :to_pem => "pem" + @key.content = key + @key.to_s.should == "pem" + end + + it "should have a :to_text method that it delegates to the actual key" do + real_key = mock 'key' + real_key.expects(:to_text).returns "keytext" + @key.content = real_key + @key.to_text.should == "keytext" + end + end + + describe "when generating the private key" do + before do + @instance = @class.new("test") + + @key = mock 'key' + end + + it "should create an instance of OpenSSL::PKey::RSA" do + OpenSSL::PKey::RSA.expects(:new).returns(@key) + + @instance.generate + end + + it "should create the private key with the keylength specified in the settings" do + Puppet.settings.expects(:value).with(:keylength).returns("50") + OpenSSL::PKey::RSA.expects(:new).with(50).returns(@key) + + @instance.generate + end + + it "should set the content to the generated key" do + OpenSSL::PKey::RSA.stubs(:new).returns(@key) + @instance.generate + @instance.content.should equal(@key) + end + + it "should return the generated key" do + OpenSSL::PKey::RSA.stubs(:new).returns(@key) + @instance.generate.should equal(@key) + end + + it "should return the key in pem format" do + @instance.generate + @instance.content.expects(:to_pem).returns "my normal key" + @instance.to_s.should == "my normal key" + end + + describe "with a password file set" do + it "should return a nil password if the password file does not exist" do + FileTest.expects(:exist?).with("/path/to/pass").returns false + File.expects(:read).with("/path/to/pass").never + + @instance.password_file = "/path/to/pass" + + @instance.password.should be_nil + end + + it "should return the contents of the password file as its password" do + FileTest.expects(:exist?).with("/path/to/pass").returns true + File.expects(:read).with("/path/to/pass").returns "my password" + + @instance.password_file = "/path/to/pass" + + @instance.password.should == "my password" + end + + it "should export the private key to text using the password" do + Puppet.settings.stubs(:value).with(:keylength).returns("50") + + @instance.password_file = "/path/to/pass" + @instance.stubs(:password).returns "my password" + + OpenSSL::PKey::RSA.expects(:new).returns(@key) + @instance.generate + + cipher = mock 'cipher' + OpenSSL::Cipher::DES.expects(:new).with(:EDE3, :CBC).returns cipher + @key.expects(:export).with(cipher, "my password").returns "my encrypted key" + + @instance.to_s.should == "my encrypted key" + end + end + end +end diff --git a/spec/unit/transaction/report.rb b/spec/unit/transaction/report.rb index 644f8d709..8d49b16a0 100755 --- a/spec/unit/transaction/report.rb +++ b/spec/unit/transaction/report.rb @@ -35,6 +35,6 @@ describe Puppet::Transaction::Report, " when being indirect" do end after do - Puppet::Indirector::Indirection.clear_cache + Puppet::Util::Cacher.invalidate end end diff --git a/spec/unit/type/file.rb b/spec/unit/type/file.rb index 12b806d88..d6add8609 100755 --- a/spec/unit/type/file.rb +++ b/spec/unit/type/file.rb @@ -93,8 +93,4 @@ describe Puppet::Type.type(:file) do ("%o" % (File.stat(@file).mode & 007777)).should == "%o" % 0755 end end - - after do - Puppet::Type::File.clear - end end diff --git a/spec/unit/type/interface.rb b/spec/unit/type/interface.rb index 27f34b7e0..4e27e35ea 100755 --- a/spec/unit/type/interface.rb +++ b/spec/unit/type/interface.rb @@ -90,6 +90,4 @@ describe interface do it "should have a target parameter" do @class.attrtype(:target).should == :param end - - after { @class.clear } end diff --git a/spec/unit/type/mount.rb b/spec/unit/type/mount.rb index a9b78672e..9d09225cc 100755 --- a/spec/unit/type/mount.rb +++ b/spec/unit/type/mount.rb @@ -11,8 +11,6 @@ describe Puppet::Type.type(:mount) do mount = Puppet::Type.type(:mount).create(:name => "yay") mount.should(:ensure).should be_nil end - - after { Puppet::Type.type(:mount).clear } end describe Puppet::Type.type(:mount), "when validating attributes" do @@ -51,8 +49,6 @@ describe Puppet::Type.type(:mount)::Ensure, "when validating values" do it "should support :mounted as a value to :ensure" do Puppet::Type.type(:mount).create(:name => "yay", :ensure => :mounted) end - - after { Puppet::Type.type(:mount).clear } end describe Puppet::Type.type(:mount)::Ensure do @@ -64,10 +60,6 @@ describe Puppet::Type.type(:mount)::Ensure do @ensure = @mount.property(:ensure) end - after :each do - Puppet::Type.type(:mount).clear - end - def mount_stub(params) Puppet::Type.type(:mount).validproperties.each do |prop| unless params[prop] diff --git a/spec/unit/type/noop_metaparam.rb b/spec/unit/type/noop_metaparam.rb index 2a3e0160d..540603ef9 100755 --- a/spec/unit/type/noop_metaparam.rb +++ b/spec/unit/type/noop_metaparam.rb @@ -9,8 +9,6 @@ describe Puppet::Type.type(:file).attrclass(:noop) do @file = Puppet::Type.newfile :path => "/what/ever" end - after { Puppet::Type::File.clear } - it "should accept true as a value" do lambda { @file[:noop] = true }.should_not raise_error end diff --git a/spec/unit/type/package.rb b/spec/unit/type/package.rb index 335910c63..d2fc85ed1 100755 --- a/spec/unit/type/package.rb +++ b/spec/unit/type/package.rb @@ -29,8 +29,6 @@ describe Puppet::Type::Package do pkg = Puppet::Type::Package.create(:name => "yay") pkg.should(:ensure).should == :present end - - after { Puppet::Type::Package.clear } end describe Puppet::Type::Package, "when validating attributes" do @@ -97,8 +95,6 @@ describe Puppet::Type::Package, "when validating attribute values" do it "should accept any string as an argument to :source" do proc { Puppet::Type::Package.create(:name => "yay", :source => "stuff") }.should_not raise_error(Puppet::Error) end - - after { Puppet::Type::Package.clear } end module PackageEvaluationTesting @@ -116,12 +112,6 @@ describe Puppet::Type::Package do @catalog = Puppet::Node::Catalog.new @catalog.add_resource(@package) end - - after :each do - @catalog.clear(true) - Puppet::Type::Package.clear - end - describe Puppet::Type::Package, "when it should be purged" do include PackageEvaluationTesting diff --git a/spec/unit/type/schedule.rb b/spec/unit/type/schedule.rb index da38f68a9..b533d17e4 100755 --- a/spec/unit/type/schedule.rb +++ b/spec/unit/type/schedule.rb @@ -42,18 +42,12 @@ module ScheduleTesting end describe Puppet::Type::Schedule do - before :each do Puppet.settings.stubs(:value).with(:ignoreschedules).returns(false) @schedule = Puppet::Type::Schedule.create(:name => "testing") end - after :each do - Puppet::Type::Schedule.clear - end - - describe Puppet::Type::Schedule do include ScheduleTesting diff --git a/spec/unit/type/service.rb b/spec/unit/type/service.rb index e8358cb22..1a57bdd41 100755 --- a/spec/unit/type/service.rb +++ b/spec/unit/type/service.rb @@ -117,8 +117,6 @@ describe Puppet::Type::Service, "when validating attribute values" do svc = Puppet::Type::Service.create(:name => "yay", :path => ["/one:/two", "/three:/four"]) svc[:path].should == %w{/one /two /three /four} end - - after { Puppet::Type::Service.clear } end describe Puppet::Type::Service, "when setting default attribute values" do @@ -148,8 +146,6 @@ describe Puppet::Type::Service, "when setting default attribute values" do svc = Puppet::Type::Service.create(:name => "nfs.client") svc[:control].should == "NFS_CLIENT_START" end - - after { Puppet::Type::Service.clear } end describe Puppet::Type::Service, "when retrieving the host's current state" do @@ -169,8 +165,6 @@ describe Puppet::Type::Service, "when retrieving the host's current state" do @service[:enable] = true @service.property(:enable).retrieve.should == :yepper end - - after { Puppet::Type::Service.clear } end describe Puppet::Type::Service, "when changing the host" do @@ -217,8 +211,6 @@ describe Puppet::Type::Service, "when changing the host" do @service.property(:ensure).sync end - - after { Puppet::Type::Service.clear } end describe Puppet::Type::Service, "when refreshing the service" do @@ -251,6 +243,4 @@ describe Puppet::Type::Service, "when refreshing the service" do @service.provider.expects(:restart) @service.refresh end - - after { Puppet::Type::Service.clear } end diff --git a/spec/unit/util/cacher.rb b/spec/unit/util/cacher.rb new file mode 100755 index 000000000..73e449588 --- /dev/null +++ b/spec/unit/util/cacher.rb @@ -0,0 +1,158 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/util/cacher' + +class CacheClassTest + include Puppet::Util::Cacher + + cached_attr(:testing) { Time.now } + + def sa_cache + attr_cache(:ca_cache) { Time.now } + end +end + +class CacheInstanceTest + extend Puppet::Util::Cacher + + def self.sa_cache + attr_cache(:ca_cache) { Time.now } + end +end + +describe "a cacher user using cached values", :shared => true do + it "should use the block to generate a new value if none is present" do + now = Time.now + Time.stubs(:now).returns now + @object.sa_cache.should equal(now) + end + + it "should not test for validity if it is creating the value" do + # This is only necessary in the class, since it has this value kicking + # around. + @object.instance_variable_set("@cacher_caches", nil) + Puppet::Util::Cacher.expects(:valid?).never + @object.sa_cache + end + + it "should not consider cached false values to be missing values" do + Puppet::Util::Cacher.stubs(:valid?).returns true + + # This is only necessary in the class, since it has this value kicking + # around. + @object.instance_variable_set("@cacher_caches", nil) + Time.stubs(:now).returns false + @object.sa_cache + @object.sa_cache.should be_false + end + + it "should return cached values if they are still valid" do + Puppet::Util::Cacher.stubs(:valid?).returns true + + @object.sa_cache.should equal(@object.sa_cache) + end + + it "should use the block to generate new values if the cached values are invalid" do + Puppet::Util::Cacher.stubs(:valid?).returns false + + @object.sa_cache.should_not equal(@object.sa_cache) + end + + it "should still cache values after an invalidation" do + # Load the cache + @object.sa_cache + + Puppet::Util::Cacher.invalidate + @object.sa_cache.should equal(@object.sa_cache) + end +end + +describe Puppet::Util::Cacher do + before do + Puppet::Util::Cacher.invalidate + end + after do + Puppet::Util::Cacher.invalidate + end + + it "should have a method for invalidating caches" do + Puppet::Util::Cacher.should respond_to(:invalidate) + end + + it "should have a method for determining whether a cached value is valid" do + Puppet::Util::Cacher.should respond_to(:valid?) + end + + it "should consider cached values valid if the cached value was created and there was never an invalidation" do + Puppet::Util::Cacher.instance_variable_set("@timestamp", nil) + + Puppet::Util::Cacher.should be_valid(Time.now) + end + + it "should consider cached values valid if the cached value was created since the last invalidation" do + Puppet::Util::Cacher.invalidate + + Puppet::Util::Cacher.should be_valid(Time.now + 1) + end + + it "should consider cached values invalid if the cache was invalidated after the cached value was created" do + Puppet::Util::Cacher.invalidate + + Puppet::Util::Cacher.should_not be_valid(Time.now - 1) + end + + describe "when used to extend a class" do + before do + @object = CacheClassTest.new + end + + it_should_behave_like "a cacher user using cached values" + + it "should provide a class method for defining cached attributes" do + CacheClassTest.private_methods.should be_include("cached_attr") + end + + describe "and defining cached attributes" do + it "should create an accessor for the cached attribute" do + @object.should respond_to(:testing) + end + + it "should return a value calculated from the provided block" do + time = Time.now + Time.stubs(:now).returns time + @object.testing.should equal(time) + end + + it "should return the cached value from the getter if the value is still valid" do + value = @object.testing + Puppet::Util::Cacher.expects(:valid?).returns true + @object.testing.should equal(value) + end + + it "should regenerate and return a new value using the provided block if the value is no longer valid" do + value = @object.testing + Puppet::Util::Cacher.expects(:valid?).returns false + @object.testing.should_not equal(value) + end + end + + it "should provide a private instance method for caching values" do + @object.private_methods.should be_include("attr_cache") + end + + end + + describe "when included in a class" do + before do + @object = CacheInstanceTest + end + + it "should provide a private instance method for caching values" do + CacheInstanceTest.private_methods.should be_include("attr_cache") + end + + it_should_behave_like "a cacher user using cached values" + end +end diff --git a/spec/unit/util/settings.rb b/spec/unit/util/settings.rb index a6b358462..ca533ef9a 100755 --- a/spec/unit/util/settings.rb +++ b/spec/unit/util/settings.rb @@ -93,6 +93,11 @@ describe Puppet::Util::Settings, " when setting values" do @settings[:myval].should == "yay" end + it "should clear the list of used sections" do + @settings.expects(:clearused) + @settings[:myval] = "yay" + end + it "should call passed blocks when values are set" do values = [] @settings.setdefaults(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }}) @@ -284,11 +289,6 @@ describe Puppet::Util::Settings, " when parsing its configuration" do lambda { @settings.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. - @settings.should respond_to(:old_parse) - end - it "should convert booleans in the configuration file into Ruby booleans" do text = "[main] one = true @@ -481,7 +481,7 @@ describe Puppet::Util::Settings, " when being used to manage the host machine" d def stub_transaction @bucket = mock 'bucket' - @config = mock 'config', :clear => nil + @config = mock 'config' @trans = mock 'transaction' @settings.expects(:to_transportable).with(:whatever).returns(@bucket) @@ -597,14 +597,6 @@ describe Puppet::Util::Settings, " when being used to manage the host machine" d file.should be_nil end - it "should not try to manage files in memory" do - main = Puppet::Type.type(:file).create(:path => "/maindir") - - trans = @settings.to_transportable - - lambda { trans.to_catalog }.should_not raise_error - end - it "should do nothing if a catalog cannot be created" do bucket = mock 'bucket' catalog = mock 'catalog' @@ -616,34 +608,6 @@ describe Puppet::Util::Settings, " when being used to manage the host machine" d @settings.use(:mysection) end - it "should clear the catalog after applying" do - bucket = mock 'bucket' - catalog = mock 'catalog' - - @settings.expects(:to_transportable).returns bucket - bucket.expects(:to_catalog).returns catalog - catalog.stubs(:host_config=) - catalog.stubs(:apply) - catalog.expects(:clear) - - @settings.use(:mysection) - end - - it "should clear the catalog even if there is an exception during applying" do - bucket = mock 'bucket' - catalog = mock 'catalog' - - @settings.expects(:to_transportable).returns bucket - bucket.expects(:to_catalog).returns catalog - catalog.stubs(:host_config=) - catalog.expects(:apply).raises(ArgumentError) - catalog.expects(:clear) - - # We don't care about the raised exception, we just care that - # we clear the catalog even with the exception - lambda { @settings.use(:mysection) }.should raise_error - end - it "should do nothing if all specified sections have already been used" do bucket = mock 'bucket' catalog = mock 'catalog' @@ -701,6 +665,4 @@ describe Puppet::Util::Settings, " when being used to manage the host machine" d proc { @settings.use(:whatever) }.should raise_error(RuntimeError) end - - after { Puppet::Type.allclear } end diff --git a/test/executables/puppetca.rb b/test/executables/puppetca.rb deleted file mode 100755 index cdc827079..000000000 --- a/test/executables/puppetca.rb +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../lib/puppettest' - -require 'puppettest' -require 'mocha' - -class TestPuppetCA < Test::Unit::TestCase - include PuppetTest::ExeTest - - def setup - super - Puppet::Util::SUIDManager.stubs(:asuser).yields - end - - def gen_cert(ca, host) - runca("-g #{host}") - ca.getclientcert(host)[0] - end - - def mkca - Puppet::Network::Handler.ca.new() - end - - def mkcert(hostname) - cert = nil - assert_nothing_raised { - cert = Puppet::SSLCertificates::Certificate.new( - :name => hostname - ) - cert.mkcsr - } - - return cert - end - - def runca(args) - debug = "" - if Puppet[:debug] - debug = "-d " - end - return %x{puppetca --user=#{Puppet[:user]} #{debug} --group=#{Puppet[:group]} --confdir=#{Puppet[:confdir]} --vardir=#{Puppet[:vardir]} #{args} 2>&1} - end - - def test_signing - ca = mkca - Puppet[:autosign] = false - - %w{host.test.com Other.Testing.Com}.each do |host| - cert = mkcert(host) - resp = nil - assert_nothing_raised { - # We need to use a fake name so it doesn't think the cert is from - # itself. Strangely, getcert stores the csr, because it's a server-side - # method, not client. - resp = ca.getcert(cert.csr.to_pem, host, "127.0.0.1") - } - assert_equal(["",""], resp) - - output = nil - assert_nothing_raised { - output = runca("--list").chomp.split("\n").reject { |line| line =~ /warning:/ } # stupid ssl.rb - } - assert_equal($?,0) - assert_equal([host.downcase], output) - assert_nothing_raised { - output = runca("--sign -a").chomp.split("\n") - } - - - assert_equal($?,0) - assert_equal(["Signed #{host.downcase}"], output) - - - signedfile = ca.ca.host2certfile(host) - assert(FileTest.exists?(signedfile), "cert does not exist") - assert(! FileTest.executable?(signedfile), "cert is executable") - - uid = Puppet::Util.uid(Puppet[:user]) - - if Puppet::Util::SUIDManager.uid == 0 - assert(! FileTest.owned?(signedfile), "cert is owned by root") - end - assert_nothing_raised { - output = runca("--list").chomp.split("\n") - } - assert_equal($?,0) - assert_equal(["No certificates to sign"], output) - end - end - - # This method takes a long time to run because of all of the external - # executable calls. - def test_revocation - ca = Puppet::SSLCertificates::CA.new() - host1 = gen_cert(ca, "host1.example.com") - host2 = gen_cert(ca, "host2.example.com") - host3 = gen_cert(ca, "host3.example.com") - runca("-r host1.example.com") - runca("-r #{host2.serial}") - runca("-r 0x#{host3.serial.to_s(16)}") - runca("-r 0xff") - - # Recreate CA to force reading of CRL - ca = Puppet::SSLCertificates::CA.new() - crl = ca.crl - revoked = crl.revoked.collect { |r| r.serial } - exp = [host1.serial, host2.serial, host3.serial, 255] - assert_equal(exp, revoked) - end - - def test_case_insensitive_sign - end -end - diff --git a/test/executables/puppetmasterd.rb b/test/executables/puppetmasterd.rb deleted file mode 100755 index 6d4ddf56f..000000000 --- a/test/executables/puppetmasterd.rb +++ /dev/null @@ -1,147 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../lib/puppettest' - -require 'puppet' -require 'puppet/network/client' -require 'puppettest' -require 'socket' - -class TestPuppetMasterD < Test::Unit::TestCase - include PuppetTest::ExeTest - def setup - super - Puppet[:certdnsnames] = "localhost" - end - - def getcerts - include Puppet::Daemon - if self.readcerts - return [@cert, @key, @cacert, @cacertfile] - else - raise "Couldn't read certs" - end - end - - # start the daemon and verify it responds and such - def test_normalstart - startmasterd - - pidfile = File.join(Puppet[:vardir], "run", "puppetmasterd.pid") - assert(FileTest.exists?(pidfile), "PID file does not exist") - - sleep(1) - assert_nothing_raised { - socket = TCPSocket.new("127.0.0.1", @@port) - socket.close - } - - client = nil - assert_nothing_raised() { - client = Puppet::Network::Client.status.new( - :Server => "localhost", - :Port => @@port - ) - } - - # set our client up to auto-sign - assert(Puppet[:autosign] =~ /^#{File::SEPARATOR}/, - "Autosign is set to %s, not a file" % Puppet[:autosign]) - - FileUtils.mkdir_p(File.dirname(Puppet[:autosign])) - File.open(Puppet[:autosign], "w") { |f| - f.puts Puppet[:certname] - } - - retval = nil - - # init the client certs - assert_nothing_raised() { - client.cert - } - - # call status - assert_nothing_raised() { - retval = client.status - } - assert_equal(1, retval, "Status.status return value was %s" % retval) - - # this client shoulduse the same certs - assert_nothing_raised() { - client = Puppet::Network::Client.master.new( - :Server => "localhost", - :Port => @@port - ) - } - assert_nothing_raised() { - retval = client.getconfig - } - - objects = nil - end - - # verify that we can run puppetmasterd in parse-only mode - def test_parseonly - startmasterd("--parseonly > /dev/null") - sleep(1) - - pid = nil - ps = Facter["ps"].value || "ps -ef" - %x{#{ps}}.chomp.split(/\n/).each { |line| - next if line =~ /^puppet/ # skip normal master procs - if line =~ /puppetmasterd.+--manifest/ - ary = line.split(" ") - pid = ary[1].to_i - end - } - - assert($? == 0, "Puppetmasterd ended with non-zero exit status") - - assert_nil(pid, "Puppetmasterd is still running after parseonly") - end - - def disabled_test_sslconnection - #file = File.join(exampledir, "code", "head") - #startmasterd("--manifest #{file}") - - #assert_nothing_raised { - # socket = TCPSocket.new("127.0.0.1", Puppet[:masterport]) - # socket.close - #} - - client = nil - cert, key, cacert, cacertfile = getcerts() - - assert_nothing_raised() { - client = Net::HTTP.new("localhost", Puppet[:masterport]) - client.cert = cert - client.key = key - client.ca_file = cacertfile - client.use_ssl = true - client.start_immediately = true - } - retval = nil - - assert_nothing_raised() { - retval = client.nothing - } - assert_equal(1, retval, "return value was %s" % retval) - facts = {} - Facter.each { |p,v| - facts[p] = v - } - textfacts = CGI.escape(YAML.dump(facts)) - assert_nothing_raised() { - #Puppet.notice "calling status" - #retval = client.call("status.status", "") - retval = client.call("puppetmaster.getconfig", textfacts, "yaml") - } - - objects = nil - assert_nothing_raised { - YAML.load(CGI.unescape(retval)) - } - #stopmasterd - end -end - diff --git a/test/language/parser.rb b/test/language/parser.rb index cdd13297a..effb2d40c 100755 --- a/test/language/parser.rb +++ b/test/language/parser.rb @@ -26,15 +26,6 @@ class TestParser < Test::Unit::TestCase parser.file = file parser.parse } - - Puppet::Type.eachtype { |type| - type.each { |obj| - assert(obj.file, "File is not set on %s" % obj.ref) - assert(obj.name, "Name is not set on %s" % obj.ref) - assert(obj.line, "Line is not set on %s" % obj.ref) - } - } - Puppet::Type.allclear } end @@ -49,7 +40,6 @@ class TestParser < Test::Unit::TestCase config.compile #ast.classes[""].evaluate config.topscope } - Puppet::Type.allclear } end @@ -680,7 +670,6 @@ file { "/tmp/yayness": manifest = File.join(modpath, "manifest.pp") manifest_texts.each do |txt| - Puppet::Type.allclear File.open(manifest, "w") { |f| f.puts txt } assert_nothing_raised { diff --git a/test/language/snippets.rb b/test/language/snippets.rb index 982ddfec4..95a518388 100755 --- a/test/language/snippets.rb +++ b/test/language/snippets.rb @@ -24,14 +24,14 @@ class TestSnippets < Test::Unit::TestCase end def assert_file(path, msg = nil) - unless file = @file[path] + unless file = @catalog.resource(:file, path) msg ||= "Could not find file %s" % path raise msg end end def assert_mode_equal(mode, path) - unless file = @file[path] + unless file = @catalog.resource(:file, path) raise "Could not find file %s" % path end @@ -213,8 +213,8 @@ class TestSnippets < Test::Unit::TestCase path1 = "/tmp/argumenttest1" path2 = "/tmp/argumenttest2" - file1 = @file[path1] - file2 = @file[path2] + file1 = @catalog.resource(:file, path1) + file2 = @catalog.resource(:file, path2) assert_file(path1) assert_mode_equal(0755, path1) @@ -233,7 +233,7 @@ class TestSnippets < Test::Unit::TestCase } paths.each { |path| - file = @file[path] + file = @catalog.resource(:file, path) assert(file, "File %s is missing" % path) assert_mode_equal(0755, path) } @@ -243,7 +243,7 @@ class TestSnippets < Test::Unit::TestCase paths = %w{a b c d e f g h}.collect { |l| "/tmp/iteration%stest" % l } paths.each { |path| - file = @file[path] + file = @catalog.resource(:file, path) assert_file(path) assert_mode_equal(0755, path) } @@ -264,7 +264,7 @@ class TestSnippets < Test::Unit::TestCase dir = "/tmp/testdirtest" assert_file(file) assert_file(dir) - assert_equal(:directory, @file[dir].should(:ensure), "Directory is not set to be a directory") + assert_equal(:directory, @catalog.resource(:file, dir).should(:ensure), "Directory is not set to be a directory") end def snippet_scopetest @@ -351,7 +351,7 @@ class TestSnippets < Test::Unit::TestCase }.each { |count, str| path = "/tmp/singlequote%s" % count assert_file(path) - assert_equal(str, @file[path].should(:content)) + assert_equal(str, @catalog.resource(:file, path).should(:content)) } end @@ -389,21 +389,20 @@ class TestSnippets < Test::Unit::TestCase end def snippet_emptyexec - assert(Puppet::Type.type(:exec)["touch /tmp/emptyexectest"], - "Did not create exec") + assert(@catalog.resource(:exec, "touch /tmp/emptyexectest"), "Did not create exec") end def snippet_multisubs path = "/tmp/multisubtest" assert_file(path) - file = @file[path] + file = @catalog.resource(: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") + assert_nil(@catalog.resource(:file, "/tmp/colltest2"), "Incorrectly collected file") end def snippet_virtualresources @@ -476,16 +475,6 @@ class TestSnippets < Test::Unit::TestCase catalog = catalog.to_ral } - 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) - } - } @catalog = catalog assert_nothing_raised { self.send(mname) diff --git a/test/lib/puppettest.rb b/test/lib/puppettest.rb index 63f8121b5..6c1e059b8 100755 --- a/test/lib/puppettest.rb +++ b/test/lib/puppettest.rb @@ -233,6 +233,7 @@ module PuppetTest end f = File.join(self.tmpdir(), "tempfile_" + @@tmpfilenum.to_s) + @@tmpfiles ||= [] @@tmpfiles << f return f end @@ -298,7 +299,6 @@ module PuppetTest @@tmppids.clear - Puppet::Type.allclear Puppet::Util::Storage.clear Puppet.clear Puppet.settings.clear diff --git a/test/lib/puppettest/exetest.rb b/test/lib/puppettest/exetest.rb index 05de56c0f..0d66c5a07 100644 --- a/test/lib/puppettest/exetest.rb +++ b/test/lib/puppettest/exetest.rb @@ -58,7 +58,6 @@ module PuppetTest::ExeTest args += " --masterport %s" % @@port args += " --user %s" % Puppet::Util::SUIDManager.uid args += " --group %s" % Puppet::Util::SUIDManager.gid - args += " --nonodes" args += " --autosign true" #if Puppet[:debug] diff --git a/test/lib/puppettest/support/utils.rb b/test/lib/puppettest/support/utils.rb index cb4a6924c..b749c7931 100644 --- a/test/lib/puppettest/support/utils.rb +++ b/test/lib/puppettest/support/utils.rb @@ -42,12 +42,6 @@ module PuppetTest::Support::Utils # stop any services that might be hanging around def stopservices - if stype = Puppet::Type.type(:service) - stype.each { |service| - service[:ensure] = :stopped - service.evaluate - } - end end # TODO: rewrite this to use the 'etc' module. diff --git a/test/network/client/ca.rb b/test/network/client/ca.rb index 9781e9589..2546642cd 100755 --- a/test/network/client/ca.rb +++ b/test/network/client/ca.rb @@ -35,6 +35,7 @@ class TestClientCA < Test::Unit::TestCase Puppet.settings.stubs(:value).with(:http_proxy_host).returns(nil) Puppet.settings.stubs(:value).with(:http_proxy_port).returns(nil) Puppet.settings.stubs(:value).with(:http_keepalive).returns(false) + Puppet.settings.stubs(:value).with(:configtimeout).returns(180) # Just throw an error; the important thing is the values, not what happens next. Net::HTTP.stubs(:new).with("myca", 321, nil, nil).raises(ArgumentError) diff --git a/test/network/client/master.rb b/test/network/client/master.rb index dc2140e62..c0d14ccee 100755 --- a/test/network/client/master.rb +++ b/test/network/client/master.rb @@ -421,24 +421,6 @@ end assert_equal(facts["environment"], Puppet[:environment], "Did not add environment to client facts") end - # This is partially to fix #532, but also to save on memory. - def test_remove_objects_after_every_run - client = mkclient - - ftype = Puppet::Type.type(:file) - file = ftype.create :title => "/what/ever", :ensure => :present - config = Puppet::Node::Catalog.new - config.add_resource(file) - - config.expects :apply - - client.catalog = config - client.expects(:getconfig) - client.run - - assert_nil(ftype[@createdfile], "file object was not removed from memory") - end - # #685 def test_http_failures_do_not_kill_puppetd client = mkclient diff --git a/test/network/client/resource.rb b/test/network/client/resource.rb index eb8e829eb..f3c564d9d 100755 --- a/test/network/client/resource.rb +++ b/test/network/client/resource.rb @@ -38,7 +38,6 @@ class TestResourceClient < Test::Unit::TestCase assert_instance_of(Puppet::TransObject, tobj) - Puppet::Type.allclear obj = nil assert_nothing_raised { obj = tobj.to_type @@ -49,7 +48,6 @@ class TestResourceClient < Test::Unit::TestCase File.unlink(file) # Now test applying - Puppet::Type.allclear result = nil assert_nothing_raised { result = client.apply(tobj) @@ -57,7 +55,6 @@ class TestResourceClient < Test::Unit::TestCase assert(FileTest.exists?(file), "File was not created on apply") # Lastly, test "list" - Puppet::Type.allclear list = nil assert_nothing_raised { list = client.list("user") @@ -70,14 +67,12 @@ class TestResourceClient < Test::Unit::TestCase break if count > 3 assert_instance_of(Puppet::TransObject, tobj) - Puppet::Type.allclear tobj2 = nil assert_nothing_raised { tobj2 = client.describe(tobj.type, tobj.name) } obj = nil - Puppet::Type.allclear assert_nothing_raised { obj = tobj2.to_type } diff --git a/test/network/handler/fileserver.rb b/test/network/handler/fileserver.rb index a705dbf4b..b3a9aef2b 100755 --- a/test/network/handler/fileserver.rb +++ b/test/network/handler/fileserver.rb @@ -146,10 +146,6 @@ class TestFileServer < Test::Unit::TestCase list = server.list(sfile, :manage, true, false) } - assert_nothing_raised { - file = Puppet.type(:file)[tmpfile] - } - output = "/\tfile" # verify it got listed as a file diff --git a/test/network/handler/resource.rb b/test/network/handler/resource.rb index 00a88b57f..269c9571c 100755 --- a/test/network/handler/resource.rb +++ b/test/network/handler/resource.rb @@ -13,7 +13,6 @@ class TestResourceServer < Test::Unit::TestCase def verify_described(type, described) described.each do |name, trans| - type.clear obj = nil assert_nothing_raised do obj = trans.to_type @@ -28,7 +27,6 @@ class TestResourceServer < Test::Unit::TestCase assert_equal(Puppet::Type.type(:package).defaultprovider.name, obj[:provider]) end end - type.clear end def test_describe_file @@ -42,71 +40,38 @@ class TestResourceServer < Test::Unit::TestCase server = Puppet::Network::Handler.resource.new() end - # The first run we create the file on the copy, the second run - # the file is already there so the object should be in sync - 2.times do |i| - [ [nil], - [[:content, :mode], []], - [[], [:content]], - [[:content], [:mode]] - ].each do |ary| - retrieve = ary[0] || [] - ignore = ary[1] || [] - - File.open(file, "w") { |f| f.print str } - - result = nil - assert_nothing_raised do - result = server.describe("file", file, *ary) - end - - assert(result, "Could not retrieve file information") - - assert_instance_of(Puppet::TransObject, result) + [ [nil], + [[:content, :mode], []], + [[], [:content]], + [[:content], [:mode]] + ].each do |ary| + retrieve = ary[0] || [] + ignore = ary[1] || [] - # Now we have to clear, so that the server's object gets removed - Puppet::Type.type(:file).clear + File.open(file, "w") { |f| f.print str } - # And remove the file, so we can verify it gets recreated - if i == 0 - File.unlink(file) - end - - object = nil - assert_nothing_raised do - object = result.to_type - end + result = nil + assert_nothing_raised do + result = server.describe("file", file, *ary) + end - assert(object, "Could not create type") + assert(result, "Could not retrieve file information") - retrieve.each do |property| - assert(object.should(property), "Did not retrieve %s" % property) - end + assert_instance_of(Puppet::TransObject, result) - ignore.each do |property| - assert(! object.should(property), "Incorrectly retrieved %s" % property) - end + object = nil + assert_nothing_raised do + object = result.to_type + end - if i == 0 - assert_events([:file_created], object) - else - assert_nothing_raised { - assert(object.insync?(object.retrieve), "Object was not in sync") - } - end + assert(object, "Could not create type") - assert(FileTest.exists?(file), "File did not get recreated") + retrieve.each do |property| + assert(object.should(property), "Did not retrieve %s" % property) + end - if i == 0 - if object.should(:content) - assert_equal(str, File.read(file), "File contents are not the same") - else - assert_equal("", File.read(file), "File content was incorrectly made") - end - end - if FileTest.exists? file - File.unlink(file) - end + ignore.each do |property| + assert(! object.should(property), "Incorrectly retrieved %s" % property) end end end @@ -140,9 +105,6 @@ class TestResourceServer < Test::Unit::TestCase assert_instance_of(Puppet::TransObject, result) - # Now we have to clear, so that the server's object gets removed - Puppet::Type.type(:file).clear - # And remove the file, so we can verify it gets recreated Dir.rmdir(file) @@ -151,6 +113,8 @@ class TestResourceServer < Test::Unit::TestCase object = result.to_type end + catalog = mk_catalog(object) + assert(object, "Could not create type") retrieve.each do |property| @@ -160,11 +124,6 @@ class TestResourceServer < Test::Unit::TestCase ignore.each do |property| assert(! object.should(property), "Incorrectly retrieved %s" % property) end - - #assert_apply(object) - assert_events([:directory_created], object) - assert(FileTest.directory?(file), "Directory did not get recreated") - Dir.rmdir(file) end end @@ -198,8 +157,6 @@ class TestResourceServer < Test::Unit::TestCase bucket = server.list(type.name) } - #type.clear - count = 0 described = {} bucket.each do |obj| @@ -251,8 +208,6 @@ class TestResourceServer < Test::Unit::TestCase filetrans = server.describe("file", file) } - Puppet::Type.type(:file).clear - bucket = Puppet::TransBucket.new bucket.type = "file" bucket.name = "test" @@ -273,7 +228,6 @@ class TestResourceServer < Test::Unit::TestCase yaml = Base64.encode64(YAML::dump(bucket)) } - Puppet::Type.type(:file).clear File.unlink(file) if Base64.decode64(yaml) =~ /(.{20}Loglevel.{20})/ diff --git a/test/network/server/webrick.rb b/test/network/server/webrick.rb index fe6d69ade..0cdade4ba 100755 --- a/test/network/server/webrick.rb +++ b/test/network/server/webrick.rb @@ -67,7 +67,7 @@ class TestWebrickServer < Test::Unit::TestCase end # Test that a client whose cert has been revoked really can't connect - def test_certificate_revocation + def test_xcertificate_revocation Puppet[:autosign] = true serverpid, server = mk_status_server @@ -101,9 +101,6 @@ class TestWebrickServer < Test::Unit::TestCase def mk_status_client client = nil - # Otherwise, the client initalization will trip over itself - # since elements created in the last run are still around - Puppet::Type::allclear assert_nothing_raised() { client = Puppet::Network::Client.status.new( diff --git a/test/other/relationships.rb b/test/other/relationships.rb index bf83caf4a..4e666450e 100755 --- a/test/other/relationships.rb +++ b/test/other/relationships.rb @@ -81,6 +81,9 @@ class TestRelationships < Test::Unit::TestCase ) end + catalog = mk_catalog(*files) + catalog.add_resource(*execs) + # Add our first relationship if out[param] files[0][param] = execs[0] @@ -106,8 +109,6 @@ class TestRelationships < Test::Unit::TestCase execs[0][param], "Incorrect source list") end check_relationship(sources, targets, out[param], refreshers.include?(param)) - - Puppet::Type.allclear end end @@ -166,6 +167,7 @@ class TestRelationships < Test::Unit::TestCase exec = Puppet::Type.newexec(:title => "myexec", :cwd => path, :command => "/bin/echo") + catalog = mk_catalog(file, exec) reqs = nil assert_nothing_raised do reqs = exec.autorequire @@ -176,9 +178,8 @@ class TestRelationships < Test::Unit::TestCase # Now make sure that these relationships are added to the # relationship graph - config = mk_catalog(file, exec) - config.apply do |trans| - assert(config.relationship_graph.edge?(file, exec), "autorequire edge was not created") + catalog.apply do |trans| + assert(catalog.relationship_graph.edge?(file, exec), "autorequire edge was not created") end end diff --git a/test/other/transactions.rb b/test/other/transactions.rb index ce2d0d52b..8cae3388b 100755 --- a/test/other/transactions.rb +++ b/test/other/transactions.rb @@ -598,15 +598,15 @@ class TestTransactions < Test::Unit::TestCase yay = Puppet::Type.newgenerator :title => "yay" rah = Puppet::Type.newgenerator :title => "rah" - config = mk_catalog(yay, rah) - trans = Puppet::Transaction.new(config) + catalog = mk_catalog(yay, rah) + trans = Puppet::Transaction.new(catalog) assert_nothing_raised do trans.generate end %w{ya ra y r}.each do |name| - assert(trans.catalog.vertex?(Puppet::Type.type(:generator)[name]), + assert(catalog.resource(:generator, name), "Generated %s was not a vertex" % name) assert($finished.include?(name), "%s was not finished" % name) end @@ -617,10 +617,8 @@ class TestTransactions < Test::Unit::TestCase end %w{ya ra y r}.each do |name| - assert(!trans.catalog.vertex?(Puppet::Type.type(:generator)[name]), + assert(! catalog.resource(:generator, name), "Generated vertex %s was not removed from graph" % name) - assert_nil(Puppet::Type.type(:generator)[name], - "Generated vertex %s was not removed from class" % name) end end @@ -637,8 +635,8 @@ class TestTransactions < Test::Unit::TestCase yay = Puppet::Type.newgenerator :title => "yay" rah = Puppet::Type.newgenerator :title => "rah", :subscribe => yay - config = mk_catalog(yay, rah) - trans = Puppet::Transaction.new(config) + catalog = mk_catalog(yay, rah) + trans = Puppet::Transaction.new(catalog) trans.prepare @@ -647,7 +645,7 @@ class TestTransactions < Test::Unit::TestCase assert_nothing_raised("failed to apply yay") do trans.eval_resource(yay) end - ya = type["ya"] + ya = catalog.resource(type.name, "ya") assert(ya, "Did not generate ya") assert(trans.relationship_graph.vertex?(ya), "Did not add ya to rel_graph") @@ -660,11 +658,11 @@ class TestTransactions < Test::Unit::TestCase # Now make sure it in turn eval_generates appropriately assert_nothing_raised("failed to apply yay") do - trans.eval_resource(type["ya"]) + trans.eval_resource(catalog.resource(type.name, "ya")) end %w{y}.each do |name| - res = type[name] + res = catalog.resource(type.name, "ya") assert(res, "Did not generate %s" % name) assert(trans.relationship_graph.vertex?(res), "Did not add %s to rel_graph" % name) @@ -672,7 +670,7 @@ class TestTransactions < Test::Unit::TestCase end assert_nothing_raised("failed to eval_generate with nil response") do - trans.eval_resource(type["y"]) + trans.eval_resource(catalog.resource(type.name, "y")) end assert(trans.relationship_graph.edge?(yay, ya), "no edge was created for ya => yay") @@ -680,7 +678,7 @@ class TestTransactions < Test::Unit::TestCase trans.eval_resource(rah) end - ra = type["ra"] + ra = catalog.resource(type.name, "ra") assert(ra, "Did not generate ra") assert(trans.relationship_graph.vertex?(ra), "Did not add ra to rel_graph" % name) @@ -699,14 +697,12 @@ class TestTransactions < Test::Unit::TestCase end %w{ya ra y r}.each do |name| - assert(!trans.relationship_graph.vertex?(type[name]), + assert(!trans.relationship_graph.vertex?(catalog.resource(type.name, name)), "Generated vertex %s was not removed from graph" % name) - assert_nil(type[name], - "Generated vertex %s was not removed from class" % name) end # Now, start over and make sure that everything gets evaluated. - trans = Puppet::Transaction.new(config) + trans = Puppet::Transaction.new(catalog) $evaluated.clear assert_nothing_raised do trans.evaluate diff --git a/test/ral/manager/instances.rb b/test/ral/manager/instances.rb index a50ecb213..5305b3ff3 100755 --- a/test/ral/manager/instances.rb +++ b/test/ral/manager/instances.rb @@ -89,21 +89,5 @@ class TestTypeInstances < Test::Unit::TestCase # Now make sure the resources have an 'ensure' property to go with the value in the provider assert(resources[:one].send(:instance_variable_get, "@parameters").include?(:ensure), "Did not create ensure property") end - - # Make sure resources are entirely deleted. - def test_delete - aliases = %w{one} - catalog = mk_catalog - obj = @type.create(:name => "testing", :alias => "two", :catalog => catalog) - aliases << "two" - - @type.alias("two", obj) - - obj.remove - assert_nil(@type["testing"], "Object was not removed from objects hash") - assert_nil(@type["one"], "Object's alias was not removed") - assert_nil(@type["two"], "Object's second alias was not removed") - - end end diff --git a/test/ral/manager/type.rb b/test/ral/manager/type.rb index c6efe4f00..f3a116f6f 100755 --- a/test/ral/manager/type.rb +++ b/test/ral/manager/type.rb @@ -65,7 +65,6 @@ class TestType < Test::Unit::TestCase assert_nothing_raised() { file.evaluate } - Puppet.type(:file).clear assert_nothing_raised() { system("rm -f %s" % path) file = Puppet.type(:file).create( @@ -110,47 +109,7 @@ class TestType < Test::Unit::TestCase assert_equal("testing", group.name, "Could not retrieve name") end - # Verify that values get merged correctly - def test_mergepropertyvalues - file = tempfile() - - # Create the first version - assert_nothing_raised { - Puppet.type(:file).create( - :path => file, - :owner => ["root", "bin"] - ) - } - - # Make sure no other statements are allowed - assert_raise(Puppet::Error) { - Puppet.type(:file).create( - :path => file, - :group => "root" - ) - } - end - - def test_aliases_to_self_are_not_failures - resource = Puppet.type(:file).create( - :name => "/path/to/some/missing/file", - :ensure => "file" - ) - resource.stubs(:path).returns("") - - catalog = stub 'catalog' - catalog.expects(:resource).with(:file, "/path/to/some/missing/file").returns(resource) - resource.catalog = catalog - - # Verify our adding ourselves as an alias isn't an error. - assert_nothing_raised("Could not add alias") { - resource[:alias] = "/path/to/some/missing/file" - } - - assert_equal(resource.object_id, Puppet.type(:file)["/path/to/some/missing/file"].object_id, "Could not retrieve alias to self") - end - - def test_aliases_are_added_to_class_and_catalog + def test_aliases_are_added_to_catalog resource = Puppet.type(:file).create( :name => "/path/to/some/missing/file", :ensure => "file" @@ -165,8 +124,6 @@ class TestType < Test::Unit::TestCase assert_nothing_raised("Could not add alias") { resource[:alias] = "funtest" } - - assert_equal(resource.object_id, Puppet.type(:file)["funtest"].object_id, "Could not retrieve alias") end def test_aliasing_fails_without_a_catalog @@ -182,7 +139,7 @@ class TestType < Test::Unit::TestCase def test_catalogs_are_set_during_initialization_if_present_on_the_transobject trans = Puppet::TransObject.new("/path/to/some/file", :file) - trans.catalog = :my_config + trans.catalog = stub 'catalog', :resource => nil resource = trans.to_type assert_equal(resource.catalog, trans.catalog, "Did not set catalog on initialization") end @@ -217,37 +174,6 @@ class TestType < Test::Unit::TestCase assert(twoobj.requires?(oneobj), "Requirement was not created") end - # Verify that names are aliases, not equivalents - def test_nameasalias - file = nil - # Create the parent dir, so we make sure autorequiring the parent dir works - parentdir = tempfile() - dir = Puppet.type(:file).create( - :name => parentdir, - :ensure => "directory" - ) - assert_apply(dir) - path = File.join(parentdir, "subdir") - name = "a test file" - transport = Puppet::TransObject.new(name, "file") - transport[:path] = path - transport[:ensure] = "file" - assert_nothing_raised { - file = transport.to_type - } - - assert_equal(path, file[:path]) - assert_equal(name, file.title) - - assert_nothing_raised { - file.retrieve - } - - assert_apply(file) - - assert(Puppet.type(:file)[name], "Could not look up object by name") - end - def test_ensuredefault user = nil assert_nothing_raised { @@ -434,7 +360,6 @@ class TestType < Test::Unit::TestCase assert_equal(path, file[:name], "Did not get correct name") file = nil - Puppet::Type.type(:file).clear # Now make sure we can specify both and still get the right answers assert_nothing_raised do @@ -475,7 +400,6 @@ class TestType < Test::Unit::TestCase # Now try it using the class method on Type oldid = obj.object_id obj = nil - Puppet::Type.type(:file).clear assert_nothing_raised { obj = Puppet::Type.create(trans) @@ -487,7 +411,6 @@ class TestType < Test::Unit::TestCase # Now try the same things with hashes instead of a transobject oldid = obj.object_id obj = nil - Puppet::Type.type(:file).clear hash = { :type => :file, :title => "Myfile", @@ -510,7 +433,6 @@ class TestType < Test::Unit::TestCase # Now try it using the class method on Type oldid = obj.object_id obj = nil - Puppet::Type.type(:file).clear assert_nothing_raised { obj = Puppet::Type.create(hash) @@ -533,25 +455,6 @@ class TestType < Test::Unit::TestCase end end - def test_title_and_name - obj = nil - path = tempfile() - fileobj = Puppet::Type.type(:file) - - assert_nothing_raised do - obj = fileobj.create( - :title => "myfile", - :path => path - ) - end - - assert_equal(obj, fileobj["myfile"], - "Could not retrieve obj by title") - - assert_equal(obj, fileobj[path], - "Could not retrieve obj by name") - end - # Make sure default providers behave correctly def test_defaultproviders # Make a fake type @@ -577,6 +480,7 @@ class TestType < Test::Unit::TestCase # Make sure that we can have multiple non-isomorphic objects with the same name, # but not with isomorphic objects. def test_isomorphic_names + catalog = mk_catalog # First do execs, since they're not isomorphic. echo = Puppet::Util.binary "echo" exec1 = exec2 = nil @@ -586,37 +490,31 @@ class TestType < Test::Unit::TestCase :command => "#{echo} funtest" ) end + catalog.add_resource(exec1) assert_nothing_raised do exec2 = Puppet::Type.type(:exec).create( :title => "exec2", :command => "#{echo} funtest" ) end - - assert_apply(exec1, exec2) + catalog.add_resource(exec2) # Now do files, since they are. This should fail. file1 = file2 = nil path = tempfile() - assert_nothing_raised do - file1 = Puppet::Type.type(:file).create( - :title => "file1", - :path => path, - :content => "yayness" - ) - end - - # This will fail, but earlier systems will catch it. - assert_raise(Puppet::Error) do - file2 = Puppet::Type.type(:file).create( - :title => "file2", - :path => path, - :content => "rahness" - ) - end + file1 = Puppet::Type.type(:file).create( + :title => "file1", + :path => path, + :content => "yayness" + ) + catalog.add_resource(file1) - assert(file1, "Did not create first file") - assert_nil(file2, "Incorrectly created second file") + file2 = Puppet::Type.type(:file).create( + :title => "file2", + :path => path, + :content => "rahness" + ) + assert_raise(ArgumentError) { catalog.add_resource(file2) } end def test_tags @@ -654,20 +552,6 @@ class TestType < Test::Unit::TestCase end end - # Make sure that classes behave like hashes. - def test_class_hash_behaviour - path = tempfile() - - filetype = Puppet::Type.type(:file) - one = Puppet::Type.newfile :path => path - - assert_equal(one, filetype[path], "Did not get file back") - - assert_raise(Puppet::Error) do - filetype[path] = one - end - end - def test_ref path = tempfile() Puppet::Type.type(:exec) # uggh, the methods need to load the types @@ -685,7 +569,7 @@ class TestType < Test::Unit::TestCase type = Puppet::Type.type(:exec) mk = Proc.new do |i, hash| hash[:title] = "exec%s" % i - hash[:command] = "/bin/echo" + hash[:command] = "/bin/echo %s" % i if parent = hash[:parent] hash.delete(:parent) end @@ -753,7 +637,8 @@ class TestType < Test::Unit::TestCase # Partially test #704, but also cover the rest of the schedule management bases. def test_schedule - Puppet::Type.type(:schedule).create(:name => "maint") + schedule = Puppet::Type.type(:schedule).create(:name => "maint") + catalog = mk_catalog(schedule) {"maint" => true, nil => false, :fail => :fail}.each do |name, should| args = {:name => tempfile, :ensure => :file} @@ -761,21 +646,27 @@ class TestType < Test::Unit::TestCase args[:schedule] = name end resource = Puppet::Type.type(:file).create(args) + catalog.add_resource(resource) if should == :fail assert_raise(Puppet::Error, "Did not fail on missing schedule") do resource.schedule end + elsif should == false + assert_nil(resource.schedule, "Set the schedule tho it is set to nil") else sched = nil assert_nothing_raised("Failed when schedule was %s" % sched) do sched = resource.schedule end + assert(sched, "Did not find schedule %s" % sched.inspect) + if should assert_equal(name, sched.name, "did not get correct schedule back") end end + catalog.remove_resource(resource) end end diff --git a/test/ral/providers/group.rb b/test/ral/providers/group.rb index 18b0866b9..f912ec51a 100755 --- a/test/ral/providers/group.rb +++ b/test/ral/providers/group.rb @@ -21,7 +21,6 @@ class TestGroupProvider < Test::Unit::TestCase def teardown super - Puppet.type(:group).clear @@tmpgroups.each { |group| unless missing?(group) remove(group) diff --git a/test/ral/type/basic.rb b/test/ral/type/basic.rb deleted file mode 100755 index 3c5faeee0..000000000 --- a/test/ral/type/basic.rb +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../../lib/puppettest' - -require 'puppettest' - -class TestBasic < Test::Unit::TestCase - include PuppetTest - - def setup - super - @component = nil - @configfile = nil - @command = nil - - assert_nothing_raised() { - @component = Puppet.type(:component).create( - :name => "yaytest", - :type => "testing" - ) - } - - assert_nothing_raised() { - @filepath = tempfile() - @configfile = Puppet.type(:file).create( - :path => @filepath, - :ensure => "file", - :checksum => "md5" - ) - } - assert_nothing_raised() { - @command = Puppet.type(:exec).create( - :title => "echo", - :command => "echo yay", - :path => ENV["PATH"] - ) - } - @config = mk_catalog(@component, @configfile, @command) - @config.add_edge @component, @configfile - @config.add_edge @component, @command - end - - def teardown - super - stopservices - end - - def test_values - [:ensure, :checksum].each do |param| - prop = @configfile.property(param) - assert(prop, "got no property for %s" % param) - assert(prop.value, "got no value for %s" % param) - end - end - - def test_name_calls - [@command, @configfile].each { |obj| - Puppet.debug "obj is %s" % obj - assert_nothing_raised(){ - obj.name - } - } - end - - def test_name_equality - assert_equal(@filepath, @configfile.title) - - assert_equal("echo", @command.title) - end - - def test_object_retrieval - [@command, @configfile].each { |obj| - assert_equal(obj.class[obj.name].object_id, obj.object_id, - "%s did not match class version" % obj.ref) - } - end - - def test_paths - [@configfile, @command, @component].each { |obj| - assert_nothing_raised { - assert_instance_of(String, obj.path) - } - } - end -end diff --git a/test/ral/type/cron.rb b/test/ral/type/cron.rb index 73e941894..8e3d25350 100755 --- a/test/ral/type/cron.rb +++ b/test/ral/type/cron.rb @@ -25,7 +25,6 @@ class TestCron < Test::Unit::TestCase def teardown super @crontype.defaultprovider = nil - Puppet::Util::FileType.filetype(:ram).clear end def eachprovider @@ -145,21 +144,19 @@ class TestCron < Test::Unit::TestCase next unless prop.name.to_s == "command" assert(prop.insync?(value), "Property %s is not considered in sync with value %s" % [prop.name, value.inspect]) end - - @crontype.clear end end def test_makeandretrievecron %w{storeandretrieve a-name another-name more_naming SomeName}.each do |name| cron = mkcron(name) - comp = mk_catalog(name, cron) - trans = assert_events([:cron_created], comp, name) + catalog = mk_catalog(name, cron) + trans = assert_events([:cron_created], catalog, name) cron.provider.class.prefetch cron = nil - assert(cron = Puppet.type(:cron)[name], "Could not retrieve named cron") + assert(cron = catalog.resource(:cron, name), "Could not retrieve named cron") assert_instance_of(Puppet.type(:cron), cron) end end @@ -247,7 +244,6 @@ class TestCron < Test::Unit::TestCase next unless prop.name.to_s == "minute" assert(prop.insync?(value), "Property %s is not considered in sync with value %s" % [prop.name, value.inspect]) end - @crontype.clear end end @@ -294,8 +290,6 @@ class TestCron < Test::Unit::TestCase # Write it to our file assert_apply(cron) - @crontype.clear - crons = [] assert_nothing_raised { @crontype.instances.each do |cron| @@ -484,7 +478,6 @@ class TestCron < Test::Unit::TestCase cron = @crontype.create(:name => "space testing", :command => string) assert_apply(cron) - cron.class.clear cron = @crontype.create(:name => "space testing", :command => string) # Now make sure that it's correctly in sync diff --git a/test/ral/type/exec.rb b/test/ral/type/exec.rb index e2a3dd9ed..8c7c3f69b 100755 --- a/test/ral/type/exec.rb +++ b/test/ral/type/exec.rb @@ -22,8 +22,6 @@ class TestExec < Test::Unit::TestCase def test_numvsstring [0, "0"].each { |val| - Puppet.type(:exec).clear - Puppet.type(:component).clear command = nil output = nil assert_nothing_raised { @@ -50,13 +48,11 @@ class TestExec < Test::Unit::TestCase :path => "/usr/bin:/bin:/usr/sbin:/sbin" ) } - Puppet.type(:exec).clear assert_nothing_raised { command = Puppet.type(:exec).create( :command => "/bin/echo" ) } - Puppet.type(:exec).clear assert_nothing_raised { command = Puppet.type(:exec).create( :command => "/bin/echo", @@ -242,6 +238,8 @@ class TestExec < Test::Unit::TestCase :command => "cat %s %s" % [exe, oexe], :path => ENV["PATH"] ) + + catalog = mk_catalog(file, baseobj, ofile, exec, cat) rels = nil assert_nothing_raised do diff --git a/test/ral/type/file.rb b/test/ral/type/file.rb index 5cf989aa3..fc7c39796 100755 --- a/test/ral/type/file.rb +++ b/test/ral/type/file.rb @@ -30,10 +30,10 @@ class TestFile < Test::Unit::TestCase @file = Puppet::Type.type(:file) $method = @method_name Puppet[:filetimeout] = -1 + Facter.stubs(:to_hash).returns({}) end def teardown - Puppet::Util::Storage.clear system("rm -rf %s" % Puppet[:statefile]) super end @@ -409,7 +409,6 @@ class TestFile < Test::Unit::TestCase of.puts "some more text, yo" } } - Puppet.type(:file).clear # now recreate the file assert_nothing_raised() { @@ -445,11 +444,6 @@ class TestFile < Test::Unit::TestCase } assert_events([:file_changed], file) - # verify that we're actually getting notified when a file changes - assert_nothing_raised() { - Puppet.type(:file).clear - } - if path =~ /nonexists/ File.unlink(path) end @@ -492,7 +486,7 @@ class TestFile < Test::Unit::TestCase # Create a test directory path = tempfile() dir = @file.create :path => path, :mode => 0755, :recurse => true - config = mk_catalog(dir) + catalog = mk_catalog(dir) Dir.mkdir(path) @@ -505,7 +499,7 @@ class TestFile < Test::Unit::TestCase test = File.join(path, "file") File.open(test, "w") { |f| f.puts "yay" } assert_nothing_raised() { ret = dir.localrecurse(true) } - fileobj = @file[test] + fileobj = catalog.resource(:file, test) assert(fileobj, "child object was not created") assert_equal([fileobj], ret, "child object was not returned") @@ -549,7 +543,7 @@ class TestFile < Test::Unit::TestCase assert_nothing_raised() { ret = dir.localrecurse(true) } assert_equal([bad], ret.collect { |f| f.title }, "purge failed") - badobj = @file[bad] + badobj = catalog.resource(:file, bad) assert(badobj, "did not create bad object") end @@ -700,7 +694,6 @@ class TestFile < Test::Unit::TestCase "Incorrect generated children when recurse == %s" % value.inspect) File.unlink(tmpfile) - Puppet.type(:file).clear end end @@ -745,43 +738,6 @@ class TestFile < Test::Unit::TestCase assert(file.insync?(currentvalues)) end - def test_remove - basedir = tempfile() - subdir = File.join(basedir, "this") - FileUtils.mkdir_p(subdir) - - dir = nil - assert_nothing_raised { - dir = Puppet.type(:file).create( - :path => basedir, - :recurse => true, - :check => %w{owner mode group} - ) - } - mk_catalog dir - - assert_nothing_raised { - dir.eval_generate - } - - obj = nil - assert_nothing_raised { - obj = Puppet.type(:file)[subdir] - } - - assert(obj, "Could not retrieve subdir object") - - assert_nothing_raised { - obj.remove(true) - } - - assert_nothing_raised { - obj = Puppet.type(:file)[subdir] - } - - assert_nil(obj, "Retrieved removed object") - end - def test_path dir = tempfile() @@ -801,15 +757,13 @@ class TestFile < Test::Unit::TestCase :check => %w{mode owner group} ) } - mk_catalog dirobj + catalog = mk_catalog dirobj assert_nothing_raised { dirobj.eval_generate } - assert_nothing_raised { - file = dirobj.class[path] - } + file = catalog.resource(:file, path) assert(file, "Could not retrieve file object") @@ -829,6 +783,7 @@ class TestFile < Test::Unit::TestCase :name => subfile, :ensure => "file" ) + catalog = mk_catalog(baseobj, subobj) edge = nil assert_nothing_raised do edge = subobj.autorequire.shift @@ -1062,14 +1017,11 @@ class TestFile < Test::Unit::TestCase File.open(file, "w", 0411) { |f| f.puts "yayness" } - obj = nil - assert_nothing_raised { - obj = Puppet::Type.type(:file).create( - :path => file, :content => "rahness\n", :backup => ".puppet-bak" - ) - } - - assert_apply(obj) + obj = Puppet::Type.type(:file).create( + :path => file, :content => "rahness\n", :backup => ".puppet-bak" + ) + catalog = mk_catalog(obj) + catalog.apply backupfile = file + obj[:backup] @@tmpfiles << backupfile @@ -1079,16 +1031,16 @@ class TestFile < Test::Unit::TestCase assert_equal(0411, filemode(backupfile), "File mode is wrong for backupfile") - bucket = "bucket" + name = "bucket" bpath = tempfile() Dir.mkdir(bpath) - Puppet::Type.type(:filebucket).create( - :title => bucket, :path => bpath - ) + bucket = Puppet::Type.type(:filebucket).create(:title => name, :path => bpath) + catalog.add_resource(bucket) - obj[:backup] = bucket + obj[:backup] = name obj[:content] = "New content" - assert_apply(obj) + catalog.finalize + catalog.apply md5 = "18cc17fa3047fcc691fdf49c0a7f539a" dir, file, pathfile = Puppet::Network::Handler.filebucket.paths(bpath, md5) @@ -1301,8 +1253,6 @@ class TestFile < Test::Unit::TestCase end assert_equal("/my/file/for/testing", file.title) - assert_equal(file, Puppet::Type.type(:file)["/my/file/for/testing"]) - Puppet::Type.type(:file).clear end end @@ -1319,7 +1269,9 @@ class TestFile < Test::Unit::TestCase assert_equal(dir, File.readlink(link)) obj = Puppet::Type.newfile :path => link, :ensure => :link, :target => file, :recurse => false, :backup => "main" - assert_apply(obj) + catalog = mk_catalog(bucket, obj) + + catalog.apply assert_equal(file, File.readlink(link)) end @@ -1367,6 +1319,8 @@ class TestFile < Test::Unit::TestCase obj = Puppet::Type.newfile :path => path, :force => true, :links => :manage + catalog = mk_catalog(obj, bucket) + Puppet[:trace] = true ["main", false].each do |backup| obj[:backup] = backup @@ -1495,8 +1449,11 @@ class TestFile < Test::Unit::TestCase ) assert(dipper, "did not receive bucket client") file = Puppet::Type.newfile :path => dest, :source => source, :recurse => true, :backup => "rtest" + + catalog = mk_catalog(bucket, file) - assert_apply(file) + catalog.apply + dfiles.each do |f| assert(FileTest.exists?(f), "destfile %s was not created" % f) end @@ -1506,7 +1463,7 @@ class TestFile < Test::Unit::TestCase f.puts "boo: %s" % File.basename(sf) } } - assert_apply(file) + catalog.apply dfiles.each do |f| assert_equal("boo: %s\n" % File.basename(f), File.read(f), "file was not copied correctly") @@ -1532,6 +1489,9 @@ class TestFile < Test::Unit::TestCase def test_backup path = tempfile() file = Puppet::Type.newfile :path => path, :content => "yay" + + catalog = mk_catalog(file) + catalog.finalize # adds the default resources. [false, :false, "false"].each do |val| assert_nothing_raised do @@ -1561,6 +1521,7 @@ class TestFile < Test::Unit::TestCase # And then an existing bucket obj = Puppet::Type.type(:filebucket).create :name => "testing" + catalog.add_resource(obj) bucket = obj.bucket assert_nothing_raised do @@ -1576,12 +1537,12 @@ class TestFile < Test::Unit::TestCase file = File.join(dir, "file") File.open(file, "w") { |f| f.puts "" } obj = Puppet::Type.newfile :path => dir, :recurse => true, :mode => 0755 - mk_catalog obj + catalog = mk_catalog obj assert_equal("/%s" % obj.ref, obj.path) list = obj.eval_generate - fileobj = obj.class[file] + fileobj = catalog.resource(:file, file) assert(fileobj, "did not generate file object") assert_equal("/%s" % fileobj.ref, fileobj.path, "did not generate correct subfile path") end @@ -1596,14 +1557,6 @@ class TestFile < Test::Unit::TestCase assert(! FileTest.exists?(path), "File was not removed") end - # Testing #434 - def test_stripping_extra_slashes_during_lookup - file = Puppet::Type.newfile(:path => "/one/two") - %w{/one/two/ /one/two /one//two //one//two//}.each do |path| - assert(Puppet::Type.type(:file)[path], "could not look up file via path %s" % path) - end - end - # Testing #438 def test_creating_properties_conflict file = tempfile() @@ -1614,7 +1567,6 @@ class TestFile < Test::Unit::TestCase assert_nothing_raised("%s conflicted with ensure" % [param]) do Puppet::Type.newfile(:path => file, param => first, :ensure => :file) end - Puppet::Type.type(:file).clear params.each do |other| next if other == param assert_raise(Puppet::Error, "%s and %s did not conflict" % [param, other]) do @@ -1687,20 +1639,20 @@ class TestFile < Test::Unit::TestCase File.open(file, "w") { |f| f.puts "yay" } File.chmod(0644, file) obj = Puppet::Type.newfile(:path => dir, :mode => 0750, :recurse => "2") - config = mk_catalog(obj) + catalog = mk_catalog(obj) children = nil assert_nothing_raised("Failure when recursing") do children = obj.eval_generate end - assert(obj.class[subdir], "did not create subdir object") + assert(catalog.resource(:file, subdir), "did not create subdir object") children.each do |c| assert_nothing_raised("Failure when recursing on %s" % c) do - c.catalog = config + c.catalog = catalog others = c.eval_generate end end - oobj = obj.class[other] + oobj = catalog.resource(:file, other) assert(oobj, "did not create other object") assert_nothing_raised do @@ -1728,12 +1680,12 @@ class TestFile < Test::Unit::TestCase obj = Puppet::Type.newfile(:path => dir, :ensure => :directory, :recurse => true) - config = mk_catalog(obj) + catalog = mk_catalog(obj) children = nil assert_nothing_raised do children = obj.eval_generate end - fobj = obj.class[file] + fobj = catalog.resource(:file, file) assert(fobj, "did not create file object") assert(fobj.should(:ensure) != :directory, "ensure was passed to child") end diff --git a/test/ral/type/file/target.rb b/test/ral/type/file/target.rb index 035ea905b..b6c84df99 100755 --- a/test/ral/type/file/target.rb +++ b/test/ral/type/file/target.rb @@ -46,7 +46,7 @@ class TestFileTarget < Test::Unit::TestCase def test_linkrecurse dest = tempfile() link = @file.create :path => tempfile(), :recurse => true, :ensure => dest - mk_catalog link + catalog = mk_catalog(link) ret = nil @@ -64,7 +64,7 @@ class TestFileTarget < Test::Unit::TestCase assert_equal(:directory, link.should(:ensure), "ensure was not set to directory") assert_equal([File.join(link.title, "one")], ret.collect { |f| f.title }, "Did not get linked file") - oneobj = @file[File.join(link.title, "one")] + oneobj = catalog.resource(:file, File.join(link.title, "one")) assert_equal(one, oneobj.should(:target), "target was not set correctly") oneobj.remove @@ -83,7 +83,7 @@ class TestFileTarget < Test::Unit::TestCase "Did not get links back") returns.each do |path| - obj = @file[path] + obj = catalog.resource(:file, path) assert(path, "did not get obj for %s" % path) sdest = File.join(dest, File.basename(path)) assert_equal(sdest, obj.should(:target), @@ -100,16 +100,14 @@ class TestFileTarget < Test::Unit::TestCase system("mkdir -p %s" % subdir) system("touch %s" % file) - link = nil - assert_nothing_raised { - link = Puppet.type(:file).create( - :ensure => source, - :path => path, - :recurse => true - ) - } + link = Puppet.type(:file).create( + :ensure => source, + :path => path, + :recurse => true + ) - assert_apply(link) + catalog = mk_catalog(link) + catalog.apply sublink = File.join(path, "subdir") linkpath = File.join(sublink, "file") @@ -118,7 +116,7 @@ class TestFileTarget < Test::Unit::TestCase assert(File.symlink?(linkpath), "path is not a link") assert_equal(file, File.readlink(linkpath)) - assert_nil(@file[sublink], "objects were not removed") + assert_nil(catalog.resource(:file, sublink), "objects were not removed") assert_equal([], link.evaluate, "Link is not in sync") end diff --git a/test/ral/type/filebucket.rb b/test/ral/type/filebucket.rb index f9706663b..28be67b0c 100755 --- a/test/ral/type/filebucket.rb +++ b/test/ral/type/filebucket.rb @@ -65,12 +65,9 @@ class TestFileBucket < Test::Unit::TestCase def test_simplebucket name = "yayness" bucketpath = tempfile() - mkbucket(name, bucketpath) + resource = mkbucket(name, bucketpath) - bucket = nil - assert_nothing_raised { - bucket = Puppet.type(:filebucket).bucket(name) - } + bucket = resource.bucket assert_instance_of(Puppet::Network::Client.dipper, bucket) @@ -109,49 +106,5 @@ class TestFileBucket < Test::Unit::TestCase assert_equal(md5, newmd5) end - - def test_fileswithbuckets - name = "yayness" - mkbucket(name, tempfile()) - - bucket = nil - assert_nothing_raised { - bucket = Puppet.type(:filebucket).bucket(name) - } - - file = mktestfile() - assert_nothing_raised { - file[:backup] = name - } - - opath = tempfile() - @@tmpfiles << opath - File.open(opath, "w") { |f| f.puts "yaytest" } - - origmd5 = File.open(file.name) { |f| newmd5 = Digest::MD5.hexdigest(f.read) } - - file[:source] = opath - #assert_nothing_raised { - # file[:backup] = true - #} - - assert_apply(file) - - # so, we've now replaced the file with the opath file - assert_equal( - File.open(opath) { |f| newmd5 = Digest::MD5.hexdigest(f.read) }, - File.open(file.name) { |f| newmd5 = Digest::MD5.hexdigest(f.read) } - ) - - #File.chmod(0644, file.name) - assert_nothing_raised { - bucket.restore(file.name, origmd5) - } - - assert_equal( - origmd5, - File.open(file.name) { |f| newmd5 = Digest::MD5.hexdigest(f.read) } - ) - end end diff --git a/test/ral/type/fileignoresource.rb b/test/ral/type/fileignoresource.rb index ff867c879..996bca424 100755 --- a/test/ral/type/fileignoresource.rb +++ b/test/ral/type/fileignoresource.rb @@ -18,6 +18,8 @@ class TestFileIgnoreSources < Test::Unit::TestCase rescue system("rm -rf %s" % Puppet[:statefile]) end + + Facter.stubs(:to_hash).returns({}) end #This is not needed unless using md5 (correct me if I'm wrong) @@ -86,8 +88,6 @@ class TestFileIgnoreSources < Test::Unit::TestCase #This file should not assert(!(FileTest.exists?(File.join(topath,sourcefile2)))) - - Puppet::Type.allclear end def test_ignore_with_wildcard @@ -155,8 +155,6 @@ class TestFileIgnoreSources < Test::Unit::TestCase assert(!(FileTest.exists?(File.join(topath,sourcefile2)))) assert(!(FileTest.exists?(File.join(topath,subdir2)))) assert(!(FileTest.exists?(File.join(File.join(topath,subdir),sourcefile2)))) - Puppet::Type.allclear - end def test_ignore_array @@ -236,10 +234,5 @@ class TestFileIgnoreSources < Test::Unit::TestCase assert(!(FileTest.exists?(File.join(topath,subdir2))), "subdir2 in dest") assert(!(FileTest.exists?(File.join(topath,subdir3))), "anotherdir in dest") assert(!(FileTest.exists?(File.join(File.join(topath,subdir),sourcefile2))), "file2 in dest/sub") - - - Puppet::Type.allclear - end - end diff --git a/test/ral/type/filesources.rb b/test/ral/type/filesources.rb index a7bb6fefa..653db5c7d 100755 --- a/test/ral/type/filesources.rb +++ b/test/ral/type/filesources.rb @@ -21,6 +21,7 @@ class TestFileSources < Test::Unit::TestCase @file = Puppet::Type.type(:file) Puppet[:filetimeout] = -1 Puppet::Util::SUIDManager.stubs(:asuser).yields + Facter.stubs(:to_hash).returns({}) end def teardown @@ -69,7 +70,7 @@ class TestFileSources < Test::Unit::TestCase :name => path ) } - config = mk_catalog(file) + catalog = mk_catalog(file) child = nil assert_nothing_raised { child = file.newchild("childtest", true) @@ -84,7 +85,8 @@ class TestFileSources < Test::Unit::TestCase source = tempfile() dest = tempfile() - file = Puppet::Type.newfile :path => dest, :source => source, :title => "copier" + file = Puppet::Type.newfile :path => dest, :source => source, + :title => "copier" property = file.property(:source) @@ -123,10 +125,7 @@ class TestFileSources < Test::Unit::TestCase File.open(target, "w") { |f| f.puts "yay" } File.symlink(target, source) - file[:links] = :manage - assert_equal("link", property.describe(source)[:type]) - - # And then make sure links get followed + # And then make sure links get followed, otherwise file[:links] = :follow assert_equal("file", property.describe(source)[:type]) end @@ -275,7 +274,7 @@ class TestFileSources < Test::Unit::TestCase # The sourcerecurse method will only ever get called when we're # recursing, so we go ahead and set it. obj = Puppet::Type.newfile :source => source, :path => dest, :recurse => true - config = mk_catalog(obj) + catalog = mk_catalog(obj) result = nil sourced = nil @@ -284,12 +283,12 @@ class TestFileSources < Test::Unit::TestCase end assert_equal([destfile], sourced, "Did not get correct list of sourced objects") - dfileobj = @file[destfile] + dfileobj = catalog.resource(:file, destfile) assert(dfileobj, "Did not create destfile object") assert_equal([dfileobj], result) # Clean this up so it can be recreated - config.remove_resource(dfileobj) + catalog.remove_resource(dfileobj) # Make sure we correctly iterate over the sources nosource = tempfile() @@ -300,7 +299,7 @@ class TestFileSources < Test::Unit::TestCase result, sourced = obj.sourcerecurse(true) end assert_equal([destfile], sourced, "Did not get correct list of sourced objects") - dfileobj = @file[destfile] + dfileobj = catalog.resource(:file, destfile) assert(dfileobj, "Did not create destfile object with a missing source") assert_equal([dfileobj], result) dfileobj.remove @@ -356,23 +355,20 @@ class TestFileSources < Test::Unit::TestCase end def recursive_source_test(fromdir, todir) - Puppet::Type.allclear initstorage tofile = nil trans = nil - assert_nothing_raised { - tofile = Puppet.type(:file).create( - :path => todir, - :recurse => true, - :backup => false, - :source => fromdir - ) - } - assert_apply(tofile) + tofile = Puppet.type(:file).create( + :path => todir, + :recurse => true, + :backup => false, + :source => fromdir + ) + catalog = mk_catalog(tofile) + catalog.apply assert(FileTest.exists?(todir), "Created dir %s does not exist" % todir) - Puppet::Type.allclear end def run_complex_sources(networked = false) @@ -414,9 +410,6 @@ class TestFileSources < Test::Unit::TestCase def test_sources_with_deleted_destfiles fromdir, todir, one, two = run_complex_sources assert(FileTest.exists?(todir)) - - # We shouldn't have a 'two' file object in memory - assert_nil(@file[two], "object for 'two' is still in memory") # then delete a file File.unlink(two) @@ -532,107 +525,6 @@ class TestFileSources < Test::Unit::TestCase return file end - def test_NetworkSources - server = nil - mounts = { - "/" => "root" - } - - fileserverconf = mkfileserverconf(mounts) - - Puppet[:autosign] = true - - Puppet[:masterport] = 8762 - Puppet[:name] = "puppetmasterd" - Puppet[:certdnsnames] = "localhost" - - serverpid = nil - assert_nothing_raised() { - server = Puppet::Network::HTTPServer::WEBrick.new( - :Handlers => { - :CA => {}, # so that certs autogenerate - :FileServer => { - :Config => fileserverconf - } - } - ) - - } - serverpid = fork { - assert_nothing_raised() { - #trap(:INT) { server.shutdown; Kernel.exit! } - trap(:INT) { server.shutdown } - server.start - } - } - @@tmppids << serverpid - - sleep(1) - - fromdir, todir = run_complex_sources("root") - assert_trees_equal(fromdir,todir) - recursive_source_test(fromdir, todir) - assert_trees_equal(fromdir,todir) - - assert_nothing_raised { - system("kill -INT %s" % serverpid) - } - end - - def test_unmountedNetworkSources - server = nil - mounts = { - "/" => "root", - "/noexistokay" => "noexist" - } - - fileserverconf = mkfileserverconf(mounts) - - Puppet[:autosign] = true - Puppet[:masterport] = @port - Puppet[:certdnsnames] = "localhost" - - serverpid = nil - assert_nothing_raised("Could not start on port %s" % @port) { - server = Puppet::Network::HTTPServer::WEBrick.new( - :Port => @port, - :Handlers => { - :CA => {}, # so that certs autogenerate - :FileServer => { - :Config => fileserverconf - } - } - ) - - } - - serverpid = fork { - assert_nothing_raised() { - #trap(:INT) { server.shutdown; Kernel.exit! } - trap(:INT) { server.shutdown } - server.start - } - } - @@tmppids << serverpid - - sleep(1) - - name = File.join(tmpdir(), "nosourcefile") - file = Puppet.type(:file).create( - :source => "puppet://localhost/noexist/file", - :name => name - ) - - assert_nothing_raised { - file.retrieve - } - - comp = mk_catalog(file) - comp.apply - - assert(!FileTest.exists?(name), "File with no source exists anyway") - end - def test_alwayschecksum from = tempfile() to = tempfile() @@ -741,22 +633,18 @@ class TestFileSources < Test::Unit::TestCase File.open(source, "w") { |f| f.puts "yay" } File.symlink(source, link) - file = nil - assert_nothing_raised { - file = Puppet.type(:file).create( - :name => dest, - :source => link, - :links => :follow - ) - } + file = Puppet.type(:file).create(:name => dest, :source => link) - assert_events([:file_created], file) - assert(FileTest.file?(dest), "Destination is not a file") + catalog = mk_catalog(file) - # Now copy the links - file[:links] = :manage - assert_events([:link_created], file) - assert(FileTest.symlink?(dest), "Destination is not a link") + # Default to managing links + catalog.apply + assert(FileTest.symlink?(dest), "Did not create link") + + # Now follow the links + file[:links] = :follow + catalog.apply + assert(FileTest.file?(dest), "Destination is not a file") end def test_changes diff --git a/test/ral/type/group.rb b/test/ral/type/group.rb index d28c8eea5..3c29fe505 100755 --- a/test/ral/type/group.rb +++ b/test/ral/type/group.rb @@ -39,7 +39,6 @@ class TestGroup < Test::Unit::TestCase end def teardown - Puppet.type(:group).clear Puppet::Type.type(:group).defaultprovider = nil super end diff --git a/test/ral/type/host.rb b/test/ral/type/host.rb index 9b744eeec..df7cc508f 100755 --- a/test/ral/type/host.rb +++ b/test/ral/type/host.rb @@ -55,16 +55,12 @@ class TestHost < Test::Unit::TestCase end def test_list + list = nil assert_nothing_raised do - @hosttype.defaultprovider.prefetch + list = @hosttype.defaultprovider.instances end - count = 0 - @hosttype.each do |h| - count += 1 - end - - assert_equal(0, count, "Found hosts in empty file somehow") + assert_equal(0, list.length, "Found hosts in empty file somehow") end # Darwin will actually write to netinfo here. @@ -213,12 +209,13 @@ class TestHost < Test::Unit::TestCase def test_puppetalias host = mkhost() + catalog = mk_catalog(host) assert_nothing_raised { host[:alias] = "testing" } - same = host.class["testing"] + same = catalog.resource(:host, "testing") assert(same, "Could not retrieve by alias") end end diff --git a/test/ral/type/parameter.rb b/test/ral/type/parameter.rb index e1b8e00b3..04c4b0ce1 100755 --- a/test/ral/type/parameter.rb +++ b/test/ral/type/parameter.rb @@ -131,7 +131,7 @@ class TestParameter < Test::Unit::TestCase assert_instance_of(param, obj, "alias is an instance of the wrong class") # Make sure the alias got created - assert(type["foo"], "Did not retrieve object by its alias") + assert(config.resource(type.name, "foo"), "Did not retrieve object by its alias") # Now try it during initialization other = nil diff --git a/test/ral/type/sshkey.rb b/test/ral/type/sshkey.rb index b9aed20e8..333bc377d 100755 --- a/test/ral/type/sshkey.rb +++ b/test/ral/type/sshkey.rb @@ -49,26 +49,27 @@ class TestSSHKey < Test::Unit::TestCase @catalog ||= mk_catalog - assert_nothing_raised { - key = @sshkeytype.create( - :name => "host%s.madstop.com" % @kcount, - :key => "%sAAAAB3NzaC1kc3MAAACBAMnhSiku76y3EGkNCDsUlvpO8tRgS9wL4Eh54WZfQ2lkxqfd2uT/RTT9igJYDtm/+UHuBRdNGpJYW1Nw2i2JUQgQEEuitx4QKALJrBotejGOAWxxVk6xsh9xA0OW8Q3ZfuX2DDitfeC8ZTCl4xodUMD8feLtP+zEf8hxaNamLlt/AAAAFQDYJyf3vMCWRLjTWnlxLtOyj/bFpwAAAIEAmRxxXb4jjbbui9GYlZAHK00689DZuX0EabHNTl2yGO5KKxGC6Esm7AtjBd+onfu4Rduxut3jdI8GyQCIW8WypwpJofCIyDbTUY4ql0AQUr3JpyVytpnMijlEyr41FfIb4tnDqnRWEsh2H7N7peW+8DWZHDFnYopYZJ9Yu4/jHRYAAACAERG50e6aRRb43biDr7Ab9NUCgM9bC0SQscI/xdlFjac0B/kSWJYTGVARWBDWug705hTnlitY9cLC5Ey/t/OYOjylTavTEfd/bh/8FkAYO+pWdW3hx6p97TBffK0b6nrc6OORT2uKySbbKOn0681nNQh4a6ueR3JRppNkRPnTk5c=" % @kcount, - :type => "ssh-dss", - :alias => ["192.168.0.%s" % @kcount], - :catalog => @catalog - ) - } + key = @sshkeytype.create( + :name => "host%s.madstop.com" % @kcount, + :key => "%sAAAAB3NzaC1kc3MAAACBAMnhSiku76y3EGkNCDsUlvpO8tRgS9wL4Eh54WZfQ2lkxqfd2uT/RTT9igJYDtm/+UHuBRdNGpJYW1Nw2i2JUQgQEEuitx4QKALJrBotejGOAWxxVk6xsh9xA0OW8Q3ZfuX2DDitfeC8ZTCl4xodUMD8feLtP+zEf8hxaNamLlt/AAAAFQDYJyf3vMCWRLjTWnlxLtOyj/bFpwAAAIEAmRxxXb4jjbbui9GYlZAHK00689DZuX0EabHNTl2yGO5KKxGC6Esm7AtjBd+onfu4Rduxut3jdI8GyQCIW8WypwpJofCIyDbTUY4ql0AQUr3JpyVytpnMijlEyr41FfIb4tnDqnRWEsh2H7N7peW+8DWZHDFnYopYZJ9Yu4/jHRYAAACAERG50e6aRRb43biDr7Ab9NUCgM9bC0SQscI/xdlFjac0B/kSWJYTGVARWBDWug705hTnlitY9cLC5Ey/t/OYOjylTavTEfd/bh/8FkAYO+pWdW3hx6p97TBffK0b6nrc6OORT2uKySbbKOn0681nNQh4a6ueR3JRppNkRPnTk5c=" % @kcount, + :type => "ssh-dss", + :alias => ["192.168.0.%s" % @kcount], + :catalog => @catalog + ) + + @catalog.add_resource(key) return key end def test_instances + list = nil assert_nothing_raised { - Puppet.type(:sshkey).instances + list = Puppet.type(:sshkey).instances } count = 0 - @sshkeytype.each do |h| + list.each do |h| count += 1 end @@ -90,7 +91,6 @@ class TestSSHKey < Test::Unit::TestCase # Now create a new key object name = key.name key = nil - @sshkeytype.clear key = @sshkeytype.create :name => name, :target => file, :provider => :parsed key.retrieve @@ -102,19 +102,17 @@ class TestSSHKey < Test::Unit::TestCase def test_moddingkey key = mkkey() - assert_events([:sshkey_created], key) + @catalog.apply key.retrieve aliases = %w{madstop kirby yayness} key[:alias] = aliases - params = key.instance_variable_get("@parameters") - assert_events([:sshkey_changed], key) + @catalog.apply aliases.each do |name| - assert_equal(key, key.class[name], - "alias was not set") + assert_equal(key.object_id, @catalog.resource(:sshkey, name).object_id, "alias %s was not set" % name) end end @@ -136,7 +134,9 @@ class TestSSHKey < Test::Unit::TestCase key[:alias] = "testing" } - same = key.class["testing"] + key.finish + + same = @catalog.resource(:sshkey, "testing") assert(same, "Could not retrieve by alias") end @@ -170,7 +170,10 @@ class TestSSHKey < Test::Unit::TestCase } assert_apply(*keys) keys.clear - Puppet.type(:sshkey).clear + + @catalog.clear(true) + @catalog = nil + newkey = mkkey() #newkey[:ensure] = :present names << newkey.name @@ -178,17 +181,9 @@ class TestSSHKey < Test::Unit::TestCase # Verify we can retrieve that info assert_nothing_raised("Could not retrieve after second write") { - newkey.provider.class.prefetch - newkey.retrieve + newkey.provider.prefetch } - # And verify that we have data for everything - names.each { |name| - key = Puppet.type(:sshkey)[name] || - Puppet.type(:sshkey).create(:name => name) - assert(key, "Could not retrieve key for %s" % name) - assert(key.provider.exists?, "key %s is missing" % name) - } + assert(newkey.provider.exists?, "Did not see key in file") end end - diff --git a/test/ral/type/tidy.rb b/test/ral/type/tidy.rb index 657ca6e93..a3a3b4b01 100755 --- a/test/ral/type/tidy.rb +++ b/test/ral/type/tidy.rb @@ -207,8 +207,6 @@ class TestTidy < Test::Unit::TestCase assert_apply(tidy) assert(! FileTest.exists?(path), "file did not get tidied") - tidy.class.clear - # Now try one with just an age attribute. time = Time.now - 10 stat = stub 'stat', :mtime => time, :atime => time, :ftype => "file" diff --git a/test/ral/type/user.rb b/test/ral/type/user.rb index 87d5b6075..1a2de2649 100755 --- a/test/ral/type/user.rb +++ b/test/ral/type/user.rb @@ -455,6 +455,7 @@ class TestUser < Test::Unit::TestCase # Testing #455 def test_autorequire_with_no_group_should user = Puppet::Type.type(:user).create(:name => "yaytest", :check => :all) + catalog = mk_catalog(user) assert_nothing_raised do user.autorequire diff --git a/test/ral/type/yumrepo.rb b/test/ral/type/yumrepo.rb index 899a02135..273179bc4 100755 --- a/test/ral/type/yumrepo.rb +++ b/test/ral/type/yumrepo.rb @@ -17,6 +17,9 @@ class TestYumRepo < Test::Unit::TestCase f.print "[main]\nreposdir=#{@yumdir} /no/such/dir\n" end Puppet.type(:yumrepo).yumconf = @yumconf + + # It needs to be reset each time, otherwise the cache is used. + Puppet.type(:yumrepo).inifile = nil end # Modify one existing section @@ -24,19 +27,17 @@ class TestYumRepo < Test::Unit::TestCase copy_datafiles devel = make_repo("development", { :descr => "New description" }) current_values = devel.retrieve + assert_equal("development", devel[:name]) - assert_equal('Fedora Core $releasever - Development Tree', - current_values[devel.property(:descr)]) - assert_equal('New description', - devel.property(:descr).should) + assert_equal('Fedora Core $releasever - Development Tree', current_values[devel.property(:descr)]) + assert_equal('New description', devel.property(:descr).should) assert_apply(devel) + inifile = Puppet.type(:yumrepo).read() assert_equal('New description', inifile['development']['name']) - assert_equal('Fedora Core $releasever - $basearch - Base', - inifile['base']['name']) + assert_equal('Fedora Core $releasever - $basearch - Base', inifile['base']['name']) assert_equal("foo\n bar\n baz", inifile['base']['exclude']) - assert_equal(['base', 'development', 'main'], - all_sections(inifile)) + assert_equal(['base', 'development', 'main'], all_sections(inifile)) end # Create a new section diff --git a/test/util/filetype.rb b/test/util/filetype.rb index 6c7ede07b..24a968552 100755 --- a/test/util/filetype.rb +++ b/test/util/filetype.rb @@ -85,7 +85,7 @@ class TestFileType < Test::Unit::TestCase assert_nothing_raised("Could not call backup with no buckets") do obj.backup end - puppet = type["puppet"] + puppet = type.mkdefaultbucket assert(puppet, "Did not create default filebucket") assert_equal("one", puppet.bucket.getfile(Digest::MD5.hexdigest(File.read(path))), "Could not get file from backup") @@ -99,7 +99,7 @@ class TestFileType < Test::Unit::TestCase assert_equal("two", puppet.bucket.getfile(Digest::MD5.hexdigest(File.read(path))), "Could not get file from backup") end - if Facter["operatingsystem"].value == "Darwin" + if Facter["operatingsystem"].value == "Darwin" and Facter["operatingsystemrelease"] != "9.1.0" def test_ninfotoarray obj = nil type = nil diff --git a/test/util/posixtest.rb b/test/util/posixtest.rb index 34d68e3a2..8fd11b086 100755 --- a/test/util/posixtest.rb +++ b/test/util/posixtest.rb @@ -35,16 +35,6 @@ class TestPosixUtil < Test::Unit::TestCase end end - def test_get_provider_value - user = nonrootuser - obj = mk_posix_resource(:user, user) - - assert_nothing_raised do - assert_equal(user.uid, get_provider_value(:user, :uid, user.uid)) - assert_equal(user.name, get_provider_value(:user, :name, user.name)) - end - end - def test_gid_and_uid {:user => nonrootuser, :group => nonrootgroup}.each do |type, obj| method = idfield(type) diff --git a/test/util/settings.rb b/test/util/settings.rb index cf5dca76d..de6fff946 100755 --- a/test/util/settings.rb +++ b/test/util/settings.rb @@ -256,69 +256,6 @@ yay = /a/path end end - def test_old_parse - text = %{ -one = this is a test -two = another test -owner = root -group = root -yay = /a/path - -[section1] - attr = value - owner = puppet - group = puppet - attrdir = /some/dir - attr3 = $attrdir/other - } - - file = tempfile() - File.open(file, "w") { |f| f.puts text } - - assert_nothing_raised { - @config.setdefaults("puppet", - :one => ["a", "one"], - :two => ["a", "two"], - :yay => ["/default/path", "boo"], - :mkusers => [true, "uh, yeah"] - ) - } - - assert_nothing_raised { - @config.setdefaults("section1", - :attr => ["a", "one"], - :attrdir => ["/another/dir", "two"], - :attr3 => ["$attrdir/maybe", "boo"] - ) - } - - assert_nothing_raised { - @config.old_parse(file) - } - - assert_equal("value", @config[:attr]) - assert_equal("/some/dir", @config[:attrdir]) - assert_equal(:directory, @config.element(:attrdir).type) - assert_equal("/some/dir/other", @config[:attr3]) - - elem = nil - assert_nothing_raised { - elem = @config.element(:attr3) - } - - assert(elem) - assert_equal("puppet", elem.owner) - - config = nil - assert_nothing_raised { - config = @config.to_config - } - - assert_nothing_raised("Could not create transportable config") { - @config.to_transportable - } - end - def test_parse result = { :main => {:main => "main", :bad => "invalid", :cliparam => "reset"}, diff --git a/test/util/utiltest.rb b/test/util/utiltest.rb index 35e1446a1..a8dfca9c2 100755 --- a/test/util/utiltest.rb +++ b/test/util/utiltest.rb @@ -264,24 +264,5 @@ class TestPuppetUtil < Test::Unit::TestCase # Puppet::Util.execute(cmd, 0, 0) #} end - - # This is mostly to test #380. - def test_get_provider_value - group = Puppet::Type.type(:group).create :name => "yayness", :ensure => :present - - root = Puppet::Type.type(:user).create :name => "root", :ensure => :present - - val = nil - assert_nothing_raised do - val = Puppet::Util.get_provider_value(:group, :gid, "yayness") - end - assert_nil(val, "returned a value on a missing group") - - # Now make sure we get a value for one we know exists - assert_nothing_raised do - val = Puppet::Util.get_provider_value(:user, :uid, "root") - end - assert_equal(0, val, "got invalid uid for root") - end end |