diff --git a/install.rb b/install.rb index d015c4d3f..0dc2d52e3 100755 --- a/install.rb +++ b/install.rb @@ -1,457 +1,457 @@ #! /usr/bin/env ruby #-- # Copyright 2004 Austin Ziegler # Install utility. Based on the original installation script for rdoc by the # Pragmatic Programmers. # # This program is free software. It may be redistributed and/or modified under # the terms of the GPL version 2 (or later) or the Ruby licence. # # Usage # ----- # In most cases, if you have a typical project layout, you will need to do # absolutely nothing to make this work for you. This layout is: # # bin/ # executable files -- "commands" # lib/ # the source of the library # tests/ # unit tests # # The default behaviour: # 1) Run all unit test files (ending in .rb) found in all directories under # tests/. # 2) Build Rdoc documentation from all files in bin/ (excluding .bat and .cmd), # all .rb files in lib/, ./README, ./ChangeLog, and ./Install. # 3) Build ri documentation from all files in bin/ (excluding .bat and .cmd), # and all .rb files in lib/. This is disabled by default on Microsoft Windows. # 4) Install commands from bin/ into the Ruby bin directory. On Windows, if a # if a corresponding batch file (.bat or .cmd) exists in the bin directory, # it will be copied over as well. Otherwise, a batch file (always .bat) will # be created to run the specified command. # 5) Install all library files ending in .rb from lib/ into Ruby's # site_lib/version directory. # #++ require 'rbconfig' require 'find' require 'fileutils' begin require 'ftools' # apparently on some system ftools doesn't get loaded $haveftools = true rescue LoadError puts "ftools not found. Using FileUtils instead.." $haveftools = false end require 'optparse' require 'ostruct' begin require 'rdoc/rdoc' $haverdoc = true rescue LoadError puts "Missing rdoc; skipping documentation" $haverdoc = false end -PREREQS = %w{openssl facter xmlrpc/client xmlrpc/server cgi} +PREREQS = %w{openssl facter cgi} MIN_FACTER_VERSION = 1.5 InstallOptions = OpenStruct.new def glob(list) g = list.map { |i| Dir.glob(i) } g.flatten! g.compact! g.reject! { |e| e =~ /\.svn/ } g end # Set these values to what you want installed. configs = glob(%w{conf/auth.conf}) sbins = glob(%w{sbin/*}) bins = glob(%w{bin/*}) rdoc = glob(%w{bin/* sbin/* lib/**/*.rb README README-library CHANGELOG TODO Install}).reject { |e| e=~ /\.(bat|cmd)$/ } ri = glob(%w{bin/*.rb sbin/* lib/**/*.rb}).reject { |e| e=~ /\.(bat|cmd)$/ } man = glob(%w{man/man[0-9]/*}) libs = glob(%w{lib/**/*.rb lib/**/*.erb lib/**/*.py lib/puppet/util/command_line/*}) tests = glob(%w{test/**/*.rb}) def do_configs(configs, target, strip = 'conf/') Dir.mkdir(target) unless File.directory? target configs.each do |cf| ocf = File.join(InstallOptions.config_dir, cf.gsub(/#{strip}/, '')) if $haveftools File.install(cf, ocf, 0644, true) else FileUtils.install(cf, ocf, {:mode => 0644, :verbose => true}) end end end def do_bins(bins, target, strip = 's?bin/') Dir.mkdir(target) unless File.directory? target bins.each do |bf| obf = bf.gsub(/#{strip}/, '') install_binfile(bf, obf, target) end end def do_libs(libs, strip = 'lib/') libs.each do |lf| olf = File.join(InstallOptions.site_dir, lf.gsub(/#{strip}/, '')) op = File.dirname(olf) if $haveftools File.makedirs(op, true) File.chmod(0755, op) File.install(lf, olf, 0644, true) else FileUtils.makedirs(op, {:mode => 0755, :verbose => true}) FileUtils.chmod(0755, op) FileUtils.install(lf, olf, {:mode => 0644, :verbose => true}) end end end def do_man(man, strip = 'man/') man.each do |mf| omf = File.join(InstallOptions.man_dir, mf.gsub(/#{strip}/, '')) om = File.dirname(omf) if $haveftools File.makedirs(om, true) File.chmod(0755, om) File.install(mf, omf, 0644, true) else FileUtils.makedirs(om, {:mode => 0755, :verbose => true}) FileUtils.chmod(0755, om) FileUtils.install(mf, omf, {:mode => 0644, :verbose => true}) end gzip = %x{which gzip} gzip.chomp! %x{#{gzip} -f #{omf}} end end # Verify that all of the prereqs are installed def check_prereqs PREREQS.each { |pre| begin require pre if pre == "facter" # to_f isn't quite exact for strings like "1.5.1" but is good # enough for this purpose. facter_version = Facter.version.to_f if facter_version < MIN_FACTER_VERSION puts "Facter version: #{facter_version}; minimum required: #{MIN_FACTER_VERSION}; cannot install" exit -1 end end rescue LoadError puts "Could not load #{pre}; cannot install" exit -1 end } end ## # Prepare the file installation. # def prepare_installation $operatingsystem = Facter["operatingsystem"].value InstallOptions.configs = true # Only try to do docs if we're sure they have rdoc if $haverdoc InstallOptions.rdoc = true InstallOptions.ri = $operatingsystem != "windows" else InstallOptions.rdoc = false InstallOptions.ri = false end InstallOptions.tests = true ARGV.options do |opts| opts.banner = "Usage: #{File.basename($0)} [options]" opts.separator "" opts.on('--[no-]rdoc', 'Prevents the creation of RDoc output.', 'Default on.') do |onrdoc| InstallOptions.rdoc = onrdoc end opts.on('--[no-]ri', 'Prevents the creation of RI output.', 'Default off on mswin32.') do |onri| InstallOptions.ri = onri end opts.on('--[no-]tests', 'Prevents the execution of unit tests.', 'Default on.') do |ontest| InstallOptions.tests = ontest end opts.on('--[no-]configs', 'Prevents the installation of config files', 'Default off.') do |ontest| InstallOptions.configs = ontest end opts.on('--destdir[=OPTIONAL]', 'Installation prefix for all targets', 'Default essentially /') do |destdir| InstallOptions.destdir = destdir end opts.on('--configdir[=OPTIONAL]', 'Installation directory for config files', 'Default /etc/puppet') do |configdir| InstallOptions.configdir = configdir end opts.on('--bindir[=OPTIONAL]', 'Installation directory for binaries', 'overrides Config::CONFIG["bindir"]') do |bindir| InstallOptions.bindir = bindir end opts.on('--sbindir[=OPTIONAL]', 'Installation directory for system binaries', 'overrides Config::CONFIG["sbindir"]') do |sbindir| InstallOptions.sbindir = sbindir end opts.on('--sitelibdir[=OPTIONAL]', 'Installation directory for libraries', 'overrides Config::CONFIG["sitelibdir"]') do |sitelibdir| InstallOptions.sitelibdir = sitelibdir end opts.on('--mandir[=OPTIONAL]', 'Installation directory for man pages', 'overrides Config::CONFIG["mandir"]') do |mandir| InstallOptions.mandir = mandir end opts.on('--quick', 'Performs a quick installation. Only the', 'installation is done.') do |quick| InstallOptions.rdoc = false InstallOptions.ri = false InstallOptions.tests = false InstallOptions.configs = true end opts.on('--full', 'Performs a full installation. All', 'optional installation steps are run.') do |full| InstallOptions.rdoc = true InstallOptions.ri = true InstallOptions.tests = true InstallOptions.configs = true end opts.separator("") opts.on_tail('--help', "Shows this help text.") do $stderr.puts opts exit end opts.parse! end tmpdirs = [ENV['TMP'], ENV['TEMP'], "/tmp", "/var/tmp", "."] version = [Config::CONFIG["MAJOR"], Config::CONFIG["MINOR"]].join(".") libdir = File.join(Config::CONFIG["libdir"], "ruby", version) # Mac OS X 10.5 and higher declare bindir and sbindir as # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/sbin # which is not generally where people expect executables to be installed # These settings are appropriate defaults for all OS X versions. if RUBY_PLATFORM =~ /^universal-darwin[\d\.]+$/ Config::CONFIG['bindir'] = "/usr/bin" Config::CONFIG['sbindir'] = "/usr/sbin" end if not InstallOptions.configdir.nil? configdir = InstallOptions.configdir elsif $operatingsystem == "windows" begin require 'win32/dir' rescue LoadError => e puts "Cannot run on Microsoft Windows without the sys-admin, win32-process, win32-dir & win32-service gems: #{e}" exit -1 end configdir = File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "etc") else configdir = "/etc/puppet" end if not InstallOptions.bindir.nil? bindir = InstallOptions.bindir else bindir = Config::CONFIG['bindir'] end if not InstallOptions.sbindir.nil? sbindir = InstallOptions.sbindir else sbindir = Config::CONFIG['sbindir'] end if not InstallOptions.sitelibdir.nil? sitelibdir = InstallOptions.sitelibdir else sitelibdir = Config::CONFIG["sitelibdir"] if sitelibdir.nil? sitelibdir = $LOAD_PATH.find { |x| x =~ /site_ruby/ } if sitelibdir.nil? sitelibdir = File.join(libdir, "site_ruby") elsif sitelibdir !~ Regexp.quote(version) sitelibdir = File.join(sitelibdir, version) end end end if not InstallOptions.mandir.nil? mandir = InstallOptions.mandir else mandir = Config::CONFIG['mandir'] end # This is the new way forward if not InstallOptions.destdir.nil? destdir = InstallOptions.destdir # To be deprecated once people move over to using --destdir option elsif not ENV['DESTDIR'].nil? destdir = ENV['DESTDIR'] warn "DESTDIR is deprecated. Use --destdir instead." else destdir = '' end configdir = join(destdir, configdir) bindir = join(destdir, bindir) sbindir = join(destdir, sbindir) mandir = join(destdir, mandir) sitelibdir = join(destdir, sitelibdir) FileUtils.makedirs(configdir) if InstallOptions.configs FileUtils.makedirs(bindir) FileUtils.makedirs(sbindir) FileUtils.makedirs(mandir) FileUtils.makedirs(sitelibdir) tmpdirs << bindir InstallOptions.tmp_dirs = tmpdirs.compact InstallOptions.site_dir = sitelibdir InstallOptions.config_dir = configdir InstallOptions.bin_dir = bindir InstallOptions.sbin_dir = sbindir InstallOptions.lib_dir = libdir InstallOptions.man_dir = mandir end ## # Join two paths. On Windows, dir must be converted to a relative path, # by stripping the drive letter, but only if the basedir is not empty. # def join(basedir, dir) return "#{basedir}#{dir[2..-1]}" if $operatingsystem == "windows" and basedir.length > 0 and dir.length > 2 "#{basedir}#{dir}" end ## # Build the rdoc documentation. Also, try to build the RI documentation. # def build_rdoc(files) return unless $haverdoc begin r = RDoc::RDoc.new r.document(["--main", "README", "--title", "Puppet -- Site Configuration Management", "--line-numbers"] + files) rescue RDoc::RDocError => e $stderr.puts e.message rescue Exception => e $stderr.puts "Couldn't build RDoc documentation\n#{e.message}" end end def build_ri(files) return unless $haverdoc begin ri = RDoc::RDoc.new #ri.document(["--ri-site", "--merge"] + files) ri.document(["--ri-site"] + files) rescue RDoc::RDocError => e $stderr.puts e.message rescue Exception => e $stderr.puts "Couldn't build Ri documentation\n#{e.message}" $stderr.puts "Continuing with install..." end end def run_tests(test_list) require 'test/unit/ui/console/testrunner' $LOAD_PATH.unshift "lib" test_list.each do |test| next if File.directory?(test) require test end tests = [] ObjectSpace.each_object { |o| tests << o if o.kind_of?(Class) } tests.delete_if { |o| !o.ancestors.include?(Test::Unit::TestCase) } tests.delete_if { |o| o == Test::Unit::TestCase } tests.each { |test| Test::Unit::UI::Console::TestRunner.run(test) } $LOAD_PATH.shift rescue LoadError puts "Missing testrunner library; skipping tests" end ## # Install file(s) from ./bin to Config::CONFIG['bindir']. Patch it on the way # to insert a #! line; on a Unix install, the command is named as expected # (e.g., bin/rdoc becomes rdoc); the shebang line handles running it. Under # windows, we add an '.rb' extension and let file associations do their stuff. def install_binfile(from, op_file, target) tmp_dir = nil InstallOptions.tmp_dirs.each do |t| if File.directory?(t) and File.writable?(t) tmp_dir = t break end end fail "Cannot find a temporary directory" unless tmp_dir tmp_file = File.join(tmp_dir, '_tmp') ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) File.open(from) do |ip| File.open(tmp_file, "w") do |op| ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) op.puts "#!#{ruby}" contents = ip.readlines contents.shift if contents[0] =~ /^#!/ op.write contents.join end end if $operatingsystem == "windows" installed_wrapper = false if File.exists?("#{from}.bat") FileUtils.install("#{from}.bat", File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) installed_wrapper = true end if File.exists?("#{from}.cmd") FileUtils.install("#{from}.cmd", File.join(target, "#{op_file}.cmd"), :mode => 0755, :verbose => true) installed_wrapper = true end if not installed_wrapper tmp_file2 = File.join(tmp_dir, '_tmp_wrapper') cwn = File.join(Config::CONFIG['bindir'], op_file) cwv = <<-EOS @echo off if "%OS%"=="Windows_NT" goto WinNT #{ruby} -x "#{cwn}" %1 %2 %3 %4 %5 %6 %7 %8 %9 goto done :WinNT #{ruby} -x "#{cwn}" %* goto done :done EOS File.open(tmp_file2, "w") { |cw| cw.puts cwv } FileUtils.install(tmp_file2, File.join(target, "#{op_file}.bat"), :mode => 0755, :verbose => true) File.unlink(tmp_file2) installed_wrapper = true end end FileUtils.install(tmp_file, File.join(target, op_file), :mode => 0755, :verbose => true) File.unlink(tmp_file) end check_prereqs prepare_installation #run_tests(tests) if InstallOptions.tests #build_rdoc(rdoc) if InstallOptions.rdoc #build_ri(ri) if InstallOptions.ri do_configs(configs, InstallOptions.config_dir) if InstallOptions.configs do_bins(sbins, InstallOptions.sbin_dir) do_bins(bins, InstallOptions.bin_dir) do_libs(libs) do_man(man) unless $operatingsystem == "windows" diff --git a/lib/puppet/application/agent.rb b/lib/puppet/application/agent.rb index 3cee7c374..eb747b96f 100644 --- a/lib/puppet/application/agent.rb +++ b/lib/puppet/application/agent.rb @@ -1,498 +1,473 @@ require 'puppet/application' class Puppet::Application::Agent < Puppet::Application should_parse_config run_mode :agent attr_accessor :args, :agent, :daemon, :host def preinit # Do an initial trap, so that cancels don't get a stack trace. Signal.trap(:INT) do $stderr.puts "Cancelling startup" exit(0) end { :waitforcert => nil, :detailed_exitcodes => false, :verbose => false, :debug => false, :centrallogs => false, :setdest => false, :enable => false, :disable => false, :client => true, :fqdn => nil, :serve => [], :digest => :MD5, :fingerprint => false, }.each do |opt,val| options[opt] = val end @args = {} require 'puppet/daemon' @daemon = Puppet::Daemon.new @daemon.argv = ARGV.dup end option("--centrallogging") option("--disable") option("--enable") option("--debug","-d") option("--fqdn FQDN","-f") option("--test","-t") option("--verbose","-v") option("--fingerprint") option("--digest DIGEST") - option("--serve HANDLER", "-s") do |arg| - if Puppet::Network::Handler.handler(arg) - options[:serve] << arg.to_sym - else - raise "Could not find handler for #{arg}" - end - end - option("--no-client") do |arg| options[:client] = false end option("--detailed-exitcodes") do |arg| options[:detailed_exitcodes] = true end option("--logdest DEST", "-l DEST") do |arg| begin Puppet::Util::Log.newdestination(arg) options[:setdest] = true rescue => detail puts detail.backtrace if Puppet[:debug] $stderr.puts detail.to_s end end option("--waitforcert WAITFORCERT", "-w") do |arg| options[:waitforcert] = arg.to_i end option("--port PORT","-p") do |arg| @args[:Port] = arg end def help <<-HELP puppet-agent(8) -- The puppet agent daemon ======== SYNOPSIS -------- Retrieves the client configuration from the puppet master and applies it to the local host. This service may be run as a daemon, run periodically using cron (or something similar), or run interactively for testing purposes. USAGE ----- puppet agent [--certname ] [-D|--daemonize|--no-daemonize] [-d|--debug] [--detailed-exitcodes] [--digest ] [--disable] [--enable] [--fingerprint] [-h|--help] [-l|--logdest syslog||console] - [--no-client] [--noop] [-o|--onetime] [--serve ] [-t|--test] + [--no-client] [--noop] [-o|--onetime] [-t|--test] [-v|--verbose] [-V|--version] [-w|--waitforcert ] DESCRIPTION ----------- This is the main puppet client. Its job is to retrieve the local machine's configuration from a remote server and apply it. In order to successfully communicate with the remote server, the client must have a certificate signed by a certificate authority that the server trusts; the recommended method for this, at the moment, is to run a certificate authority as part of the puppet server (which is the default). The client will connect and request a signed certificate, and will continue connecting until it receives one. Once the client has a signed certificate, it will retrieve its configuration and apply it. USAGE NOTES ----------- 'puppet agent' does its best to find a compromise between interactive use and daemon use. Run with no arguments and no configuration, it will go into the background, attempt to get a signed certificate, and retrieve and apply its configuration every 30 minutes. Some flags are meant specifically for interactive use -- in particular, 'test', 'tags' or 'fingerprint' are useful. 'test' enables verbose logging, causes the daemon to stay in the foreground, exits if the server's configuration is invalid (this happens if, for instance, you've left a syntax error on the server), and exits after running the configuration once (rather than hanging around as a long-running process). 'tags' allows you to specify what portions of a configuration you want to apply. Puppet elements are tagged with all of the class or definition names that contain them, and you can use the 'tags' flag to specify one of these names, causing only configuration elements contained within that class or definition to be applied. This is very useful when you are testing new configurations -- for instance, if you are just starting to manage 'ntpd', you would put all of the new elements into an 'ntpd' class, and call puppet with '--tags ntpd', which would only apply that small portion of the configuration during your testing, rather than applying the whole thing. 'fingerprint' is a one-time flag. In this mode 'puppet agent' will run once and display on the console (and in the log) the current certificate (or certificate request) fingerprint. Providing the '--digest' option allows to use a different digest algorithm to generate the fingerprint. The main use is to verify that before signing a certificate request on the master, the certificate request the master received is the same as the one the client sent (to prevent against man-in-the-middle attacks when signing certificates). OPTIONS ------- Note that any configuration parameter that's valid in the configuration file is also a valid long argument. For example, 'server' is a valid configuration parameter, so you can specify '--server ' as an argument. See the configuration file documentation at http://docs.puppetlabs.com/references/stable/configuration.html for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet agent with '--genconfig'. * --certname: Set the certname (unique ID) of the client. The master reads this unique identifying string, which is usually set to the node's fully-qualified domain name, to determine which configurations the node will receive. Use this option to debug setup problems or implement unusual node identification schemes. * --daemonize: Send the process into the background. This is the default. * --no-daemonize: Do not send the process into the background. * --debug: Enable full debugging. * --detailed-exitcodes: Provide transaction information via exit codes. If this is enabled, an exit code of '2' means there were changes, an exit code of '4' means there were failures during the transaction, and an exit code of '6' means there were both changes and failures. * --digest: Change the certificate fingerprinting digest algorithm. The default is MD5. Valid values depends on the version of OpenSSL installed, but should always at least contain MD5, MD2, SHA1 and SHA256. * --disable: Disable working on the local system. This puts a lock file in place, causing 'puppet agent' not to work on the system until the lock file is removed. This is useful if you are testing a configuration and do not want the central configuration to override the local state until everything is tested and committed. 'puppet agent' uses the same lock file while it is running, so no more than one 'puppet agent' process is working at a time. 'puppet agent' exits after executing this. * --enable: Enable working on the local system. This removes any lock file, causing 'puppet agent' to start managing the local system again (although it will continue to use its normal scheduling, so it might not start for another half hour). 'puppet agent' exits after executing this. * --fingerprint: Display the current certificate or certificate signing request fingerprint and then exit. Use the '--digest' option to change the digest algorithm used. * --help: Print this help message * --logdest: Where to send messages. Choose between syslog, the console, and a log file. Defaults to sending messages to syslog, or the console if debugging or verbosity is enabled. * --no-client: Do not create a config client. This will cause the daemon to run without ever checking for its configuration automatically, and only makes sense when puppet agent is being run with listen = true in puppet.conf or was started with the `--listen` option. * --noop: Use 'noop' mode where the daemon runs in a no-op or dry-run mode. This is useful for seeing what changes Puppet will make without actually executing the changes. * --onetime: Run the configuration once. Runs a single (normally daemonized) Puppet run. Useful for interactively running puppet agent when used in conjunction with the --no-daemonize option. -* --serve: - Start another type of server. By default, 'puppet agent' will start a - service handler that allows authenticated and authorized remote nodes - to trigger the configuration to be pulled down and applied. You can - specify any handler here that does not require configuration, e.g., - filebucket, ca, or resource. The handlers are in - 'lib/puppet/network/handler', and the names must match exactly, both - in the call to 'serve' and in 'namespaceauth.conf'. - * --test: Enable the most common options used for testing. These are 'onetime', 'verbose', 'ignorecache', 'no-daemonize', 'no-usecacheonfailure', 'detailed-exit-codes', 'no-splay', and 'show_diff'. * --verbose: Turn on verbose reporting. * --version: Print the puppet version number and exit. * --waitforcert: This option only matters for daemons that do not yet have certificates and it is enabled by default, with a value of 120 (seconds). This causes 'puppet agent' to connect to the server every 2 minutes and ask it to sign a certificate request. This is useful for the initial setup of a puppet client. You can turn off waiting for certificates by specifying a time of 0. EXAMPLE ------- $ puppet agent --server puppet.domain.com DIAGNOSTICS ----------- Puppet agent accepts the following signals: * SIGHUP: Restart the puppet agent daemon. * SIGINT and SIGTERM: Shut down the puppet agent daemon. * SIGUSR1: Immediately retrieve and apply configurations from the puppet master. AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end def run_command return fingerprint if options[:fingerprint] return onetime if Puppet[:onetime] main end def fingerprint unless cert = host.certificate || host.certificate_request $stderr.puts "Fingerprint asked but no certificate nor certificate request have yet been issued" exit(1) return end unless fingerprint = cert.fingerprint(options[:digest]) raise ArgumentError, "Could not get fingerprint for digest '#{options[:digest]}'" end puts fingerprint end def onetime unless options[:client] $stderr.puts "onetime is specified but there is no client" exit(43) return end @daemon.set_signal_traps begin report = @agent.run rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err detail.to_s end if not report exit(1) elsif options[:detailed_exitcodes] then exit(report.exit_status) else exit(0) end end def main Puppet.notice "Starting Puppet client version #{Puppet.version}" @daemon.start end # Enable all of the most common test options. def setup_test Puppet.settings.handlearg("--ignorecache") Puppet.settings.handlearg("--no-usecacheonfailure") Puppet.settings.handlearg("--no-splay") Puppet.settings.handlearg("--show_diff") Puppet.settings.handlearg("--no-daemonize") options[:verbose] = true Puppet[:onetime] = true options[:detailed_exitcodes] = true end # Handle the logging settings. def setup_logs if options[:debug] or options[:verbose] Puppet::Util::Log.newdestination(:console) if options[:debug] Puppet::Util::Log.level = :debug else Puppet::Util::Log.level = :info end end Puppet::Util::Log.newdestination(:syslog) unless options[:setdest] end def enable_disable_client(agent) if options[:enable] agent.enable elsif options[:disable] agent.disable end exit(0) end def setup_listen unless FileTest.exists?(Puppet[:rest_authconfig]) Puppet.err "Will not start without authorization file #{Puppet[:rest_authconfig]}" exit(14) end - handlers = nil - - if options[:serve].empty? - handlers = [:Runner] - else - handlers = options[:serve] - end - require 'puppet/network/server' # No REST handlers yet. - server = Puppet::Network::Server.new(:xmlrpc_handlers => handlers, :port => Puppet[:puppetport]) + server = Puppet::Network::Server.new(:port => Puppet[:puppetport]) @daemon.server = server end def setup_host @host = Puppet::SSL::Host.new waitforcert = options[:waitforcert] || (Puppet[:onetime] ? 0 : 120) cert = @host.wait_for_cert(waitforcert) unless options[:fingerprint] end def setup_agent # We need tomake the client either way, we just don't start it # if --no-client is set. require 'puppet/agent' require 'puppet/configurer' @agent = Puppet::Agent.new(Puppet::Configurer) enable_disable_client(@agent) if options[:enable] or options[:disable] @daemon.agent = agent if options[:client] # It'd be nice to daemonize later, but we have to daemonize before the # waitforcert happens. @daemon.daemonize if Puppet[:daemonize] setup_host @objects = [] # This has to go after the certs are dealt with. if Puppet[:listen] unless Puppet[:onetime] setup_listen else Puppet.notice "Ignoring --listen on onetime run" end end end def setup setup_test if options[:test] setup_logs exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? args[:Server] = Puppet[:server] if options[:fqdn] args[:FQDN] = options[:fqdn] Puppet[:certname] = options[:fqdn] end if options[:centrallogs] logdest = args[:Server] logdest += ":" + args[:Port] if args.include?(:Port) Puppet::Util::Log.newdestination(logdest) end Puppet.settings.use :main, :agent, :ssl # Always ignoreimport for agent. It really shouldn't even try to import, # but this is just a temporary band-aid. Puppet[:ignoreimport] = true # We need to specify a ca location for all of the SSL-related i # indirected classes to work; in fingerprint mode we just need # access to the local files and we don't need a ca. Puppet::SSL::Host.ca_location = options[:fingerprint] ? :none : :remote Puppet::Transaction::Report.indirection.terminus_class = :rest # we want the last report to be persisted locally Puppet::Transaction::Report.indirection.cache_class = :yaml # Override the default; puppetd needs this, usually. # You can still override this on the command-line with, e.g., :compiler. Puppet[:catalog_terminus] = :rest # Override the default. Puppet[:facts_terminus] = :facter Puppet::Resource::Catalog.indirection.cache_class = :yaml unless options[:fingerprint] setup_agent else setup_host end end end diff --git a/lib/puppet/application/master.rb b/lib/puppet/application/master.rb index b4da770f0..553e27945 100644 --- a/lib/puppet/application/master.rb +++ b/lib/puppet/application/master.rb @@ -1,243 +1,239 @@ require 'puppet/application' class Puppet::Application::Master < Puppet::Application should_parse_config run_mode :master option("--debug", "-d") option("--verbose", "-v") # internal option, only to be used by ext/rack/config.ru option("--rack") option("--compile host", "-c host") do |arg| options[:node] = arg end option("--logdest DEST", "-l DEST") do |arg| begin Puppet::Util::Log.newdestination(arg) options[:setdest] = true rescue => detail puts detail.backtrace if Puppet[:debug] $stderr.puts detail.to_s end end option("--parseonly") do puts "--parseonly has been removed. Please use 'puppet parser validate '" exit 1 end def help <<-HELP puppet-master(8) -- The puppet master daemon ======== SYNOPSIS -------- The central puppet server. Functions as a certificate authority by default. USAGE ----- puppet master [-D|--daemonize|--no-daemonize] [-d|--debug] [-h|--help] [-l|--logdest |console|syslog] [-v|--verbose] [-V|--version] [--compile ] DESCRIPTION ----------- This command starts an instance of puppet master, running as a daemon and using Ruby's built-in Webrick webserver. Puppet master can also be managed by other application servers; when this is the case, this executable is not used. OPTIONS ------- Note that any configuration parameter that's valid in the configuration file is also a valid long argument. For example, 'ssldir' is a valid configuration parameter, so you can specify '--ssldir ' as an argument. See the configuration file documentation at http://docs.puppetlabs.com/references/stable/configuration.html for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet master with '--genconfig'. * --daemonize: Send the process into the background. This is the default. * --no-daemonize: Do not send the process into the background. * --debug: Enable full debugging. * --help: Print this help message. * --logdest: Where to send messages. Choose between syslog, the console, and a log file. Defaults to sending messages to syslog, or the console if debugging or verbosity is enabled. * --verbose: Enable verbosity. * --version: Print the puppet version number and exit. * --compile: Compile a catalogue and output it in JSON from the puppet master. Uses facts contained in the $vardir/yaml/ directory to compile the catalog. EXAMPLE ------- puppet master DIAGNOSTICS ----------- When running as a standalone daemon, puppet master accepts the following signals: * SIGHUP: Restart the puppet master server. * SIGINT and SIGTERM: Shut down the puppet master server. AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end def preinit Signal.trap(:INT) do $stderr.puts "Cancelling startup" exit(0) end # Create this first-off, so we have ARGV require 'puppet/daemon' @daemon = Puppet::Daemon.new @daemon.argv = ARGV.dup end def run_command if options[:node] compile else main end end def compile Puppet::Util::Log.newdestination :console raise ArgumentError, "Cannot render compiled catalogs without pson support" unless Puppet.features.pson? begin unless catalog = Puppet::Resource::Catalog.indirection.find(options[:node]) raise "Could not compile catalog for #{options[:node]}" end jj catalog.to_resource rescue => detail $stderr.puts detail exit(30) end exit(0) end def main require 'etc' require 'puppet/file_serving/content' require 'puppet/file_serving/metadata' - xmlrpc_handlers = [:Status, :FileServer, :Master, :Report, :Filebucket] - - xmlrpc_handlers << :CA if Puppet[:ca] - # Make sure we've got a localhost ssl cert Puppet::SSL::Host.localhost # And now configure our server to *only* hit the CA for data, because that's # all it will have write access to. Puppet::SSL::Host.ca_location = :only if Puppet::SSL::CertificateAuthority.ca? if Puppet.features.root? begin Puppet::Util.chuser rescue => detail puts detail.backtrace if Puppet[:trace] $stderr.puts "Could not change user to #{Puppet[:user]}: #{detail}" exit(39) end end unless options[:rack] require 'puppet/network/server' - @daemon.server = Puppet::Network::Server.new(:xmlrpc_handlers => xmlrpc_handlers) + @daemon.server = Puppet::Network::Server.new() @daemon.daemonize if Puppet[:daemonize] else require 'puppet/network/http/rack' - @app = Puppet::Network::HTTP::Rack.new(:xmlrpc_handlers => xmlrpc_handlers, :protocols => [:rest, :xmlrpc]) + @app = Puppet::Network::HTTP::Rack.new() end Puppet.notice "Starting Puppet master version #{Puppet.version}" unless options[:rack] @daemon.start else return @app end end def setup raise Puppet::Error.new("Puppet master is not supported on Microsoft Windows") if Puppet.features.microsoft_windows? # Handle the logging settings. if options[:debug] or options[:verbose] if options[:debug] Puppet::Util::Log.level = :debug else Puppet::Util::Log.level = :info end unless Puppet[:daemonize] or options[:rack] Puppet::Util::Log.newdestination(:console) options[:setdest] = true end end Puppet::Util::Log.newdestination(:syslog) unless options[:setdest] exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? Puppet.settings.use :main, :master, :ssl, :metrics # Cache our nodes in yaml. Currently not configurable. Puppet::Node.indirection.cache_class = :yaml # Configure all of the SSL stuff. if Puppet::SSL::CertificateAuthority.ca? Puppet::SSL::Host.ca_location = :local Puppet.settings.use :ca Puppet::SSL::CertificateAuthority.instance else Puppet::SSL::Host.ca_location = :none end end end diff --git a/lib/puppet/feature/base.rb b/lib/puppet/feature/base.rb index 8813197a8..7e60734b4 100644 --- a/lib/puppet/feature/base.rb +++ b/lib/puppet/feature/base.rb @@ -1,66 +1,66 @@ require 'puppet/util/feature' # Add the simple features, all in one file. # Order is important as some features depend on others # We have a syslog implementation Puppet.features.add(:syslog, :libs => ["syslog"]) # We can use POSIX user functions Puppet.features.add(:posix) do require 'etc' Etc.getpwuid(0) != nil && Puppet.features.syslog? end # We can use Microsoft Windows functions Puppet.features.add(:microsoft_windows) do begin require 'sys/admin' require 'win32/process' require 'win32/dir' require 'win32/service' require 'win32ole' require 'win32/api' require 'win32/taskscheduler' true rescue LoadError => err warn "Cannot run on Microsoft Windows without the sys-admin, win32-process, win32-dir, win32-service and win32-taskscheduler gems: #{err}" unless Puppet.features.posix? end end raise Puppet::Error,"Cannot determine basic system flavour" unless Puppet.features.posix? or Puppet.features.microsoft_windows? # We've got LDAP available. Puppet.features.add(:ldap, :libs => ["ldap"]) # We have the Rdoc::Usage library. Puppet.features.add(:usage, :libs => %w{rdoc/ri/ri_paths rdoc/usage}) # We have libshadow, useful for managing passwords. Puppet.features.add(:libshadow, :libs => ["shadow"]) # We're running as root. Puppet.features.add(:root) { require 'puppet/util/suidmanager'; Puppet::Util::SUIDManager.root? } # We've got mongrel available -Puppet.features.add(:mongrel, :libs => %w{rubygems mongrel puppet/network/http_server/mongrel}) +Puppet.features.add(:mongrel, :libs => %w{rubygems mongrel puppet/network/http/mongrel}) # We have lcs diff Puppet.features.add :diff, :libs => %w{diff/lcs diff/lcs/hunk} # We have augeas Puppet.features.add(:augeas, :libs => ["augeas"]) # We have RRD available Puppet.features.add(:rrd_legacy, :libs => ["RRDtool"]) Puppet.features.add(:rrd, :libs => ["RRD"]) # We have OpenSSL Puppet.features.add(:openssl, :libs => ["openssl"]) # We have CouchDB Puppet.features.add(:couchdb, :libs => ["couchrest"]) # We have sqlite Puppet.features.add(:sqlite, :libs => ["sqlite3"]) diff --git a/lib/puppet/file_serving/metadata.rb b/lib/puppet/file_serving/metadata.rb index 587c2196d..131f9cb8b 100644 --- a/lib/puppet/file_serving/metadata.rb +++ b/lib/puppet/file_serving/metadata.rb @@ -1,156 +1,142 @@ require 'puppet' require 'puppet/indirector' require 'puppet/file_serving' require 'puppet/file_serving/base' require 'puppet/util/checksums' require 'puppet/file_serving/indirection_hooks' # A class that handles retrieving file metadata. class Puppet::FileServing::Metadata < Puppet::FileServing::Base include Puppet::Util::Checksums extend Puppet::Indirector indirects :file_metadata, :extend => Puppet::FileServing::IndirectionHooks attr_reader :path, :owner, :group, :mode, :checksum_type, :checksum, :ftype, :destination PARAM_ORDER = [:mode, :ftype, :owner, :group] - def attributes_with_tabs - raise(ArgumentError, "Cannot manage files of type #{ftype}") unless ['file','directory','link'].include? ftype - desc = [] - PARAM_ORDER.each { |check| - check = :ftype if check == :type - desc << send(check) - } - - desc << checksum - desc << @destination rescue nil if ftype == 'link' - - desc.join("\t") - end - def checksum_type=(type) raise(ArgumentError, "Unsupported checksum type #{type}") unless respond_to?("#{type}_file") @checksum_type = type end class MetaStat extend Forwardable def initialize(stat) @stat = stat end def_delegator :@stat, :uid, :owner def_delegator :@stat, :gid, :group def_delegators :@stat, :mode, :ftype end class WindowsStat < MetaStat if Puppet.features.microsoft_windows? require 'puppet/util/windows/security' end def initialize(stat, path) super(stat) @path = path end { :owner => 'S-1-5-32-544', :group => 'S-1-0-0', :mode => 0644 }.each do |method, default_value| define_method method do Puppet::Util::Windows::Security.send("get_#{method}", @path) || default_value end end end def collect_stat(path) stat = stat() if Puppet.features.microsoft_windows? WindowsStat.new(stat, path) else MetaStat.new(stat) end end # Retrieve the attributes for this file, relative to a base directory. # Note that File.stat raises Errno::ENOENT if the file is absent and this # method does not catch that exception. def collect real_path = full_path stat = collect_stat(real_path) @owner = stat.owner @group = stat.group @ftype = stat.ftype # We have to mask the mode, yay. @mode = stat.mode & 007777 case stat.ftype when "file" @checksum = ("{#{@checksum_type}}") + send("#{@checksum_type}_file", real_path).to_s when "directory" # Always just timestamp the directory. @checksum_type = "ctime" @checksum = ("{#{@checksum_type}}") + send("#{@checksum_type}_file", path).to_s when "link" @destination = File.readlink(real_path) @checksum = ("{#{@checksum_type}}") + send("#{@checksum_type}_file", real_path).to_s rescue nil else raise ArgumentError, "Cannot manage files of type #{stat.ftype}" end end def initialize(path,data={}) @owner = data.delete('owner') @group = data.delete('group') @mode = data.delete('mode') if checksum = data.delete('checksum') @checksum_type = checksum['type'] @checksum = checksum['value'] end @checksum_type ||= "md5" @ftype = data.delete('type') @destination = data.delete('destination') super(path,data) end PSON.register_document_type('FileMetadata',self) def to_pson_data_hash { 'document_type' => 'FileMetadata', 'data' => super['data'].update( { 'owner' => owner, 'group' => group, 'mode' => mode, 'checksum' => { 'type' => checksum_type, 'value' => checksum }, 'type' => ftype, 'destination' => destination, }), 'metadata' => { 'api_version' => 1 } } end def to_pson(*args) to_pson_data_hash.to_pson(*args) end def self.from_pson(data) new(data.delete('path'), data) end end diff --git a/lib/puppet/module.rb b/lib/puppet/module.rb index 00468df96..6f84e077c 100644 --- a/lib/puppet/module.rb +++ b/lib/puppet/module.rb @@ -1,207 +1,202 @@ require 'puppet/util/logging' # Support for modules class Puppet::Module class Error < Puppet::Error; end class MissingModule < Error; end class IncompatibleModule < Error; end class UnsupportedPlatform < Error; end class IncompatiblePlatform < Error; end class MissingMetadata < Error; end class InvalidName < Error; end include Puppet::Util::Logging TEMPLATES = "templates" FILES = "files" MANIFESTS = "manifests" PLUGINS = "plugins" FILETYPES = [MANIFESTS, FILES, TEMPLATES, PLUGINS] # Return an array of paths by splitting the +modulepath+ config # parameter. Only consider paths that are absolute and existing # directories def self.modulepath(environment = nil) Puppet::Node::Environment.new(environment).modulepath end # Find and return the +module+ that +path+ belongs to. If +path+ is # absolute, or if there is no module whose name is the first component # of +path+, return +nil+ def self.find(modname, environment = nil) return nil unless modname Puppet::Node::Environment.new(environment).module(modname) end attr_reader :name, :environment attr_writer :environment attr_accessor :source, :author, :version, :license, :puppetversion, :summary, :description, :project_page def has_metadata? return false unless metadata_file return false unless FileTest.exist?(metadata_file) metadata = PSON.parse File.read(metadata_file) return metadata.is_a?(Hash) && !metadata.keys.empty? end def initialize(name, environment = nil) @name = name assert_validity if environment.is_a?(Puppet::Node::Environment) @environment = environment else @environment = Puppet::Node::Environment.new(environment) end load_metadata if has_metadata? validate_puppet_version validate_dependencies end FILETYPES.each do |type| # A boolean method to let external callers determine if # we have files of a given type. define_method(type +'?') do return false unless path return false unless FileTest.exist?(subpath(type)) return true end # A method for returning a given file of a given type. # e.g., file = mod.manifest("my/manifest.pp") # # If the file name is nil, then the base directory for the # file type is passed; this is used for fileserving. define_method(type.to_s.sub(/s$/, '')) do |file| return nil unless path # If 'file' is nil then they're asking for the base path. # This is used for things like fileserving. if file full_path = File.join(subpath(type), file) else full_path = subpath(type) end return nil unless FileTest.exist?(full_path) return full_path end end def exist? ! path.nil? end - # Find the first 'files' directory. This is used by the XMLRPC fileserver. - def file_directory - subpath("files") - end - def license_file return @license_file if defined?(@license_file) return @license_file = nil unless path @license_file = File.join(path, "License") end def load_metadata data = PSON.parse File.read(metadata_file) [:source, :author, :version, :license, :puppetversion].each do |attr| unless value = data[attr.to_s] unless attr == :puppetversion raise MissingMetadata, "No #{attr} module metadata provided for #{self.name}" end end send(attr.to_s + "=", value) end end # Return the list of manifests matching the given glob pattern, # defaulting to 'init.{pp,rb}' for empty modules. def match_manifests(rest) pat = File.join(path, MANIFESTS, rest || 'init') [manifest("init.pp"),manifest("init.rb")].compact + Dir. glob(pat + (File.extname(pat).empty? ? '.{pp,rb}' : '')). reject { |f| FileTest.directory?(f) } end def metadata_file return @metadata_file if defined?(@metadata_file) return @metadata_file = nil unless path @metadata_file = File.join(path, "metadata.json") end # Find this module in the modulepath. def path environment.modulepath.collect { |path| File.join(path, name) }.find { |d| FileTest.directory?(d) } end # Find all plugin directories. This is used by the Plugins fileserving mount. def plugin_directory subpath("plugins") end def requires(name, version = nil) @requires ||= [] @requires << [name, version] end def supports(name, version = nil) @supports ||= [] @supports << [name, version] end def to_s result = "Module #{name}" result += "(#{path})" if path result end def validate_dependencies return unless defined?(@requires) @requires.each do |name, version| unless mod = environment.module(name) raise MissingModule, "Missing module #{name} required by #{self.name}" end if version and mod.version != version raise IncompatibleModule, "Required module #{name} is version #{mod.version} but #{self.name} requires #{version}" end end end def validate_puppet_version return unless puppetversion and puppetversion != Puppet.version raise IncompatibleModule, "Module #{self.name} is only compatible with Puppet version #{puppetversion}, not #{Puppet.version}" end private def subpath(type) return File.join(path, type) unless type.to_s == "plugins" backward_compatible_plugins_dir end def backward_compatible_plugins_dir if dir = File.join(path, "plugins") and FileTest.exist?(dir) Puppet.warning "using the deprecated 'plugins' directory for ruby extensions; please move to 'lib'" return dir else return File.join(path, "lib") end end def assert_validity raise InvalidName, "Invalid module name; module names must be alphanumeric (plus '-'), not '#{name}'" unless name =~ /^[-\w]+$/ end end diff --git a/lib/puppet/network/handler.rb b/lib/puppet/network/handler.rb deleted file mode 100644 index 3cad3872f..000000000 --- a/lib/puppet/network/handler.rb +++ /dev/null @@ -1,50 +0,0 @@ -require 'puppet/util/docs' -require 'puppet/util/subclass_loader' - -module Puppet::Network - # The base class for the different handlers. The handlers are each responsible - # for separate xmlrpc namespaces. - class Handler - extend Puppet::Util::Docs - - # This is so that the handlers can subclass just 'Handler', rather - # then having to specify the full class path. - Handler = self - attr_accessor :server, :local - - extend Puppet::Util::SubclassLoader - extend Puppet::Util - - handle_subclasses :handler, "puppet/network/handler" - - # Return the xmlrpc interface. - def self.interface - if defined?(@interface) - return @interface - else - raise Puppet::DevError, "Handler #{self} has no defined interface" - end - end - - # Set/Determine whether we're a client- or server-side handler. - def self.side(side = nil) - if side - side = side.intern if side.is_a?(String) - raise ArgumentError, "Invalid side registration '#{side}' for #{self.name}" unless [:client, :server].include?(side) - @side = side - else - @side ||= :server - return @side - end - end - - # Create an empty init method with the same signature. - def initialize(hash = {}) - end - - def local? - self.local - end - end -end - diff --git a/lib/puppet/network/handler/ca.rb b/lib/puppet/network/handler/ca.rb deleted file mode 100644 index a61f62faf..000000000 --- a/lib/puppet/network/handler/ca.rb +++ /dev/null @@ -1,61 +0,0 @@ -require 'openssl' -require 'puppet' -require 'xmlrpc/server' -require 'puppet/network/handler' - -class Puppet::Network::Handler - class CA < Handler - attr_reader :ca - - desc "Provides an interface for signing CSRs. Accepts a CSR and returns - the CA certificate and the signed certificate, or returns nil if - the cert is not signed." - - @interface = XMLRPC::Service::Interface.new("puppetca") { |iface| - iface.add_method("array getcert(csr)") - } - - def initialize(hash = {}) - Puppet.settings.use(:main, :ssl, :ca) - - @ca = Puppet::SSL::CertificateAuthority.instance - end - - # our client sends us a csr, and we either store it for later signing, - # or we sign it right away - def getcert(csrtext, client = nil, clientip = nil) - csr = Puppet::SSL::CertificateRequest.from_s(csrtext) - hostname = csr.name - - unless @ca - Puppet.notice "Host #{hostname} asked for signing from non-CA master" - return "" - end - - # We used to save the public key, but it's basically unnecessary - # and it mucks with the permissions requirements. - - # first check to see if we already have a signed cert for the host - cert = Puppet::SSL::Certificate.indirection.find(hostname) - cacert = Puppet::SSL::Certificate.indirection.find(@ca.host.name) - - if cert - Puppet.info "Retrieving existing certificate for #{hostname}" - unless csr.content.public_key.to_s == cert.content.public_key.to_s - raise Puppet::Error, "Certificate request does not match existing certificate; run 'puppetca --clean #{hostname}'." - end - [cert.to_s, cacert.to_s] - else - Puppet::SSL::CertificateRequest.indirection.save(csr) - - # We determine whether we signed the csr by checking if there's a certificate for it - if cert = Puppet::SSL::Certificate.indirection.find(hostname) - [cert.to_s, cacert.to_s] - else - nil - end - end - end - end -end - diff --git a/lib/puppet/network/handler/filebucket.rb b/lib/puppet/network/handler/filebucket.rb deleted file mode 100755 index 0ca467f7a..000000000 --- a/lib/puppet/network/handler/filebucket.rb +++ /dev/null @@ -1,53 +0,0 @@ -require 'fileutils' -require 'digest/md5' -require 'puppet/external/base64' -require 'puppet/network/handler' -require 'xmlrpc/server' - -class Puppet::Network::Handler # :nodoc: - # Accept files and store them by md5 sum, returning the md5 sum back - # to the client. Alternatively, accept an md5 sum and return the - # associated content. - class FileBucket < Handler - desc "The interface to Puppet's FileBucket system. Can be used to store - files in and retrieve files from a filebucket." - - @interface = XMLRPC::Service::Interface.new("puppetbucket") { |iface| - iface.add_method("string addfile(string, string)") - iface.add_method("string getfile(string)") - } - - Puppet::Util.logmethods(self, true) - attr_reader :name, :path - - def initialize(hash) - @path = hash[:Path] || Puppet[:bucketdir] - @name = "Filebucket[#{@path}]" - end - - # Accept a file from a client and store it by md5 sum, returning - # the sum. - def addfile(contents, path, client = nil, clientip = nil) - contents = Base64.decode64(contents) if client - bucket = Puppet::FileBucket::File.new(contents) - Puppet::FileBucket::File.indirection.save(bucket) - end - - # Return the contents associated with a given md5 sum. - def getfile(md5, client = nil, clientip = nil) - bucket = Puppet::FileBucket::File.indirection.find("md5:#{md5}") - contents = bucket.contents - - if client - return Base64.encode64(contents) - else - return contents - end - end - - def to_s - self.name - end - end -end - diff --git a/lib/puppet/network/handler/fileserver.rb b/lib/puppet/network/handler/fileserver.rb deleted file mode 100755 index 8fe3da29a..000000000 --- a/lib/puppet/network/handler/fileserver.rb +++ /dev/null @@ -1,732 +0,0 @@ -require 'puppet' -require 'puppet/network/authstore' -require 'webrick/httpstatus' -require 'cgi' -require 'delegate' -require 'sync' -require 'puppet/network/handler' - -require 'puppet/network/handler' -require 'puppet/network/xmlrpc/server' -require 'puppet/file_serving' -require 'puppet/file_serving/metadata' -require 'puppet/network/handler' - -class Puppet::Network::Handler - AuthStoreError = Puppet::AuthStoreError - class FileServerError < Puppet::Error; end - class FileServer < Handler - desc "The interface to Puppet's fileserving abilities." - - attr_accessor :local - - CHECKPARAMS = [:mode, :type, :owner, :group, :checksum] - - # Special filserver module for puppet's module system - MODULES = "modules" - PLUGINS = "plugins" - - @interface = XMLRPC::Service::Interface.new("fileserver") { |iface| - iface.add_method("string describe(string, string)") - iface.add_method("string list(string, string, boolean, array)") - iface.add_method("string retrieve(string, string)") - } - - def self.params - CHECKPARAMS.dup - end - - # If the configuration file exists, then create (if necessary) a LoadedFile - # object to manage it; else, return nil. - def configuration - # Short-circuit the default case. - return @configuration if defined?(@configuration) - - config_path = @passed_configuration_path || Puppet[:fileserverconfig] - return nil unless FileTest.exist?(config_path) - - # The file exists but we don't have a LoadedFile instance for it. - @configuration = Puppet::Util::LoadedFile.new(config_path) - end - - # Create our default mounts for modules and plugins. This is duplicated code, - # but I'm not really worried about that. - def create_default_mounts - @mounts = {} - Puppet.debug "No file server configuration file; autocreating #{MODULES} mount with default permissions" - mount = Mount.new(MODULES) - mount.allow("*") - @mounts[MODULES] = mount - - Puppet.debug "No file server configuration file; autocreating #{PLUGINS} mount with default permissions" - mount = PluginMount.new(PLUGINS) - mount.allow("*") - @mounts[PLUGINS] = mount - end - - # Describe a given file. This returns all of the manageable aspects - # of that file. - def describe(url, links = :follow, client = nil, clientip = nil) - links = links.intern if links.is_a? String - - mount, path = convert(url, client, clientip) - - mount.debug("Describing #{url} for #{client}") if client - - # use the mount to resolve the path for us. - return "" unless full_path = mount.file_path(path, client) - - metadata = Puppet::FileServing::Metadata.new(url, :path => full_path, :links => links) - - return "" unless metadata.exist? - - begin - metadata.collect - rescue => detail - puts detail.backtrace if Puppet[:trace] - Puppet.err detail - return "" - end - - metadata.attributes_with_tabs - end - - # Create a new fileserving module. - def initialize(hash = {}) - @mounts = {} - @files = {} - - @local = hash[:Local] - - @noreadconfig = true if hash[:Config] == false - - @passed_configuration_path = hash[:Config] - - if hash.include?(:Mount) - @passedconfig = true - raise Puppet::DevError, "Invalid mount hash #{hash[:Mount].inspect}" unless hash[:Mount].is_a?(Hash) - - hash[:Mount].each { |dir, name| - self.mount(dir, name) if FileTest.exists?(dir) - } - self.mount(nil, MODULES) - self.mount(nil, PLUGINS) - else - @passedconfig = false - if configuration - readconfig(false) # don't check the file the first time. - else - create_default_mounts - end - end - end - - # List a specific directory's contents. - def list(url, links = :ignore, recurse = false, ignore = false, client = nil, clientip = nil) - mount, path = convert(url, client, clientip) - - mount.debug "Listing #{url} for #{client}" if client - - return "" unless mount.path_exists?(path, client) - - desc = mount.list(path, recurse, ignore, client) - - if desc.length == 0 - mount.notice "Got no information on //#{mount}/#{path}" - return "" - end - - desc.collect { |sub| sub.join("\t") }.join("\n") - end - - def local? - self.local - end - - # Is a given mount available? - def mounted?(name) - @mounts.include?(name) - end - - # Mount a new directory with a name. - def mount(path, name) - if @mounts.include?(name) - if @mounts[name] != path - raise FileServerError, "#{@mounts[name].path} is already mounted at #{name}" - else - # it's already mounted; no problem - return - end - end - - # Let the mounts do their own error-checking. - @mounts[name] = Mount.new(name, path) - @mounts[name].info "Mounted #{path}" - - @mounts[name] - end - - # Retrieve a file from the local disk and pass it to the remote - # client. - def retrieve(url, links = :ignore, client = nil, clientip = nil) - links = links.intern if links.is_a? String - - mount, path = convert(url, client, clientip) - - mount.info "Sending #{url} to #{client}" if client - - unless mount.path_exists?(path, client) - mount.debug "#{mount} reported that #{path} does not exist" - return "" - end - - links = links.intern if links.is_a? String - - if links == :ignore and FileTest.symlink?(path) - mount.debug "I think that #{path} is a symlink and we're ignoring them" - return "" - end - - str = mount.read_file(path, client) - - if @local - return str - else - return CGI.escape(str) - end - end - - def umount(name) - @mounts.delete(name) if @mounts.include? name - end - - private - - def authcheck(file, mount, client, clientip) - # If we're local, don't bother passing in information. - if local? - client = nil - clientip = nil - end - unless mount.allowed?(client, clientip) - mount.warning "#{client} cannot access #{file}" - raise Puppet::AuthorizationError, "Cannot access #{mount}" - end - end - - # Take a URL and some client info and return a mount and relative - # path pair. - # - def convert(url, client, clientip) - readconfig - - url = URI.unescape(url) - - mount, stub = splitpath(url, client) - - authcheck(url, mount, client, clientip) - - return mount, stub - end - - # Return the mount for the Puppet modules; allows file copying from - # the modules. - def modules_mount(module_name, client) - # Find our environment, if we have one. - unless hostname = (client || Facter.value("hostname")) - raise ArgumentError, "Could not find hostname" - end - env = (node = Puppet::Node.indirection.find(hostname)) ? node.environment : nil - - # And use the environment to look up the module. - (mod = Puppet::Node::Environment.new(env).module(module_name) and mod.files?) ? @mounts[MODULES].copy(mod.name, mod.file_directory) : nil - end - - # Read the configuration file. - def readconfig(check = true) - return if @noreadconfig - - return unless configuration - - return if check and ! @configuration.changed? - - newmounts = {} - begin - File.open(@configuration.file) { |f| - mount = nil - count = 1 - f.each { |line| - case line - when /^\s*#/; next # skip comments - when /^\s*$/; next # skip blank lines - when /\[([-\w]+)\]/ - name = $1 - raise FileServerError, "#{newmounts[name]} is already mounted as #{name} in #{@configuration.file}" if newmounts.include?(name) - mount = Mount.new(name) - newmounts[name] = mount - when /^\s*(\w+)\s+(.+)$/ - var = $1 - value = $2 - case var - when "path" - raise FileServerError.new("No mount specified for argument #{var} #{value}") unless mount - if mount.name == MODULES - Puppet.warning "The '#{mount.name}' module can not have a path. Ignoring attempt to set it" - else - begin - mount.path = value - rescue FileServerError => detail - Puppet.err "Removing mount #{mount.name}: #{detail}" - newmounts.delete(mount.name) - end - end - when "allow" - raise FileServerError.new("No mount specified for argument #{var} #{value}") unless mount - value.split(/\s*,\s*/).each { |val| - begin - mount.info "allowing #{val} access" - mount.allow(val) - rescue AuthStoreError => detail - puts detail.backtrace if Puppet[:trace] - - raise FileServerError.new( - detail.to_s, - - count, @configuration.file) - end - } - when "deny" - raise FileServerError.new("No mount specified for argument #{var} #{value}") unless mount - value.split(/\s*,\s*/).each { |val| - begin - mount.info "denying #{val} access" - mount.deny(val) - rescue AuthStoreError => detail - - raise FileServerError.new( - detail.to_s, - - count, @configuration.file) - end - } - else - raise FileServerError.new("Invalid argument '#{var}'", count, @configuration.file) - end - else - raise FileServerError.new("Invalid line '#{line.chomp}'", count, @configuration.file) - end - count += 1 - } - } - rescue Errno::EACCES => detail - Puppet.err "FileServer error: Cannot read #{@configuration}; cannot serve" - #raise Puppet::Error, "Cannot read #{@configuration}" - rescue Errno::ENOENT => detail - Puppet.err "FileServer error: '#{@configuration}' does not exist; cannot serve" - end - - unless newmounts[MODULES] - Puppet.debug "No #{MODULES} mount given; autocreating with default permissions" - mount = Mount.new(MODULES) - mount.allow("*") - newmounts[MODULES] = mount - end - - unless newmounts[PLUGINS] - Puppet.debug "No #{PLUGINS} mount given; autocreating with default permissions" - mount = PluginMount.new(PLUGINS) - mount.allow("*") - newmounts[PLUGINS] = mount - end - - unless newmounts[PLUGINS].valid? - Puppet.debug "No path given for #{PLUGINS} mount; creating a special PluginMount" - # We end up here if the user has specified access rules for - # the plugins mount, without specifying a path (which means - # they want to have the default behaviour for the mount, but - # special access control). So we need to move all the - # user-specified access controls into the new PluginMount - # object... - mount = PluginMount.new(PLUGINS) - # Yes, you're allowed to hate me for this. - - mount.instance_variable_set( - :@declarations, - - newmounts[PLUGINS].instance_variable_get(:@declarations) - ) - newmounts[PLUGINS] = mount - end - - # Verify each of the mounts are valid. - # We let the check raise an error, so that it can raise an error - # pointing to the specific problem. - newmounts.each { |name, mount| - raise FileServerError, "Invalid mount #{name}" unless mount.valid? - } - @mounts = newmounts - end - - # Split the path into the separate mount point and path. - def splitpath(dir, client) - # the dir is based on one of the mounts - # so first retrieve the mount path - mount = nil - path = nil - if dir =~ %r{/([-\w]+)} - # Strip off the mount name. - mount_name, path = dir.sub(%r{^/}, '').split(File::Separator, 2) - - unless mount = modules_mount(mount_name, client) - unless mount = @mounts[mount_name] - raise FileServerError, "Fileserver module '#{mount_name}' not mounted" - end - end - else - raise FileServerError, "Fileserver error: Invalid path '#{dir}'" - end - - if path.nil? or path == '' - path = '/' - elsif path - # Remove any double slashes that might have occurred - path = URI.unescape(path.gsub(/\/\//, "/")) - end - - return mount, path - end - - def to_s - "fileserver" - end - - # A simple class for wrapping mount points. Instances of this class - # don't know about the enclosing object; they're mainly just used for - # authorization. - class Mount < Puppet::Network::AuthStore - attr_reader :name - - @@syncs = {} - - @@files = {} - - Puppet::Util.logmethods(self, true) - - # Create a map for a specific client. - def clientmap(client) - { - "h" => client.sub(/\..*$/, ""), - "H" => client, - "d" => client.sub(/[^.]+\./, "") # domain name - } - end - - # Replace % patterns as appropriate. - def expand(path, client = nil) - # This map should probably be moved into a method. - map = nil - - if client - map = clientmap(client) - else - Puppet.notice "No client; expanding '#{path}' with local host" - # Else, use the local information - map = localmap - end - path.gsub(/%(.)/) do |v| - key = $1 - if key == "%" - "%" - else - map[key] || v - end - end - end - - # Do we have any patterns in our path, yo? - def expandable? - if defined?(@expandable) - @expandable - else - false - end - end - - # Return a fully qualified path, given a short path and - # possibly a client name. - def file_path(relative_path, node = nil) - full_path = path(node) - - unless full_path - p self - raise ArgumentError.new("Mounts without paths are not usable") unless full_path - end - - # If there's no relative path name, then we're serving the mount itself. - return full_path unless relative_path and relative_path != "/" - - File.join(full_path, relative_path) - end - - # Create out object. It must have a name. - def initialize(name, path = nil) - unless name =~ %r{^[-\w]+$} - raise FileServerError, "Invalid name format '#{name}'" - end - @name = name - - if path - self.path = path - else - @path = nil - end - - @files = {} - - super() - end - - def fileobj(path, links, client) - obj = nil - 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 - # the effort. - obj[:audit] = CHECKPARAMS - else - - obj = Puppet::Type.type(:file).new( - - :name => file_path(path, client), - - :audit => CHECKPARAMS - ) - @files[file_path(path, client)] = obj - end - - if links == :manage - links = :follow - end - - # This, ah, might be completely redundant - obj[:links] = links unless obj[:links] == links - - obj - end - - # Read the contents of the file at the relative path given. - def read_file(relpath, client) - File.read(file_path(relpath, client)) - end - - # Cache this manufactured map, since if it's used it's likely - # to get used a lot. - def localmap - unless defined?(@@localmap) - @@localmap = { - "h" => Facter.value("hostname"), - "H" => [Facter.value("hostname"), - Facter.value("domain")].join("."), - "d" => Facter.value("domain") - } - end - @@localmap - end - - # Return the path as appropriate, expanding as necessary. - def path(client = nil) - if expandable? - return expand(@path, client) - else - return @path - end - end - - # Set the path. - def path=(path) - # FIXME: For now, just don't validate paths with replacement - # patterns in them. - if path =~ /%./ - # Mark that we're expandable. - @expandable = true - else - raise FileServerError, "#{path} does not exist" unless FileTest.exists?(path) - raise FileServerError, "#{path} is not a directory" unless FileTest.directory?(path) - raise FileServerError, "#{path} is not readable" unless FileTest.readable?(path) - @expandable = false - end - @path = path - end - - # Verify that the path given exists within this mount's subtree. - # - def path_exists?(relpath, client = nil) - File.exists?(file_path(relpath, client)) - end - - # Return the current values for the object. - def properties(obj) - obj.retrieve.inject({}) { |props, ary| props[ary[0].name] = ary[1]; props } - end - - # Retrieve a specific directory relative to a mount point. - # If they pass in a client, then expand as necessary. - def subdir(dir = nil, client = nil) - basedir = self.path(client) - - dirname = if dir - File.join(basedir, *dir.split("/")) - else - basedir - end - - dirname - end - - def sync(path) - @@syncs[path] ||= Sync.new - @@syncs[path] - end - - def to_s - "mount[#{@name}]" - end - - # Verify our configuration is valid. This should really check to - # make sure at least someone will be allowed, but, eh. - def valid? - if name == MODULES - return @path.nil? - else - return ! @path.nil? - end - end - - # Return a new mount with the same properties as +self+, except - # with a different name and path. - def copy(name, path) - result = self.clone - result.path = path - result.instance_variable_set(:@name, name) - result - end - - # List the contents of the relative path +relpath+ of this mount. - # - # +recurse+ is the number of levels to recurse into the tree, - # or false to provide no recursion or true if you just want to - # go for broke. - # - # +ignore+ is an array of filenames to ignore when traversing - # the list. - # - # The return value of this method is a complex nest of arrays, - # which describes a directory tree. Each file or directory is - # represented by an array, where the first element is the path - # of the file (relative to the root of the mount), and the - # second element is the type. A directory is represented by an - # array as well, where the first element is a "directory" array, - # while the remaining elements are other file or directory - # arrays. Confusing? Hell yes. As an added bonus, all names - # must start with a slash, because... well, I'm fairly certain - # a complete explanation would involve the words "crack pipe" - # and "bad batch". - # - def list(relpath, recurse, ignore, client = nil) - abspath = file_path(relpath, client) - if FileTest.exists?(abspath) - if FileTest.directory?(abspath) and recurse - return reclist(abspath, recurse, ignore) - else - return [["/", File.stat(abspath).ftype]] - end - end - nil - end - - def reclist(abspath, recurse, ignore) - require 'puppet/file_serving' - require 'puppet/file_serving/fileset' - if recurse.is_a?(Fixnum) - args = { :recurse => true, :recurselimit => recurse, :links => :follow } - else - args = { :recurse => recurse, :links => :follow } - end - args[:ignore] = ignore if ignore - fs = Puppet::FileServing::Fileset.new(abspath, args) - ary = fs.files.collect do |file| - if file == "." - file = "/" - else - file = File.join("/", file ) - end - stat = fs.stat(File.join(abspath, file)) - next if stat.nil? - [ file, stat.ftype ] - end - - ary.compact - end - - end - - # A special mount class specifically for the plugins mount -- just - # has some magic to effectively do a union mount of the 'plugins' - # directory of all modules. - # - class PluginMount < Mount - def path(client) - '' - end - - def mod_path_exists?(mod, relpath, client = nil) - ! mod.plugin(relpath).nil? - end - - def path_exists?(relpath, client = nil) - !valid_modules(client).find { |mod| mod.plugin(relpath) }.nil? - end - - def valid? - true - end - - def mod_file_path(mod, relpath, client = nil) - File.join(mod, PLUGINS, relpath) - end - - def file_path(relpath, client = nil) - return nil unless mod = valid_modules(client).find { |m| m.plugin(relpath) } - mod.plugin(relpath) - end - - # create a list of files by merging all modules - def list(relpath, recurse, ignore, client = nil) - result = [] - valid_modules(client).each do |mod| - if modpath = mod.plugin(relpath) - if FileTest.directory?(modpath) and recurse - ary = reclist(modpath, recurse, ignore) - ary ||= [] - result += ary - else - result += [["/", File.stat(modpath).ftype]] - end - end - end - result - end - - private - def valid_modules(client) - Puppet::Node::Environment.new.modules.find_all { |mod| mod.exist? } - end - - def add_to_filetree(f, filetree) - first, rest = f.split(File::SEPARATOR, 2) - end - end - end -end - diff --git a/lib/puppet/network/handler/report.rb b/lib/puppet/network/handler/report.rb deleted file mode 100755 index 5e3ee266d..000000000 --- a/lib/puppet/network/handler/report.rb +++ /dev/null @@ -1,82 +0,0 @@ -require 'puppet/util/instance_loader' -require 'puppet/reports' -require 'puppet/network/handler' -require 'xmlrpc/server' - -# A simple server for triggering a new run on a Puppet client. -class Puppet::Network::Handler - class Report < Handler - desc "Accepts a Puppet transaction report and processes it." - - @interface = XMLRPC::Service::Interface.new("puppetreports") { |iface| - iface.add_method("string report(array)") - } - - # Add a new report type. - def self.newreport(name, options = {}, &block) - Puppet.warning "The interface for registering report types has changed; use Puppet::Reports.register_report for report type #{name}" - Puppet::Reports.register_report(name, options, &block) - end - - def initialize(*args) - super - Puppet.settings.use(:main, :reporting, :metrics) - end - - # Accept a report from a client. - def report(report, client = nil, clientip = nil) - # Unescape the report - report = CGI.unescape(report) unless @local - - Puppet.info "Processing reports #{reports().join(", ")} for #{client}" - begin - process(report) - rescue => detail - Puppet.err "Could not process report for #{client}: #{detail}" - puts detail.backtrace if Puppet[:trace] - end - end - - private - - # Process the report using all of the existing hooks. - def process(yaml) - return if Puppet[:reports] == "none" - - # First convert the report to real objects - begin - report = YAML.load(yaml) - rescue => detail - Puppet.warning "Could not load report: #{detail}" - return - end - - # Used for those reports that accept yaml - client = report.host - - reports.each do |name| - if mod = Puppet::Reports.report(name) - # We have to use a dup because we're including a module in the - # report. - newrep = report.dup - begin - newrep.extend(mod) - newrep.process - rescue => detail - puts detail.backtrace if Puppet[:trace] - Puppet.err "Report #{name} failed: #{detail}" - end - else - Puppet.warning "No report named '#{name}'" - end - end - end - - # Handle the parsing of the reports attribute. - def reports - # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] - x = Puppet[:reports].gsub(/(^\s+)|(\s+$)/, '').split(/\s*,\s*/) - end - end -end - diff --git a/lib/puppet/network/handler/runner.rb b/lib/puppet/network/handler/runner.rb deleted file mode 100755 index 1bc62bcd9..000000000 --- a/lib/puppet/network/handler/runner.rb +++ /dev/null @@ -1,33 +0,0 @@ -require 'puppet/run' -require 'puppet/network/handler' -require 'xmlrpc/server' - -class Puppet::Network::Handler - class MissingMasterError < RuntimeError; end # Cannot find the master client - # A simple server for triggering a new run on a Puppet client. - class Runner < Handler - desc "An interface for triggering client configuration runs." - - @interface = XMLRPC::Service::Interface.new("puppetrunner") { |iface| - iface.add_method("string run(string, string)") - } - - side :client - - # Run the client configuration right now, optionally specifying - # tags and whether to ignore schedules - def run(tags = nil, ignoreschedules = false, fg = true, client = nil, clientip = nil) - options = {} - options[:tags] = tags if tags - options[:ignoreschedules] = ignoreschedules if ignoreschedules - options[:background] = !fg - - runner = Puppet::Run.new(options) - - runner.run - - runner.status - end - end -end - diff --git a/lib/puppet/network/handler/status.rb b/lib/puppet/network/handler/status.rb deleted file mode 100644 index df6215f9c..000000000 --- a/lib/puppet/network/handler/status.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'puppet/network/handler' -require 'xmlrpc/server' -class Puppet::Network::Handler - class Status < Handler - desc "A simple interface for testing Puppet connectivity." - - side :client - - @interface = XMLRPC::Service::Interface.new("status") { |iface| - iface.add_method("int status()") - } - - def status(client = nil, clientip = nil) - 1 - end - end -end - diff --git a/lib/puppet/network/http/mongrel.rb b/lib/puppet/network/http/mongrel.rb index 2a638b229..ee103b550 100644 --- a/lib/puppet/network/http/mongrel.rb +++ b/lib/puppet/network/http/mongrel.rb @@ -1,55 +1,32 @@ require 'mongrel' if Puppet.features.mongrel? require 'puppet/network/http/mongrel/rest' class Puppet::Network::HTTP::Mongrel def initialize(args = {}) @listening = false end def listen(args = {}) - raise ArgumentError, ":protocols must be specified." if !args[:protocols] or args[:protocols].empty? raise ArgumentError, ":address must be specified." unless args[:address] raise ArgumentError, ":port must be specified." unless args[:port] raise "Mongrel server is already listening" if listening? - @protocols = args[:protocols] - @xmlrpc_handlers = args[:xmlrpc_handlers] @server = Mongrel::HttpServer.new(args[:address], args[:port]) - setup_handlers + @server.register('/', Puppet::Network::HTTP::MongrelREST.new(:server => @server)) @listening = true @server.run end def unlisten raise "Mongrel server is not listening" unless listening? @server.stop @server = nil @listening = false end def listening? @listening end - - private - - def setup_handlers - # Register our REST support at / - klass = class_for_protocol(:rest) - @server.register('/', klass.new(:server => @server)) - - setup_xmlrpc_handlers if @protocols.include?(:xmlrpc) and ! @xmlrpc_handlers.empty? - 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) - return Puppet::Network::HTTP::MongrelREST if protocol.to_sym == :rest - raise ArgumentError, "Unknown protocol [#{protocol}]." - end end diff --git a/lib/puppet/network/http/rack.rb b/lib/puppet/network/http/rack.rb index 5b4ef7e1c..63b611982 100644 --- a/lib/puppet/network/http/rack.rb +++ b/lib/puppet/network/http/rack.rb @@ -1,65 +1,35 @@ require 'rack' require 'rack/request' require 'rack/response' require 'puppet/network/http' require 'puppet/network/http/rack/rest' -require 'puppet/network/http/rack/xmlrpc' # An rack application, for running the Puppet HTTP Server. class Puppet::Network::HTTP::Rack - - def initialize(args) - raise ArgumentError, ":protocols must be specified." if !args[:protocols] or args[:protocols].empty? - protocols = args[:protocols] - - # Always prepare a REST handler - @rest_http_handler = Puppet::Network::HTTP::RackREST.new - protocols.delete :rest - - # Prepare the XMLRPC handler, for backward compatibility (if requested) - @xmlrpc_path = '/RPC2' - if args[:protocols].include?(:xmlrpc) - raise ArgumentError, "XMLRPC was requested, but no handlers were given" if !args.include?(:xmlrpc_handlers) - - @xmlrpc_http_handler = Puppet::Network::HTTP::RackXMLRPC.new(args[:xmlrpc_handlers]) - protocols.delete :xmlrpc - end - - raise ArgumentError, "there were unknown :protocols specified." if !protocols.empty? - end - # The real rack application (which needs to respond to call). # The work we need to do, roughly is: # * Read request (from env) and prepare a response # * Route the request to the correct handler # * Return the response (in rack-format) to our caller. def call(env) request = Rack::Request.new(env) response = Rack::Response.new Puppet.debug 'Handling request: %s %s' % [request.request_method, request.fullpath] - # if we shall serve XMLRPC, have /RPC2 go to the xmlrpc handler - if @xmlrpc_http_handler and @xmlrpc_path == request.path_info[0, @xmlrpc_path.size] - handler = @xmlrpc_http_handler - else - # everything else is handled by the new REST handler - handler = @rest_http_handler - end - begin - handler.process(request, response) + Puppet::Network::HTTP::RackREST.new.process(request, response) rescue => detail # Send a Status 500 Error on unhandled exceptions. response.status = 500 response['Content-Type'] = 'text/plain' response.write 'Internal Server Error: "%s"' % detail.message # log what happened Puppet.err "Puppet Server (Rack): Internal Server Error: Unhandled Exception: \"%s\"" % detail.message Puppet.err "Backtrace:" detail.backtrace.each { |line| Puppet.err " > #{line}" } end response.finish end end diff --git a/lib/puppet/network/http/rack/httphandler.rb b/lib/puppet/network/http/rack/httphandler.rb index c54062357..e4fa93aa5 100644 --- a/lib/puppet/network/http/rack/httphandler.rb +++ b/lib/puppet/network/http/rack/httphandler.rb @@ -1,16 +1,13 @@ require 'openssl' require 'puppet/ssl/certificate' class Puppet::Network::HTTP::RackHttpHandler - def initialize - end - # do something useful with request (a Rack::Request) and use # response to fill your Rack::Response def process(request, response) raise NotImplementedError, "Your RackHttpHandler subclass is supposed to override service(request)" end end diff --git a/lib/puppet/network/http/rack/xmlrpc.rb b/lib/puppet/network/http/rack/xmlrpc.rb deleted file mode 100644 index f75342783..000000000 --- a/lib/puppet/network/http/rack/xmlrpc.rb +++ /dev/null @@ -1,65 +0,0 @@ -require 'puppet/network/http/rack/httphandler' -require 'puppet/network/xmlrpc/server' -require 'resolv' - -class Puppet::Network::HTTP::RackXMLRPC < Puppet::Network::HTTP::RackHttpHandler - def initialize(handlers) - @xmlrpc_server = Puppet::Network::XMLRPCServer.new - handlers.each do |name| - Puppet.debug " -> register xmlrpc namespace #{name}" - unless handler = Puppet::Network::Handler.handler(name) - raise ArgumentError, "Invalid XMLRPC handler #{name}" - end - @xmlrpc_server.add_handler(handler.interface, handler.new({})) - end - super() - end - - def process(request, response) - # errors are sent as text/plain - response['Content-Type'] = 'text/plain' - if not request.post? - response.status = 405 - response.write 'Method Not Allowed' - return - end - if request.media_type != "text/xml" - response.status = 400 - response.write 'Bad Request' - return - end - - # get auth/certificate data - client_request = build_client_request(request) - - response_body = @xmlrpc_server.process(request.body.read, client_request) - - response.status = 200 - response['Content-Type'] = 'text/xml; charset=utf-8' - response.write response_body - end - - def build_client_request(request) - ip = request.ip - - # if we find SSL info in the headers, use them to get a hostname. - # try this with :ssl_client_header, which defaults should work for - # Apache with StdEnvVars. - if dn = request.env[Puppet[:ssl_client_header]] and dn_matchdata = dn.match(/^.*?CN\s*=\s*(.*)/) - node = dn_matchdata[1].to_str - authenticated = (request.env[Puppet[:ssl_client_verify_header]] == 'SUCCESS') - else - begin - node = Resolv.getname(ip) - rescue => detail - Puppet.err "Could not resolve #{ip}: #{detail}" - node = "unknown" - end - authenticated = false - end - - Puppet::Network::ClientRequest.new(node, ip, authenticated) - end - -end - diff --git a/lib/puppet/network/http/webrick.rb b/lib/puppet/network/http/webrick.rb index 52aec1bf1..6cbc7a85a 100644 --- a/lib/puppet/network/http/webrick.rb +++ b/lib/puppet/network/http/webrick.rb @@ -1,139 +1,107 @@ 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 @mutex = Mutex.new end - def self.class_for_protocol(protocol) - return Puppet::Network::HTTP::WEBrickREST if protocol.to_sym == :rest - raise "Unknown protocol [#{protocol}]." - end - def listen(args = {}) - raise ArgumentError, ":protocols must be specified." if !args[:protocols] or args[:protocols].empty? raise ArgumentError, ":address must be specified." unless args[:address] raise ArgumentError, ":port must be specified." unless args[:port] - @protocols = args[:protocols] - @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) @server.listeners.each { |l| l.start_immediately = false } - setup_handlers + @server.mount('/', Puppet::Network::HTTP::WEBrickREST, :this_value_is_apparently_necessary_but_unused) @mutex.synchronize do raise "WEBrick server is already listening" if @listening @listening = true @thread = Thread.new { @server.start { |sock| raise "Client disconnected before connection could be established" unless IO.select([sock],nil,nil,6.2) sock.accept @server.run(sock) } } sleep 0.1 until @server.status == :Running end end def unlisten @mutex.synchronize do raise "WEBrick server is not listening" unless @listening @server.shutdown @thread.join @server = nil @listening = false end end def listening? @mutex.synchronize do @listening 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.run_mode.master? 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 = {} # Get the cached copy. We know it's been generated, too. host = Puppet::SSL::Host.localhost raise Puppet::Error, "Could not retrieve certificate for #{host.name} and not running on a valid certificate authority" unless host.certificate results[:SSLPrivateKey] = host.key.content results[:SSLCertificate] = host.certificate.content results[:SSLStartImmediately] = true results[:SSLEnable] = true raise Puppet::Error, "Could not find CA certificate" unless Puppet::SSL::Certificate.indirection.find(Puppet::SSL::CA_NAME) 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. - klass = self.class.class_for_protocol(:rest) - @server.mount('/', klass, :this_value_is_apparently_necessary_but_unused) - - # And then set up xmlrpc, if configured. - @server.mount("/RPC2", xmlrpc_servlet) if @protocols.include?(:xmlrpc) and ! @xmlrpc_handlers.empty? - 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 #{handler}" - end - hclass.new({}) - } - Puppet::Network::XMLRPC::WEBrickServlet.new handlers - end end diff --git a/lib/puppet/network/http_server.rb b/lib/puppet/network/http_server.rb deleted file mode 100644 index e3826a654..000000000 --- a/lib/puppet/network/http_server.rb +++ /dev/null @@ -1,3 +0,0 @@ -# Just a stub, so we can correctly scope other classes. -module Puppet::Network::HTTPServer # :nodoc: -end diff --git a/lib/puppet/network/http_server/mongrel.rb b/lib/puppet/network/http_server/mongrel.rb deleted file mode 100644 index fb9516461..000000000 --- a/lib/puppet/network/http_server/mongrel.rb +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env ruby -# File: 06-11-14-mongrel_xmlrpc.rb -# Author: Manuel Holtgrewe -# -# Copyright (c) 2006 Manuel Holtgrewe, 2007 Luke Kanies -# -# This file is based heavily on a file retrieved from -# http://ttt.ggnore.net/2006/11/15/xmlrpc-with-mongrel-and-ruby-off-rails/ - -require 'rubygems' -require 'mongrel' -require 'xmlrpc/server' -require 'puppet/network/xmlrpc/server' -require 'puppet/network/http_server' -require 'puppet/network/client_request' -require 'puppet/network/handler' - -require 'resolv' - -# This handler can be hooked into Mongrel to accept HTTP requests. After -# checking whether the request itself is sane, the handler forwards it -# to an internal instance of XMLRPC::BasicServer to process it. -# -# You can access the server by calling the Handler's "xmlrpc_server" -# attribute accessor method and add XMLRPC handlers there. For example: -# -#
-# handler = XmlRpcHandler.new
-# handler.xmlrpc_server.add_handler("my.add") { |a, b| a.to_i + b.to_i }
-# 
-module Puppet::Network - class HTTPServer::Mongrel < ::Mongrel::HttpHandler - attr_reader :xmlrpc_server - - def initialize(handlers) - if Puppet[:debug] - $mongrel_debug_client = true - Puppet.debug 'Mongrel client debugging enabled. [$mongrel_debug_client = true].' - end - # Create a new instance of BasicServer. We are supposed to subclass it - # but that does not make sense since we would not introduce any new - # behaviour and we have to subclass Mongrel::HttpHandler so our handler - # works for Mongrel. - @xmlrpc_server = Puppet::Network::XMLRPCServer.new - handlers.each do |name| - unless handler = Puppet::Network::Handler.handler(name) - raise ArgumentError, "Invalid handler #{name}" - end - @xmlrpc_server.add_handler(handler.interface, handler.new({})) - end - end - - # This method produces the same results as XMLRPC::CGIServer.serve - # from Ruby's stdlib XMLRPC implementation. - def process(request, response) - # Make sure this has been a POST as required for XMLRPC. - request_method = request.params[Mongrel::Const::REQUEST_METHOD] || Mongrel::Const::GET - if request_method != "POST" - response.start(405) { |head, out| out.write("Method Not Allowed") } - return - end - - # Make sure the user has sent text/xml data. - request_mime = request.params["CONTENT_TYPE"] || "text/plain" - if parse_content_type(request_mime).first != "text/xml" - response.start(400) { |head, out| out.write("Bad Request") } - return - end - - # Make sure there is data in the body at all. - length = request.params[Mongrel::Const::CONTENT_LENGTH].to_i - if length <= 0 - response.start(411) { |head, out| out.write("Length Required") } - return - end - - # Check the body to be valid. - if request.body.nil? or request.body.size != length - response.start(400) { |head, out| out.write("Bad Request") } - return - end - - info = client_info(request) - - # All checks above passed through - response.start(200) do |head, out| - head["Content-Type"] = "text/xml; charset=utf-8" - begin - out.write(@xmlrpc_server.process(request.body, info)) - rescue => detail - puts detail.backtrace - raise - end - end - end - - private - - def client_info(request) - params = request.params - ip = params["HTTP_X_FORWARDED_FOR"] ? params["HTTP_X_FORWARDED_FOR"].split(',').last.strip : params["REMOTE_ADDR"] - # JJM #906 The following dn.match regular expression is forgiving - # enough to match the two Distinguished Name string contents - # coming from Apache, Pound or other reverse SSL proxies. - if dn = params[Puppet[:ssl_client_header]] and dn_matchdata = dn.match(/^.*?CN\s*=\s*(.*)/) - client = dn_matchdata[1].to_str - valid = (params[Puppet[:ssl_client_verify_header]] == 'SUCCESS') - else - begin - client = Resolv.getname(ip) - rescue => detail - Puppet.err "Could not resolve #{ip}: #{detail}" - client = "unknown" - end - valid = false - end - - info = Puppet::Network::ClientRequest.new(client, ip, valid) - - info - end - - # Taken from XMLRPC::ParseContentType - def parse_content_type(str) - a, *b = str.split(";") - return a.strip, *b - end - end -end diff --git a/lib/puppet/network/rest_authconfig.rb b/lib/puppet/network/rest_authconfig.rb index 80167103b..94bc60d02 100644 --- a/lib/puppet/network/rest_authconfig.rb +++ b/lib/puppet/network/rest_authconfig.rb @@ -1,102 +1,98 @@ require 'puppet/network/authconfig' module Puppet class Network::RestAuthConfig < Network::AuthConfig extend MonitorMixin attr_accessor :rights DEFAULT_ACL = [ { :acl => "~ ^\/catalog\/([^\/]+)$", :method => :find, :allow => '$1', :authenticated => true }, { :acl => "~ ^\/node\/([^\/]+)$", :method => :find, :allow => '$1', :authenticated => true }, # this one will allow all file access, and thus delegate # to fileserver.conf { :acl => "/file" }, { :acl => "/certificate_revocation_list/ca", :method => :find, :authenticated => true }, { :acl => "/report", :method => :save, :authenticated => true }, # These allow `auth any`, because if you can do them anonymously you # should probably also be able to do them when trusted. { :acl => "/certificate/ca", :method => :find, :authenticated => :any }, { :acl => "/certificate/", :method => :find, :authenticated => :any }, { :acl => "/certificate_request", :method => [:find, :save], :authenticated => :any }, { :acl => "/status", :method => [:find], :authenticated => true }, ] def self.main synchronize do add_acl = @main.nil? super @main.insert_default_acl if add_acl and !@main.exists? end @main end def allowed?(request) Puppet.deprecation_warning "allowed? should not be called for REST authorization - use check_authorization instead" check_authorization(request) end # check wether this request is allowed in our ACL # raise an Puppet::Network::AuthorizedError if the request # is denied. def check_authorization(indirection, method, key, params) read - # we're splitting the request in part because - # fail_on_deny could as well be called in the XMLRPC context - # with a ClientRequest. - if authorization_failure_exception = @rights.is_request_forbidden_and_why?(indirection, method, key, params) Puppet.warning("Denying access: #{authorization_failure_exception}") raise authorization_failure_exception end end def initialize(file = nil, parsenow = true) super(file || Puppet[:rest_authconfig], parsenow) # if we didn't read a file (ie it doesn't exist) # make sure we can create some default rights @rights ||= Puppet::Network::Rights.new end def parse super() insert_default_acl end # force regular ACLs to be present def insert_default_acl if exists? then reason = "none were found in '#{@file}'" else reason = "#{Puppet[:rest_authconfig]} doesn't exist" end DEFAULT_ACL.each do |acl| unless rights[acl[:acl]] Puppet.info "Inserting default '#{acl[:acl]}' (auth #{acl[:authenticated]}) ACL because #{reason}" mk_acl(acl) end end # queue an empty (ie deny all) right for every other path # actually this is not strictly necessary as the rights system # denies not explicitely allowed paths unless rights["/"] rights.newright("/") rights.restrict_authenticated("/", :any) end end def mk_acl(acl) @rights.newright(acl[:acl]) @rights.allow(acl[:acl], acl[:allow] || "*") if method = acl[:method] method = [method] unless method.is_a?(Array) method.each { |m| @rights.restrict_method(acl[:acl], m) } end @rights.restrict_authenticated(acl[:acl], acl[:authenticated]) unless acl[:authenticated].nil? end end end diff --git a/lib/puppet/network/server.rb b/lib/puppet/network/server.rb index e4de07dea..87952f9cf 100644 --- a/lib/puppet/network/server.rb +++ b/lib/puppet/network/server.rb @@ -1,165 +1,139 @@ require 'puppet/network/http' require 'puppet/util/pidlock' class Puppet::Network::Server - attr_reader :server_type, :protocols, :address, :port + attr_reader :server_type, :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 Puppet::Util.secure_open("/tmp/daemonout", "w") { |f| f.puts "Could not start #{Puppet[:name]}: #{detail}" } raise "Could not start #{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.synchronize_on(Puppet[:name],Sync::EX) do raise "Could not create PID file: #{pidfile}" unless Puppet::Util::Pidlock.new(pidfile).lock end end # Remove the pid file for our daemon. def remove_pidfile Puppet::Util.synchronize_on(Puppet[:name],Sync::EX) do locker = Puppet::Util::Pidlock.new(pidfile) locker.unlock or Puppet.err "Could not remove PID file #{pidfile}" if locker.locked? end end # Provide the path to our pidfile. def pidfile Puppet[:pidfile] end def initialize(args = {}) - valid_args = [:handlers, :xmlrpc_handlers, :port] + valid_args = [:handlers, :port] bad_args = args.keys.find_all { |p| ! valid_args.include?(p) }.collect { |p| p.to_s }.join(",") raise ArgumentError, "Invalid argument(s) #{bad_args}" unless bad_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}]") @port = args[:port] || Puppet[:masterport] || raise(ArgumentError, "Must specify :port or configure Puppet :masterport") @address = determine_bind_address - @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 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? indirections.flatten.each do |i| raise(ArgumentError, "Indirection [#{i}] is unknown.") 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? - - namespaces.flatten.each do |i| - raise(ArgumentError, "XMLRPC handler '#{i}' is unknown.") unless @xmlrpc_routes[i.to_sym] - end - - namespaces.flatten.each do |i| - @xmlrpc_routes.delete(i.to_sym) - end - end - def listening? @listening end def listen 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) + http_server.listen(:address => address, :port => port, :handlers => @routes.keys) end def unlisten 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 @http_server ||= http_server_class.new end def http_server_class_by_type(kind) Puppet::Network::HTTP.server_class_by_type(kind) end def determine_bind_address tmp = Puppet[:bindaddress] return tmp if tmp != "" server_type.to_s == "webrick" ? "0.0.0.0" : "127.0.0.1" end end diff --git a/lib/puppet/network/xmlrpc/processor.rb b/lib/puppet/network/xmlrpc/processor.rb deleted file mode 100644 index dea8a02fa..000000000 --- a/lib/puppet/network/xmlrpc/processor.rb +++ /dev/null @@ -1,86 +0,0 @@ -require 'puppet/network/authorization' -require 'xmlrpc/server' - -# Just silly. -class ::XMLRPC::FaultException - def to_s - self.message - end -end - -module Puppet::Network - # Most of our subclassing is just so that we can get - # access to information from the request object, like - # the client name and IP address. - module XMLRPCProcessor - include Puppet::Network::Authorization - - ERR_UNAUTHORIZED = 30 - - def add_handler(interface, handler) - @loadedhandlers << interface.prefix - super(interface, handler) - end - - def handler_loaded?(handler) - @loadedhandlers.include?(handler.to_s) - end - - # Convert our data and client request into xmlrpc calls, and verify - # they're authorized and such-like. This method differs from the - # default in that it expects a ClientRequest object in addition to the - # data. - def process(data, request) - call, params = parser.parseMethodCall(data) - params << request.name << request.ip - handler, method = call.split(".") - request.handler = handler - request.method = method - begin - verify(request) - rescue InvalidClientRequest => detail - raise ::XMLRPC::FaultException.new(ERR_UNAUTHORIZED, detail.to_s) - end - handle(request.call, *params) - end - - private - - # Provide error handling for method calls. - def protect_service(obj, *args) - begin - obj.call(*args) - rescue ::XMLRPC::FaultException - raise - rescue Puppet::AuthorizationError => detail - Puppet.err "Permission denied: #{detail}" - raise ::XMLRPC::FaultException.new( - 1, detail.to_s - ) - rescue Puppet::Error => detail - puts detail.backtrace if Puppet[:trace] - Puppet.err detail.to_s - error = ::XMLRPC::FaultException.new( - 1, detail.to_s - ) - error.set_backtrace detail.backtrace - raise error - rescue => detail - puts detail.backtrace if Puppet[:trace] - Puppet.err "Could not call: #{detail}" - error = ::XMLRPC::FaultException.new(1, detail.to_s) - error.set_backtrace detail.backtrace - raise error - end - end - - # Set up our service hook and init our handler list. - def setup_processor - @loadedhandlers = [] - self.set_service_hook do |obj, *args| - protect_service(obj, *args) - end - end - end -end - diff --git a/lib/puppet/network/xmlrpc/server.rb b/lib/puppet/network/xmlrpc/server.rb deleted file mode 100644 index e54881756..000000000 --- a/lib/puppet/network/xmlrpc/server.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'xmlrpc/server' -require 'puppet/network/authorization' -require 'puppet/network/xmlrpc/processor' - -module Puppet::Network - # Most of our subclassing is just so that we can get - # access to information from the request object, like - # the client name and IP address. - class XMLRPCServer < ::XMLRPC::BasicServer - include Puppet::Util - include Puppet::Network::XMLRPCProcessor - - def initialize - super() - setup_processor - end - end -end - diff --git a/lib/puppet/network/xmlrpc/webrick_servlet.rb b/lib/puppet/network/xmlrpc/webrick_servlet.rb deleted file mode 100644 index c538cf74c..000000000 --- a/lib/puppet/network/xmlrpc/webrick_servlet.rb +++ /dev/null @@ -1,114 +0,0 @@ -require 'xmlrpc/server' -require 'puppet/network/authorization' -require 'puppet/network/xmlrpc/processor' - -module Puppet::Network::XMLRPC - class ServletError < RuntimeError; end - class WEBrickServlet < ::XMLRPC::WEBrickServlet - include Puppet::Network::XMLRPCProcessor - - # This is a hackish way to avoid an auth message every time we have a - # normal operation - def self.log(msg) - @logs ||= {} - if @logs.include?(msg) - @logs[msg] += 1 - else - Puppet.info msg - @logs[msg] = 1 - end - end - - # Accept a list of handlers and register them all. - def initialize(handlers) - # the servlet base class does not consume any arguments - # and its BasicServer base class only accepts a 'class_delim' - # option which won't change in Puppet at all - # thus, we don't need to pass any args to our base class, - # and we can consume them all ourselves - super() - - setup_processor - - # Set up each of the passed handlers. - handlers.each do |handler| - add_handler(handler.class.interface, handler) - end - end - - # Handle the actual request. We can't use the super() method, because - # we need to pass a ClientRequest object to process so we can do - # authorization. It's the only way to stay thread-safe. - def service(request, response) - if @valid_ip - raise WEBrick::HTTPStatus::Forbidden unless @valid_ip.any? { |ip| request.peeraddr[3] =~ ip } - end - - if request.request_method != "POST" - raise WEBrick::HTTPStatus::MethodNotAllowed, - "unsupported method `#{request.request_method}'." - end - - raise WEBrick::HTTPStatus::BadRequest if parse_content_type(request['Content-type']).first != "text/xml" - - length = (request['Content-length'] || 0).to_i - - raise WEBrick::HTTPStatus::LengthRequired unless length > 0 - - data = request.body - - raise WEBrick::HTTPStatus::BadRequest if data.nil? or data.size != length - - resp = process(data, client_request(request)) - raise WEBrick::HTTPStatus::InternalServerError if resp.nil? or resp.size <= 0 - - response.status = 200 - response['Content-Length'] = resp.size - response['Content-Type'] = "text/xml; charset=utf-8" - response.body = resp - end - - private - - # Generate a ClientRequest object for later validation. - def client_request(request) - if peer = request.peeraddr - client = peer[2] - clientip = peer[3] - else - - raise ::XMLRPC::FaultException.new( - - ERR_UNCAUGHT_EXCEPTION, - - "Could not retrieve client information" - ) - end - - # If they have a certificate (which will almost always be true) - # then we get the hostname from the cert, instead of via IP - # info - valid = false - if cert = request.client_cert - nameary = cert.subject.to_a.find { |ary| - ary[0] == "CN" - } - - if nameary.nil? - Puppet.warning "Could not retrieve server name from cert" - else - unless client == nameary[1] - Puppet.debug "Overriding #{client} with cert name #{nameary[1]}" - client = nameary[1] - end - valid = true - end - end - - info = Puppet::Network::ClientRequest.new(client, clientip, valid) - - info - end - end -end - diff --git a/lib/puppet/reference/network.rb b/lib/puppet/reference/network.rb deleted file mode 100644 index ee8fea07e..000000000 --- a/lib/puppet/reference/network.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'puppet/network/handler' - -network = Puppet::Util::Reference.newreference :network, :depth => 2, :doc => "Available network handlers and clients" do - ret = "" - Puppet::Network::Handler.subclasses.sort { |a,b| a.to_s <=> b.to_s }.each do |name| - handler = Puppet::Network::Handler.handler(name) - - next if ! handler.doc or handler.doc == "" - - interface = handler.interface - - ret << markdown_header(name, 2) - - ret << scrub(handler.doc) - ret << "\n\n" - ret << option(:prefix, interface.prefix) - ret << option(:side, handler.side.to_s.capitalize) - ret << option(:methods, interface.methods.collect { |ary| ary[0] }.join(", ") ) - ret << "\n\n" - end - - ret -end - -network.header = " -This is a list of all Puppet network interfaces. Each interface is -implemented in the form of a client and a handler; the handler is loaded -on the server, and the client knows how to call the handler's methods -appropriately. - -Most handlers are meant to be started on the server, usually within -`puppet master`, and the clients are mostly started on the client, -usually within `puppet agent`. - -You can find the server-side handler for each interface at -`puppet/network/handler/.rb` and the client class at -`puppet/network/client/.rb`. - -" diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb index c89ea935a..2d537714f 100644 --- a/lib/puppet/type/file.rb +++ b/lib/puppet/type/file.rb @@ -1,814 +1,813 @@ require 'digest/md5' require 'cgi' require 'etc' require 'uri' require 'fileutils' require 'enumerator' require 'pathname' -require 'puppet/network/handler' require 'puppet/util/diff' require 'puppet/util/checksums' require 'puppet/util/backups' Puppet::Type.newtype(:file) do include Puppet::Util::MethodHelper include Puppet::Util::Checksums include Puppet::Util::Backups @doc = "Manages local files, including setting ownership and permissions, creation of both files and directories, and retrieving entire files from remote servers. As Puppet matures, it expected that the `file` resource will be used less and less to manage content, and instead native resources will be used to do so. If you find that you are often copying files in from a central location, rather than using native resources, please contact Puppet Labs and we can hopefully work with you to develop a native resource to support what you are doing. **Autorequires:** If Puppet is managing the user or group that owns a file, the file resource will autorequire them. If Puppet is managing any parent directories of a file, the file resource will autorequire them." def self.title_patterns [ [ /^(.*?)\/*\Z/m, [ [ :path, lambda{|x| x} ] ] ] ] end newparam(:path) do desc "The path to the file to manage. Must be fully qualified." isnamevar validate do |value| unless Puppet::Util.absolute_path?(value) fail Puppet::Error, "File paths must be fully qualified, not '#{value}'" end end # convert the current path in an index into the collection and the last # path name. The aim is to use less storage for all common paths in a hierarchy munge do |value| # We know the value is absolute, so expanding it will just standardize it. path, name = ::File.split(::File.expand_path(value)) { :index => Puppet::FileCollection.collection.index(path), :name => name } end # and the reverse unmunge do |value| basedir = Puppet::FileCollection.collection.path(value[:index]) ::File.join( basedir, value[:name] ) end end newparam(:backup) do desc "Whether files should be backed up before being replaced. The preferred method of backing files up is via a `filebucket`, which stores files by their MD5 sums and allows easy retrieval without littering directories with backups. You can specify a local filebucket or a network-accessible server-based filebucket by setting `backup => bucket-name`. Alternatively, if you specify any value that begins with a `.` (e.g., `.puppet-bak`), then Puppet will use copy the file in the same directory with that value as the extension of the backup. Setting `backup => false` disables all backups of the file in question. Puppet automatically creates a local filebucket named `puppet` and defaults to backing up there. To use a server-based filebucket, you must specify one in your configuration. filebucket { main: server => puppet, path => false, # The path => false line works around a known issue with the filebucket type. } The `puppet master` daemon creates a filebucket by default, so you can usually back up to your main server with this configuration. Once you've described the bucket in your configuration, you can use it in any file's backup attribute: file { \"/my/file\": source => \"/path/in/nfs/or/something\", backup => main } This will back the file up to the central server. At this point, the benefits of using a central filebucket are that you do not have backup files lying around on each of your machines, a given version of a file is only backed up once, you can restore any given file manually (no matter how old), and you can use Puppet Dashboard to view file contents. Eventually, transactional support will be able to automatically restore filebucketed files. " defaultto "puppet" munge do |value| # I don't really know how this is happening. value = value.shift if value.is_a?(Array) case value when false, "false", :false false when true, "true", ".puppet-bak", :true ".puppet-bak" when String value else self.fail "Invalid backup type #{value.inspect}" end end end newparam(:recurse) do desc "Whether and how deeply to do recursive management. Options are: * `inf,true` --- Regular style recursion on both remote and local directory structure. * `remote` --- Descends recursively into the remote directory but not the local directory. Allows copying of a few files into a directory containing many unmanaged files without scanning all the local files. * `false` --- Default of no recursion. * `[0-9]+` --- Same as true, but limit recursion. Warning: this syntax has been deprecated in favor of the `recurselimit` attribute. " newvalues(:true, :false, :inf, :remote, /^[0-9]+$/) # Replace the validation so that we allow numbers in # addition to string representations of them. validate { |arg| } munge do |value| newval = super(value) case newval when :true, :inf; true when :false; false when :remote; :remote when Integer, Fixnum, Bignum self.warning "Setting recursion depth with the recurse parameter is now deprecated, please use recurselimit" # recurse == 0 means no recursion return false if value == 0 resource[:recurselimit] = value true when /^\d+$/ self.warning "Setting recursion depth with the recurse parameter is now deprecated, please use recurselimit" value = Integer(value) # recurse == 0 means no recursion return false if value == 0 resource[:recurselimit] = value true else self.fail "Invalid recurse value #{value.inspect}" end end end newparam(:recurselimit) do desc "How deeply to do recursive management." newvalues(/^[0-9]+$/) munge do |value| newval = super(value) case newval when Integer, Fixnum, Bignum; value when /^\d+$/; Integer(value) else self.fail "Invalid recurselimit value #{value.inspect}" end end end newparam(:replace, :boolean => true) do desc "Whether or not to replace a file that is sourced but exists. This is useful for using file sources purely for initialization." newvalues(:true, :false) aliasvalue(:yes, :true) aliasvalue(:no, :false) defaultto :true end newparam(:force, :boolean => true) do desc "Force the file operation. Currently only used when replacing directories with links." newvalues(:true, :false) defaultto false end newparam(:ignore) do desc "A parameter which omits action on files matching specified patterns during recursion. Uses Ruby's builtin globbing engine, so shell metacharacters are fully supported, e.g. `[a-z]*`. Matches that would descend into the directory structure are ignored, e.g., `*/*`." validate do |value| unless value.is_a?(Array) or value.is_a?(String) or value == false self.devfail "Ignore must be a string or an Array" end end end newparam(:links) do desc "How to handle links during file actions. During file copying, `follow` will copy the target file instead of the link, `manage` will copy the link itself, and `ignore` will just pass it by. When not copying, `manage` and `ignore` behave equivalently (because you cannot really ignore links entirely during local recursion), and `follow` will manage the file to which the link points." newvalues(:follow, :manage) defaultto :manage end newparam(:purge, :boolean => true) do desc "Whether unmanaged files should be purged. If you have a filebucket configured the purged files will be uploaded, but if you do not, this will destroy data. Only use this option for generated files unless you really know what you are doing. This option only makes sense when recursively managing directories. Note that when using `purge` with `source`, Puppet will purge any files that are not on the remote system." defaultto :false newvalues(:true, :false) end newparam(:sourceselect) do desc "Whether to copy all valid sources, or just the first one. This parameter is only used in recursive copies; by default, the first valid source is the only one used as a recursive source, but if this parameter is set to `all`, then all valid sources will have all of their contents copied to the local host, and for sources that have the same file, the source earlier in the list will be used." defaultto :first newvalues(:first, :all) end # Autorequire the nearest ancestor directory found in the catalog. autorequire(:file) do req = [] path = Pathname.new(self[:path]) if !path.root? # Start at our parent, to avoid autorequiring ourself parents = path.parent.enum_for(:ascend) if found = parents.find { |p| catalog.resource(:file, p.to_s) } req << found.to_s end end # if the resource is a link, make sure the target is created first req << self[:target] if self[:target] req end # Autorequire the owner and group of the file. {:user => :owner, :group => :group}.each do |type, property| autorequire(type) do if @parameters.include?(property) # The user/group property automatically converts to IDs next unless should = @parameters[property].shouldorig val = should[0] if val.is_a?(Integer) or val =~ /^\d+$/ nil else val end end end end CREATORS = [:content, :source, :target] SOURCE_ONLY_CHECKSUMS = [:none, :ctime, :mtime] validate do creator_count = 0 CREATORS.each do |param| creator_count += 1 if self.should(param) end creator_count += 1 if @parameters.include?(:source) self.fail "You cannot specify more than one of #{CREATORS.collect { |p| p.to_s}.join(", ")}" if creator_count > 1 self.fail "You cannot specify a remote recursion without a source" if !self[:source] and self[:recurse] == :remote self.fail "You cannot specify source when using checksum 'none'" if self[:checksum] == :none && !self[:source].nil? SOURCE_ONLY_CHECKSUMS.each do |checksum_type| self.fail "You cannot specify content when using checksum '#{checksum_type}'" if self[:checksum] == checksum_type && !self[:content].nil? end self.warning "Possible error: recurselimit is set but not recurse, no recursion will happen" if !self[:recurse] and self[:recurselimit] provider.validate if provider.respond_to?(:validate) end def self.[](path) return nil unless path super(path.gsub(/\/+/, '/').sub(/\/$/, '')) end def self.instances return [] end # Determine the user to write files as. def asuser if self.should(:owner) and ! self.should(:owner).is_a?(Symbol) writeable = Puppet::Util::SUIDManager.asuser(self.should(:owner)) { FileTest.writable?(::File.dirname(self[:path])) } # If the parent directory is writeable, then we execute # as the user in question. Otherwise we'll rely on # the 'owner' property to do things. asuser = self.should(:owner) if writeable end asuser end def bucket return @bucket if @bucket backup = self[:backup] return nil unless backup return nil if backup =~ /^\./ unless catalog or backup == "puppet" fail "Can not find filebucket for backups without a catalog" end unless catalog and filebucket = catalog.resource(:filebucket, backup) or backup == "puppet" fail "Could not find filebucket #{backup} specified in backup" end return default_bucket unless filebucket @bucket = filebucket.bucket @bucket end def default_bucket Puppet::Type.type(:filebucket).mkdefaultbucket.bucket end # Does the file currently exist? Just checks for whether # we have a stat def exist? stat ? true : false end # We have to do some extra finishing, to retrieve our bucket if # there is one. def finish # Look up our bucket, if there is one bucket super end # Create any children via recursion or whatever. def eval_generate return [] unless self.recurse? recurse #recurse.reject do |resource| # catalog.resource(:file, resource[:path]) #end.each do |child| # catalog.add_resource child # catalog.relationship_graph.add_edge self, child #end end def ancestors ancestors = Pathname.new(self[:path]).enum_for(:ascend).map(&:to_s) ancestors.delete(self[:path]) ancestors end def flush # We want to make sure we retrieve metadata anew on each transaction. @parameters.each do |name, param| param.flush if param.respond_to?(:flush) end @stat = :needs_stat end def initialize(hash) # Used for caching clients @clients = {} super # If they've specified a source, we get our 'should' values # from it. unless self[:ensure] if self[:target] self[:ensure] = :symlink elsif self[:content] self[:ensure] = :file end end @stat = :needs_stat end # Configure discovered resources to be purged. def mark_children_for_purging(children) children.each do |name, child| next if child[:source] child[:ensure] = :absent end end # Create a new file or directory object as a child to the current # object. def newchild(path) full_path = ::File.join(self[:path], path) # Add some new values to our original arguments -- these are the ones # set at initialization. We specifically want to exclude any param # values set by the :source property or any default values. # LAK:NOTE This is kind of silly, because the whole point here is that # the values set at initialization should live as long as the resource # but values set by default or by :source should only live for the transaction # or so. Unfortunately, we don't have a straightforward way to manage # the different lifetimes of this data, so we kludge it like this. # The right-side hash wins in the merge. options = @original_parameters.merge(:path => full_path).reject { |param, value| value.nil? } # These should never be passed to our children. [:parent, :ensure, :recurse, :recurselimit, :target, :alias, :source].each do |param| options.delete(param) if options.include?(param) end self.class.new(options) end # Files handle paths specially, because they just lengthen their # path names, rather than including the full parent's title each # time. def pathbuilder # We specifically need to call the method here, so it looks # up our parent in the catalog graph. if parent = parent() # We only need to behave specially when our parent is also # a file if parent.is_a?(self.class) # Remove the parent file name list = parent.pathbuilder list.pop # remove the parent's path info return list << self.ref else return super end else return [self.ref] end end # Should we be purging? def purge? @parameters.include?(:purge) and (self[:purge] == :true or self[:purge] == "true") end # Recursively generate a list of file resources, which will # be used to copy remote files, manage local files, and/or make links # to map to another directory. def recurse children = (self[:recurse] == :remote) ? {} : recurse_local if self[:target] recurse_link(children) elsif self[:source] recurse_remote(children) end # If we're purging resources, then delete any resource that isn't on the # remote system. mark_children_for_purging(children) if self.purge? result = children.values.sort { |a, b| a[:path] <=> b[:path] } remove_less_specific_files(result) end # This is to fix bug #2296, where two files recurse over the same # set of files. It's a rare case, and when it does happen you're # not likely to have many actual conflicts, which is good, because # this is a pretty inefficient implementation. def remove_less_specific_files(files) mypath = self[:path].split(::File::Separator) other_paths = catalog.vertices. select { |r| r.is_a?(self.class) and r[:path] != self[:path] }. collect { |r| r[:path].split(::File::Separator) }. select { |p| p[0,mypath.length] == mypath } return files if other_paths.empty? files.reject { |file| path = file[:path].split(::File::Separator) other_paths.any? { |p| path[0,p.length] == p } } end # A simple method for determining whether we should be recursing. def recurse? self[:recurse] == true or self[:recurse] == :remote end # Recurse the target of the link. def recurse_link(children) perform_recursion(self[:target]).each do |meta| if meta.relative_path == "." self[:ensure] = :directory next end children[meta.relative_path] ||= newchild(meta.relative_path) if meta.ftype == "directory" children[meta.relative_path][:ensure] = :directory else children[meta.relative_path][:ensure] = :link children[meta.relative_path][:target] = meta.full_path end end children end # Recurse the file itself, returning a Metadata instance for every found file. def recurse_local result = perform_recursion(self[:path]) return {} unless result result.inject({}) do |hash, meta| next hash if meta.relative_path == "." hash[meta.relative_path] = newchild(meta.relative_path) hash end end # Recurse against our remote file. def recurse_remote(children) sourceselect = self[:sourceselect] total = self[:source].collect do |source| next unless result = perform_recursion(source) return if top = result.find { |r| r.relative_path == "." } and top.ftype != "directory" result.each { |data| data.source = "#{source}/#{data.relative_path}" } break result if result and ! result.empty? and sourceselect == :first result end.flatten # This only happens if we have sourceselect == :all unless sourceselect == :first found = [] total.reject! do |data| result = found.include?(data.relative_path) found << data.relative_path unless found.include?(data.relative_path) result end end total.each do |meta| if meta.relative_path == "." parameter(:source).metadata = meta next end children[meta.relative_path] ||= newchild(meta.relative_path) children[meta.relative_path][:source] = meta.source children[meta.relative_path][:checksum] = :md5 if meta.ftype == "file" children[meta.relative_path].parameter(:source).metadata = meta end children end def perform_recursion(path) Puppet::FileServing::Metadata.indirection.search( path, :links => self[:links], :recurse => (self[:recurse] == :remote ? true : self[:recurse]), :recurselimit => self[:recurselimit], :ignore => self[:ignore], :checksum_type => (self[:source] || self[:content]) ? self[:checksum] : :none ) end # Remove any existing data. This is only used when dealing with # links or directories. def remove_existing(should) return unless s = stat self.fail "Could not back up; will not replace" unless perform_backup unless should.to_s == "link" return if s.ftype.to_s == should.to_s end case s.ftype when "directory" if self[:force] == :true debug "Removing existing directory for replacement with #{should}" FileUtils.rmtree(self[:path]) else notice "Not removing directory; use 'force' to override" return end when "link", "file" debug "Removing existing #{s.ftype} for replacement with #{should}" ::File.unlink(self[:path]) else self.fail "Could not back up files of type #{s.ftype}" end @stat = :needs_stat true end def retrieve if source = parameter(:source) source.copy_source_values end super end # Set the checksum, from another property. There are multiple # properties that modify the contents of a file, and they need the # ability to make sure that the checksum value is in sync. def setchecksum(sum = nil) if @parameters.include? :checksum if sum @parameters[:checksum].checksum = sum else # If they didn't pass in a sum, then tell checksum to # figure it out. currentvalue = @parameters[:checksum].retrieve @parameters[:checksum].checksum = currentvalue end end end # Should this thing be a normal file? This is a relatively complex # way of determining whether we're trying to create a normal file, # and it's here so that the logic isn't visible in the content property. def should_be_file? return true if self[:ensure] == :file # I.e., it's set to something like "directory" return false if e = self[:ensure] and e != :present # The user doesn't really care, apparently if self[:ensure] == :present return true unless s = stat return(s.ftype == "file" ? true : false) end # If we've gotten here, then :ensure isn't set return true if self[:content] return true if stat and stat.ftype == "file" false end # Stat our file. Depending on the value of the 'links' attribute, we # use either 'stat' or 'lstat', and we expect the properties to use the # resulting stat object accordingly (mostly by testing the 'ftype' # value). # # We use the initial value :needs_stat to ensure we only stat the file once, # but can also keep track of a failed stat (@stat == nil). This also allows # us to re-stat on demand by setting @stat = :needs_stat. def stat return @stat unless @stat == :needs_stat method = :stat # Files are the only types that support links if (self.class.name == :file and self[:links] != :follow) or self.class.name == :tidy method = :lstat end @stat = begin ::File.send(method, self[:path]) rescue Errno::ENOENT => error nil rescue Errno::EACCES => error warning "Could not stat; permission denied" nil end end def to_resource resource = super resource.delete(:target) if resource[:target] == :notlink resource end # Write out the file. Requires the property name for logging. # Write will be done by the content property, along with checksum computation def write(property) remove_existing(:file) use_temporary_file = write_temporary_file? if use_temporary_file path = "#{self[:path]}.puppettmp_#{rand(10000)}" path = "#{self[:path]}.puppettmp_#{rand(10000)}" while ::File.exists?(path) or ::File.symlink?(path) else path = self[:path] end mode = self.should(:mode) # might be nil umask = mode ? 000 : 022 mode_int = mode ? mode.to_i(8) : nil content_checksum = Puppet::Util.withumask(umask) { ::File.open(path, 'wb', mode_int ) { |f| write_content(f) } } # And put our new file in place if use_temporary_file # This is only not true when our file is empty. begin fail_if_checksum_is_wrong(path, content_checksum) if validate_checksum? ::File.rename(path, self[:path]) rescue => detail fail "Could not rename temporary file #{path} to #{self[:path]}: #{detail}" ensure # Make sure the created file gets removed ::File.unlink(path) if FileTest.exists?(path) end end # make sure all of the modes are actually correct property_fix end private # Should we validate the checksum of the file we're writing? def validate_checksum? self[:checksum] !~ /time/ end # Make sure the file we wrote out is what we think it is. def fail_if_checksum_is_wrong(path, content_checksum) newsum = parameter(:checksum).sum_file(path) return if [:absent, nil, content_checksum].include?(newsum) self.fail "File written to disk did not match checksum; discarding changes (#{content_checksum} vs #{newsum})" end # write the current content. Note that if there is no content property # simply opening the file with 'w' as done in write is enough to truncate # or write an empty length file. def write_content(file) (content = property(:content)) && content.write(file) end private def write_temporary_file? # unfortunately we don't know the source file size before fetching it # so let's assume the file won't be empty (c = property(:content) and c.length) || (s = @parameters[:source] and 1) end # There are some cases where all of the work does not get done on # file creation/modification, so we have to do some extra checking. def property_fix properties.each do |thing| next unless [:mode, :owner, :group, :seluser, :selrole, :seltype, :selrange].include?(thing.name) # Make sure we get a new stat objct @stat = :needs_stat currentvalue = thing.retrieve thing.sync unless thing.safe_insync?(currentvalue) end end end # We put all of the properties in separate files, because there are so many # of them. The order these are loaded is important, because it determines # the order they are in the property lit. require 'puppet/type/file/checksum' require 'puppet/type/file/content' # can create the file require 'puppet/type/file/source' # can create the file require 'puppet/type/file/target' # creates a different type of file require 'puppet/type/file/ensure' # can create the file require 'puppet/type/file/owner' require 'puppet/type/file/group' require 'puppet/type/file/mode' require 'puppet/type/file/type' require 'puppet/type/file/selcontext' # SELinux file context require 'puppet/type/file/ctime' require 'puppet/type/file/mtime' diff --git a/spec/integration/network/handler_spec.rb b/spec/integration/network/handler_spec.rb deleted file mode 100755 index a71e76e71..000000000 --- a/spec/integration/network/handler_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env rspec -require 'spec_helper' - -require 'puppet/network/handler' - -describe Puppet::Network::Handler do - %w{ca filebucket fileserver report runner status}.each do |name| - it "should have a #{name} client" do - Puppet::Network::Handler.handler(name).should be_instance_of(Class) - end - - it "should have a name" do - Puppet::Network::Handler.handler(name).name.to_s.downcase.should == name.to_s.downcase - end - - it "should have an interface" do - Puppet::Network::Handler.handler(name).interface.should_not be_nil - end - - it "should have a prefix for the interface" do - Puppet::Network::Handler.handler(name).interface.prefix.should_not be_nil - end - end -end diff --git a/spec/integration/network/server/webrick_spec.rb b/spec/integration/network/server/webrick_spec.rb index 7365462d3..7fa83b05a 100755 --- a/spec/integration/network/server/webrick_spec.rb +++ b/spec/integration/network/server/webrick_spec.rb @@ -1,83 +1,83 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/network/server' require 'puppet/ssl/certificate_authority' require 'socket' describe Puppet::Network::Server, :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files describe "when using webrick" do before :each do Puppet[:servertype] = 'webrick' Puppet[:server] = '127.0.0.1' - @params = { :port => 34343, :handlers => [ :node ], :xmlrpc_handlers => [ :status ] } + @params = { :port => 34343, :handlers => [ :node ] } # Get a safe temporary file dir = tmpdir("webrick_integration_testing") Puppet.settings[:confdir] = dir Puppet.settings[:vardir] = dir Puppet.settings[:group] = Process.gid Puppet::SSL::Host.ca_location = :local ca = Puppet::SSL::CertificateAuthority.new ca.generate(Puppet[:certname]) unless Puppet::SSL::Certificate.indirection.find(Puppet[:certname]) end after do Puppet.settings.clear Puppet::SSL::Host.ca_location = :none 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 default to '0.0.0.0' as its bind address" do Puppet.settings.clear Puppet[:servertype] = 'webrick' Puppet[:bindaddress].should == '0.0.0.0' end it "should use any specified bind address" do Puppet[:bindaddress] = "127.0.0.1" @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) @server.stubs(:unlisten) # we're breaking listening internally, so we have to keep it from unlistening @server.send(:http_server).expects(:listen).with { |args| args[:address] == "127.0.0.1" } @server.listen 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 && @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 diff --git a/spec/unit/application/agent_spec.rb b/spec/unit/application/agent_spec.rb index 0b65b7923..c6045c895 100755 --- a/spec/unit/application/agent_spec.rb +++ b/spec/unit/application/agent_spec.rb @@ -1,594 +1,573 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/agent' require 'puppet/application/agent' require 'puppet/network/server' require 'puppet/daemon' -require 'puppet/network/handler' describe Puppet::Application::Agent do before :each do @puppetd = Puppet::Application[:agent] @puppetd.stubs(:puts) @daemon = stub_everything 'daemon' Puppet::Daemon.stubs(:new).returns(@daemon) Puppet[:daemonize] = false @agent = stub_everything 'agent' Puppet::Agent.stubs(:new).returns(@agent) @puppetd.preinit Puppet::Util::Log.stubs(:newdestination) Puppet::Node.indirection.stubs(:terminus_class=) Puppet::Node.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:terminus_class=) end it "should operate in agent run_mode" do @puppetd.class.run_mode.name.should == :agent end it "should ask Puppet::Application to parse Puppet configuration file" do @puppetd.should_parse_config?.should be_true end it "should declare a main command" do @puppetd.should respond_to(:main) end it "should declare a onetime command" do @puppetd.should respond_to(:onetime) end it "should declare a fingerprint command" do @puppetd.should respond_to(:fingerprint) end it "should declare a preinit block" do @puppetd.should respond_to(:preinit) end describe "in preinit" do it "should catch INT" do Signal.expects(:trap).with { |arg,block| arg == :INT } @puppetd.preinit end it "should init client to true" do @puppetd.preinit @puppetd.options[:client].should be_true end it "should init fqdn to nil" do @puppetd.preinit @puppetd.options[:fqdn].should be_nil end it "should init serve to []" do @puppetd.preinit @puppetd.options[:serve].should == [] end it "should use MD5 as default digest algorithm" do @puppetd.preinit @puppetd.options[:digest].should == :MD5 end it "should not fingerprint by default" do @puppetd.preinit @puppetd.options[:fingerprint].should be_false end end describe "when handling options" do before do @puppetd.command_line.stubs(:args).returns([]) end [:centrallogging, :disable, :enable, :debug, :fqdn, :test, :verbose, :digest].each do |option| it "should declare handle_#{option} method" do @puppetd.should respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @puppetd.options.expects(:[]=).with(option, 'arg') @puppetd.send("handle_#{option}".to_sym, 'arg') end end - it "should set an existing handler on server" do - Puppet::Network::Handler.stubs(:handler).with("handler").returns(true) - - @puppetd.handle_serve("handler") - @puppetd.options[:serve].should == [ :handler ] - end - it "should set client to false with --no-client" do @puppetd.handle_no_client(nil) @puppetd.options[:client].should be_false end it "should set waitforcert to 0 with --onetime and if --waitforcert wasn't given" do Puppet[:onetime] = true Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(0) @puppetd.setup_host end it "should use supplied waitforcert when --onetime is specified" do Puppet[:onetime] = true @puppetd.handle_waitforcert(60) Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(60) @puppetd.setup_host end it "should use a default value for waitforcert when --onetime and --waitforcert are not specified" do Puppet::SSL::Host.any_instance.expects(:wait_for_cert).with(120) @puppetd.setup_host end it "should set the log destination with --logdest" do @puppetd.options.stubs(:[]=).with { |opt,val| opt == :setdest } Puppet::Log.expects(:newdestination).with("console") @puppetd.handle_logdest("console") end it "should put the setdest options to true" do @puppetd.options.expects(:[]=).with(:setdest,true) @puppetd.handle_logdest("console") end it "should parse the log destination from the command line" do @puppetd.command_line.stubs(:args).returns(%w{--logdest /my/file}) Puppet::Util::Log.expects(:newdestination).with("/my/file") @puppetd.parse_options end it "should store the waitforcert options with --waitforcert" do @puppetd.options.expects(:[]=).with(:waitforcert,42) @puppetd.handle_waitforcert("42") end it "should set args[:Port] with --port" do @puppetd.handle_port("42") @puppetd.args[:Port].should == "42" end end describe "during setup" do before :each do @puppetd.options.stubs(:[]) Puppet.stubs(:info) FileTest.stubs(:exists?).returns(true) Puppet[:libdir] = "/dev/null/lib" Puppet::SSL::Host.stubs(:ca_location=) Puppet::Transaction::Report.indirection.stubs(:terminus_class=) Puppet::Transaction::Report.indirection.stubs(:cache_class=) Puppet::Resource::Catalog.indirection.stubs(:terminus_class=) Puppet::Resource::Catalog.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:terminus_class=) @host = stub_everything 'host' Puppet::SSL::Host.stubs(:new).returns(@host) Puppet.stubs(:settraps) end describe "with --test" do before :each do #Puppet.settings.stubs(:handlearg) @puppetd.options.stubs(:[]=) end it "should call setup_test" do @puppetd.options.stubs(:[]).with(:test).returns(true) @puppetd.expects(:setup_test) @puppetd.setup end it "should set options[:verbose] to true" do @puppetd.options.expects(:[]=).with(:verbose,true) @puppetd.setup_test end it "should set options[:onetime] to true" do Puppet[:onetime] = false @puppetd.setup_test Puppet[:onetime].should == true end it "should set options[:detailed_exitcodes] to true" do @puppetd.options.expects(:[]=).with(:detailed_exitcodes,true) @puppetd.setup_test end end it "should call setup_logs" do @puppetd.expects(:setup_logs) @puppetd.setup end describe "when setting up logs" do before :each do Puppet::Util::Log.stubs(:newdestination) end it "should set log level to debug if --debug was passed" do @puppetd.options.stubs(:[]).with(:debug).returns(true) @puppetd.setup_logs Puppet::Util::Log.level.should == :debug end it "should set log level to info if --verbose was passed" do @puppetd.options.stubs(:[]).with(:verbose).returns(true) @puppetd.setup_logs Puppet::Util::Log.level.should == :info end [:verbose, :debug].each do |level| it "should set console as the log destination with level #{level}" do @puppetd.options.stubs(:[]).with(level).returns(true) Puppet::Util::Log.expects(:newdestination).with(:console) @puppetd.setup_logs end end it "should set syslog as the log destination if no --logdest" do @puppetd.options.stubs(:[]).with(:setdest).returns(false) Puppet::Util::Log.expects(:newdestination).with(:syslog) @puppetd.setup_logs end end it "should print puppet config if asked to in Puppet config" do Puppet[:configprint] = "pluginsync" Puppet.settings.expects(:print_configs).returns true expect { @puppetd.setup }.to exit_with 0 end it "should exit after printing puppet config if asked to in Puppet config" do Puppet[:modulepath] = '/my/path' Puppet[:configprint] = "modulepath" Puppet::Util::Settings.any_instance.expects(:puts).with('/my/path') expect { @puppetd.setup }.to exit_with 0 end it "should set a central log destination with --centrallogs" do @puppetd.options.stubs(:[]).with(:centrallogs).returns(true) Puppet[:server] = "puppet.reductivelabs.com" Puppet::Util::Log.stubs(:newdestination).with(:syslog) Puppet::Util::Log.expects(:newdestination).with("puppet.reductivelabs.com") @puppetd.setup end it "should use :main, :puppetd, and :ssl" do Puppet.settings.expects(:use).with(:main, :agent, :ssl) @puppetd.setup end it "should install a remote ca location" do Puppet::SSL::Host.expects(:ca_location=).with(:remote) @puppetd.setup end it "should install a none ca location in fingerprint mode" do @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) Puppet::SSL::Host.expects(:ca_location=).with(:none) @puppetd.setup end it "should tell the report handler to use REST" do Puppet::Transaction::Report.indirection.expects(:terminus_class=).with(:rest) @puppetd.setup end it "should tell the report handler to cache locally as yaml" do Puppet::Transaction::Report.indirection.expects(:cache_class=).with(:yaml) @puppetd.setup end it "should change the catalog_terminus setting to 'rest'" do Puppet[:catalog_terminus] = :foo @puppetd.setup Puppet[:catalog_terminus].should == :rest end it "should tell the catalog handler to use cache" do Puppet::Resource::Catalog.indirection.expects(:cache_class=).with(:yaml) @puppetd.setup end it "should change the facts_terminus setting to 'facter'" do Puppet[:facts_terminus] = :foo @puppetd.setup Puppet[:facts_terminus].should == :facter end it "should create an agent" do Puppet::Agent.stubs(:new).with(Puppet::Configurer) @puppetd.setup end [:enable, :disable].each do |action| it "should delegate to enable_disable_client if we #{action} the agent" do @puppetd.options.stubs(:[]).with(action).returns(true) @puppetd.expects(:enable_disable_client).with(@agent) @puppetd.setup end end describe "when enabling or disabling agent" do [:enable, :disable].each do |action| it "should call client.#{action}" do @puppetd.options.stubs(:[]).with(action).returns(true) @agent.expects(action) expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 end end it "should finally exit" do expect { @puppetd.enable_disable_client(@agent) }.to exit_with 0 end end it "should inform the daemon about our agent if :client is set to 'true'" do @puppetd.options.expects(:[]).with(:client).returns true @daemon.expects(:agent=).with(@agent) @puppetd.setup end it "should not inform the daemon about our agent if :client is set to 'false'" do @puppetd.options[:client] = false @daemon.expects(:agent=).never @puppetd.setup end it "should daemonize if needed" do Puppet.features.stubs(:microsoft_windows?).returns false Puppet[:daemonize] = true @daemon.expects(:daemonize) @puppetd.setup end it "should wait for a certificate" do @puppetd.options.stubs(:[]).with(:waitforcert).returns(123) @host.expects(:wait_for_cert).with(123) @puppetd.setup end it "should not wait for a certificate in fingerprint mode" do @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) @puppetd.options.stubs(:[]).with(:waitforcert).returns(123) @host.expects(:wait_for_cert).never @puppetd.setup end it "should setup listen if told to and not onetime" do Puppet[:listen] = true @puppetd.options.stubs(:[]).with(:onetime).returns(false) @puppetd.expects(:setup_listen) @puppetd.setup end describe "when setting up listen" do before :each do Puppet[:authconfig] = 'auth' FileTest.stubs(:exists?).with('auth').returns(true) File.stubs(:exist?).returns(true) @puppetd.options.stubs(:[]).with(:serve).returns([]) @server = stub_everything 'server' Puppet::Network::Server.stubs(:new).returns(@server) end it "should exit if no authorization file" do Puppet.stubs(:err) FileTest.stubs(:exists?).with(Puppet[:rest_authconfig]).returns(false) expect { @puppetd.setup_listen }.to exit_with 14 end - it "should create a server to listen on at least the Runner handler" do - Puppet::Network::Server.expects(:new).with { |args| args[:xmlrpc_handlers] == [:Runner] } - - @puppetd.setup_listen - end - - it "should create a server to listen for specific handlers" do - @puppetd.options.stubs(:[]).with(:serve).returns([:handler]) - Puppet::Network::Server.expects(:new).with { |args| args[:xmlrpc_handlers] == [:handler] } - - @puppetd.setup_listen - end - it "should use puppet default port" do Puppet[:puppetport] = 32768 Puppet::Network::Server.expects(:new).with { |args| args[:port] == 32768 } @puppetd.setup_listen end end describe "when setting up for fingerprint" do before(:each) do @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) end it "should not setup as an agent" do @puppetd.expects(:setup_agent).never @puppetd.setup end it "should not create an agent" do Puppet::Agent.stubs(:new).with(Puppet::Configurer).never @puppetd.setup end it "should not daemonize" do @daemon.expects(:daemonize).never @puppetd.setup end it "should setup our certificate host" do @puppetd.expects(:setup_host) @puppetd.setup end end end describe "when running" do before :each do @puppetd.agent = @agent @puppetd.daemon = @daemon @puppetd.options.stubs(:[]).with(:fingerprint).returns(false) end it "should dispatch to fingerprint if --fingerprint is used" do @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) @puppetd.stubs(:fingerprint) @puppetd.run_command end it "should dispatch to onetime if --onetime is used" do @puppetd.options.stubs(:[]).with(:onetime).returns(true) @puppetd.stubs(:onetime) @puppetd.run_command end it "should dispatch to main if --onetime and --fingerprint are not used" do @puppetd.options.stubs(:[]).with(:onetime).returns(false) @puppetd.stubs(:main) @puppetd.run_command end describe "with --onetime" do before :each do @agent.stubs(:run).returns(:report) @puppetd.options.stubs(:[]).with(:client).returns(:client) @puppetd.options.stubs(:[]).with(:detailed_exitcodes).returns(false) Puppet.stubs(:newservice) end it "should exit if no defined --client" do $stderr.stubs(:puts) @puppetd.options.stubs(:[]).with(:client).returns(nil) expect { @puppetd.onetime }.to exit_with 43 end it "should setup traps" do @daemon.expects(:set_signal_traps) expect { @puppetd.onetime }.to exit_with 0 end it "should let the agent run" do @agent.expects(:run).returns(:report) expect { @puppetd.onetime }.to exit_with 0 end it "should finish by exiting with 0 error code" do expect { @puppetd.onetime }.to exit_with 0 end describe "and --detailed-exitcodes" do before :each do @puppetd.options.stubs(:[]).with(:detailed_exitcodes).returns(true) end it "should exit with report's computed exit status" do Puppet[:noop] = false report = stub 'report', :exit_status => 666 @agent.stubs(:run).returns(report) expect { @puppetd.onetime }.to exit_with 666 end it "should exit with the report's computer exit status, even if --noop is set." do Puppet[:noop] = true report = stub 'report', :exit_status => 666 @agent.stubs(:run).returns(report) expect { @puppetd.onetime }.to exit_with 666 end end end describe "with --fingerprint" do before :each do @cert = stub_everything 'cert' @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) @puppetd.options.stubs(:[]).with(:digest).returns(:MD5) @host = stub_everything 'host' @puppetd.stubs(:host).returns(@host) end it "should fingerprint the certificate if it exists" do @host.expects(:certificate).returns(@cert) @cert.expects(:fingerprint).with(:MD5).returns "fingerprint" @puppetd.fingerprint end it "should fingerprint the certificate request if no certificate have been signed" do @host.expects(:certificate).returns(nil) @host.expects(:certificate_request).returns(@cert) @cert.expects(:fingerprint).with(:MD5).returns "fingerprint" @puppetd.fingerprint end it "should display the fingerprint" do @host.stubs(:certificate).returns(@cert) @cert.stubs(:fingerprint).with(:MD5).returns("DIGEST") @puppetd.expects(:puts).with "DIGEST" @puppetd.fingerprint end end describe "without --onetime and --fingerprint" do before :each do Puppet.stubs(:notice) @puppetd.options.stubs(:[]).with(:client) end it "should start our daemon" do @daemon.expects(:start) @puppetd.main end end end end diff --git a/spec/unit/application/master_spec.rb b/spec/unit/application/master_spec.rb index c6df48f4d..7a70bb9ee 100755 --- a/spec/unit/application/master_spec.rb +++ b/spec/unit/application/master_spec.rb @@ -1,400 +1,376 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/application/master' require 'puppet/daemon' require 'puppet/network/server' describe Puppet::Application::Master, :unless => Puppet.features.microsoft_windows? do before :each do @master = Puppet::Application[:master] @daemon = stub_everything 'daemon' Puppet::Daemon.stubs(:new).returns(@daemon) Puppet::Util::Log.stubs(:newdestination) Puppet::Node.indirection.stubs(:terminus_class=) Puppet::Node.indirection.stubs(:cache_class=) Puppet::Node::Facts.indirection.stubs(:terminus_class=) Puppet::Node::Facts.indirection.stubs(:cache_class=) Puppet::Transaction::Report.indirection.stubs(:terminus_class=) Puppet::Resource::Catalog.indirection.stubs(:terminus_class=) Puppet::SSL::Host.stubs(:ca_location=) end it "should operate in master run_mode" do @master.class.run_mode.name.should equal(:master) end it "should ask Puppet::Application to parse Puppet configuration file" do @master.should_parse_config?.should be_true end it "should declare a main command" do @master.should respond_to(:main) end it "should declare a compile command" do @master.should respond_to(:compile) end it "should declare a preinit block" do @master.should respond_to(:preinit) end describe "during preinit" do before :each do @master.stubs(:trap) end it "should catch INT" do @master.stubs(:trap).with { |arg,block| arg == :INT } @master.preinit end it "should create a Puppet Daemon" do Puppet::Daemon.expects(:new).returns(@daemon) @master.preinit end it "should give ARGV to the Daemon" do argv = stub 'argv' ARGV.stubs(:dup).returns(argv) @daemon.expects(:argv=).with(argv) @master.preinit end end [:debug,:verbose].each do |option| it "should declare handle_#{option} method" do @master.should respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @master.options.expects(:[]=).with(option, 'arg') @master.send("handle_#{option}".to_sym, 'arg') end end describe "when applying options" do before do @master.command_line.stubs(:args).returns([]) end it "should set the log destination with --logdest" do Puppet::Log.expects(:newdestination).with("console") @master.handle_logdest("console") end it "should put the setdest options to true" do @master.options.expects(:[]=).with(:setdest,true) @master.handle_logdest("console") end it "should parse the log destination from ARGV" do @master.command_line.stubs(:args).returns(%w{--logdest /my/file}) Puppet::Util::Log.expects(:newdestination).with("/my/file") @master.parse_options end end describe "during setup" do before :each do Puppet::Log.stubs(:newdestination) Puppet.stubs(:settraps) Puppet::SSL::CertificateAuthority.stubs(:instance) Puppet::SSL::CertificateAuthority.stubs(:ca?) Puppet.settings.stubs(:use) @master.options.stubs(:[]).with(any_parameters) end it "should abort stating that the master is not supported on Windows" do Puppet.features.stubs(:microsoft_windows?).returns(true) expect { @master.setup }.to raise_error(Puppet::Error, /Puppet master is not supported on Microsoft Windows/) end it "should set log level to debug if --debug was passed" do @master.options.stubs(:[]).with(:debug).returns(true) @master.setup Puppet::Log.level.should == :debug end it "should set log level to info if --verbose was passed" do @master.options.stubs(:[]).with(:verbose).returns(true) @master.setup Puppet::Log.level.should == :info end it "should set console as the log destination if no --logdest and --daemonize" do @master.stubs(:[]).with(:daemonize).returns(:false) Puppet::Log.expects(:newdestination).with(:syslog) @master.setup end it "should set syslog as the log destination if no --logdest and not --daemonize" do Puppet::Log.expects(:newdestination).with(:syslog) @master.setup end it "should set syslog as the log destination if --rack" do @master.options.stubs(:[]).with(:rack).returns(:true) Puppet::Log.expects(:newdestination).with(:syslog) @master.setup end it "should print puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) Puppet.settings.expects(:print_configs).returns(true) expect { @master.setup }.to exit_with 0 end it "should exit after printing puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) expect { @master.setup }.to exit_with 1 end it "should tell Puppet.settings to use :main,:ssl,:master and :metrics category" do Puppet.settings.expects(:use).with(:main,:master,:ssl,:metrics) @master.setup end it "should cache class in yaml" do Puppet::Node.indirection.expects(:cache_class=).with(:yaml) @master.setup end describe "with no ca" do it "should set the ca_location to none" do Puppet::SSL::Host.expects(:ca_location=).with(:none) @master.setup end end describe "with a ca configured" do before :each do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true) end it "should set the ca_location to local" do Puppet::SSL::Host.expects(:ca_location=).with(:local) @master.setup end it "should tell Puppet.settings to use :ca category" do Puppet.settings.expects(:use).with(:ca) @master.setup end it "should instantiate the CertificateAuthority singleton" do Puppet::SSL::CertificateAuthority.expects(:instance) @master.setup end end end describe "when running" do before do @master.preinit end it "should dispatch to compile if called with --compile" do @master.options[:node] = "foo" @master.expects(:compile) @master.run_command end it "should dispatch to main otherwise" do @master.options[:node] = nil @master.expects(:main) @master.run_command end describe "the compile command" do before do Puppet.stubs(:[]).with(:environment) Puppet.stubs(:[]).with(:manifest).returns("site.pp") Puppet.stubs(:err) @master.stubs(:jj) Puppet.features.stubs(:pson?).returns true end it "should fail if pson isn't available" do Puppet.features.expects(:pson?).returns false lambda { @master.compile }.should raise_error end it "should compile a catalog for the specified node" do @master.options[:node] = "foo" Puppet::Resource::Catalog.indirection.expects(:find).with("foo").returns Puppet::Resource::Catalog.new $stdout.stubs(:puts) expect { @master.compile }.to exit_with 0 end it "should convert the catalog to a pure-resource catalog and use 'jj' to pretty-print the catalog" do catalog = Puppet::Resource::Catalog.new Puppet::Resource::Catalog.indirection.expects(:find).returns catalog catalog.expects(:to_resource).returns("rescat") @master.options[:node] = "foo" @master.expects(:jj).with("rescat") expect { @master.compile }.to exit_with 0 end it "should exit with error code 30 if no catalog can be found" do @master.options[:node] = "foo" Puppet::Resource::Catalog.indirection.expects(:find).returns nil $stderr.expects(:puts) expect { @master.compile }.to exit_with 30 end it "should exit with error code 30 if there's a failure" do @master.options[:node] = "foo" Puppet::Resource::Catalog.indirection.expects(:find).raises ArgumentError $stderr.expects(:puts) expect { @master.compile }.to exit_with 30 end end describe "the main command" do before :each do @master.preinit @server = stub_everything 'server' Puppet::Network::Server.stubs(:new).returns(@server) @app = stub_everything 'app' Puppet::SSL::Host.stubs(:localhost) Puppet::SSL::CertificateAuthority.stubs(:ca?) Process.stubs(:uid).returns(1000) Puppet.stubs(:service) Puppet.stubs(:[]) Puppet.stubs(:notice) Puppet.stubs(:start) end it "should create a Server" do Puppet::Network::Server.expects(:new) @master.main end it "should give the server to the daemon" do @daemon.expects(:server=).with(@server) @master.main end - it "should create the server with the right XMLRPC handlers" do - Puppet::Network::Server.expects(:new).with { |args| args[:xmlrpc_handlers] == [:Status, :FileServer, :Master, :Report, :Filebucket]} - - @master.main - end - - it "should create the server with a :ca xmlrpc handler if needed" do - Puppet.stubs(:[]).with(:ca).returns(true) - Puppet::Network::Server.expects(:new).with { |args| args[:xmlrpc_handlers].include?(:CA) } - - @master.main - end - it "should generate a SSL cert for localhost" do Puppet::SSL::Host.expects(:localhost) @master.main end it "should make sure to *only* hit the CA for data" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true) Puppet::SSL::Host.expects(:ca_location=).with(:only) @master.main end it "should drop privileges if running as root" do Puppet.features.stubs(:root?).returns true Puppet::Util.expects(:chuser) @master.main end it "should daemonize if needed" do Puppet.stubs(:[]).with(:daemonize).returns(true) @daemon.expects(:daemonize) @master.main end it "should start the service" do @daemon.expects(:start) @master.main end describe "with --rack", :if => Puppet.features.rack? do before do require 'puppet/network/http/rack' Puppet::Network::HTTP::Rack.stubs(:new).returns(@app) end - it "it should create the app with REST and XMLRPC support" do - @master.options.stubs(:[]).with(:rack).returns(:true) - - Puppet::Network::HTTP::Rack.expects(:new).with { |args| - args[:xmlrpc_handlers] == [:Status, :FileServer, :Master, :Report, :Filebucket] and - args[:protocols] == [:rest, :xmlrpc] - } - - @master.main - end - it "it should not start a daemon" do @master.options.stubs(:[]).with(:rack).returns(:true) @daemon.expects(:start).never @master.main end it "it should return the app" do @master.options.stubs(:[]).with(:rack).returns(:true) app = @master.main app.should equal(@app) end end end end end diff --git a/spec/unit/file_serving/metadata_spec.rb b/spec/unit/file_serving/metadata_spec.rb index 3842b05bc..c0bd6f083 100755 --- a/spec/unit/file_serving/metadata_spec.rb +++ b/spec/unit/file_serving/metadata_spec.rb @@ -1,356 +1,330 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/file_serving/metadata' describe Puppet::FileServing::Metadata do it "should should be a subclass of Base" do Puppet::FileServing::Metadata.superclass.should equal(Puppet::FileServing::Base) end it "should indirect file_metadata" do Puppet::FileServing::Metadata.indirection.name.should == :file_metadata end it "should should include the IndirectionHooks module in its indirection" do Puppet::FileServing::Metadata.indirection.singleton_class.included_modules.should include(Puppet::FileServing::IndirectionHooks) end it "should have a method that triggers attribute collection" do Puppet::FileServing::Metadata.new("/foo/bar").should respond_to(:collect) end it "should support pson serialization" do Puppet::FileServing::Metadata.new("/foo/bar").should respond_to(:to_pson) end it "should support to_pson_data_hash" do Puppet::FileServing::Metadata.new("/foo/bar").should respond_to(:to_pson_data_hash) end it "should support pson deserialization" do Puppet::FileServing::Metadata.should respond_to(:from_pson) end describe "when serializing" do before do @metadata = Puppet::FileServing::Metadata.new("/foo/bar") end it "should perform pson serialization by calling to_pson on it's pson_data_hash" do pdh = mock "data hash" pdh_as_pson = mock "data as pson" @metadata.expects(:to_pson_data_hash).returns pdh pdh.expects(:to_pson).returns pdh_as_pson @metadata.to_pson.should == pdh_as_pson end it "should serialize as FileMetadata" do @metadata.to_pson_data_hash['document_type'].should == "FileMetadata" end it "the data should include the path, relative_path, links, owner, group, mode, checksum, type, and destination" do @metadata.to_pson_data_hash['data'].keys.sort.should == %w{ path relative_path links owner group mode checksum type destination }.sort end it "should pass the path in the hash verbatum" do @metadata.to_pson_data_hash['data']['path'] == @metadata.path end it "should pass the relative_path in the hash verbatum" do @metadata.to_pson_data_hash['data']['relative_path'] == @metadata.relative_path end it "should pass the links in the hash verbatum" do @metadata.to_pson_data_hash['data']['links'] == @metadata.links end it "should pass the path owner in the hash verbatum" do @metadata.to_pson_data_hash['data']['owner'] == @metadata.owner end it "should pass the group in the hash verbatum" do @metadata.to_pson_data_hash['data']['group'] == @metadata.group end it "should pass the mode in the hash verbatum" do @metadata.to_pson_data_hash['data']['mode'] == @metadata.mode end it "should pass the ftype in the hash verbatum as the 'type'" do @metadata.to_pson_data_hash['data']['type'] == @metadata.ftype end it "should pass the destination verbatum" do @metadata.to_pson_data_hash['data']['destination'] == @metadata.destination end it "should pass the checksum in the hash as a nested hash" do @metadata.to_pson_data_hash['data']['checksum'].should be_is_a(Hash) end it "should pass the checksum_type in the hash verbatum as the checksum's type" do @metadata.to_pson_data_hash['data']['checksum']['type'] == @metadata.checksum_type end it "should pass the checksum in the hash verbatum as the checksum's value" do @metadata.to_pson_data_hash['data']['checksum']['value'] == @metadata.checksum end end end describe Puppet::FileServing::Metadata do include PuppetSpec::Files shared_examples_for "metadata collector" do let(:metadata) do data = described_class.new(path) data.collect data end describe "when collecting attributes" do describe "when managing files" do let(:path) { tmpfile('file_serving_metadata') } before :each do FileUtils.touch(path) end - it "should be able to produce xmlrpc-style attribute information" do - metadata.should respond_to(:attributes_with_tabs) - end - it "should set the owner to the file's current owner" do metadata.owner.should == owner end it "should set the group to the file's current group" do metadata.group.should == group end it "should set the mode to the file's masked mode" do set_mode(33261, path) metadata.mode.should == 0755 end describe "#checksum" do let(:checksum) { Digest::MD5.hexdigest("some content\n") } before :each do File.open(path, "wb") {|f| f.print("some content\n")} end it "should default to a checksum of type MD5 with the file's current checksum" do metadata.checksum.should == "{md5}#{checksum}" end it "should give a mtime checksum when checksum_type is set" do time = Time.now metadata.checksum_type = "mtime" metadata.expects(:mtime_file).returns(@time) metadata.collect metadata.checksum.should == "{mtime}#{@time}" end - - it "should produce tab-separated mode, type, owner, group, and checksum for xmlrpc" do - set_mode(0755, path) - - metadata.attributes_with_tabs.should == "#{0755.to_s}\tfile\t#{owner}\t#{group}\t{md5}#{checksum}" - end end end describe "when managing directories" do let(:path) { tmpdir('file_serving_metadata_dir') } let(:time) { Time.now } before :each do metadata.expects(:ctime_file).returns(time) end it "should only use checksums of type 'ctime' for directories" do metadata.collect metadata.checksum.should == "{ctime}#{time}" end it "should only use checksums of type 'ctime' for directories even if checksum_type set" do metadata.checksum_type = "mtime" metadata.expects(:mtime_file).never metadata.collect metadata.checksum.should == "{ctime}#{time}" end - - it "should produce tab-separated mode, type, owner, group, and checksum for xmlrpc" do - set_mode(0755, path) - metadata.collect - - metadata.attributes_with_tabs.should == "#{0755.to_s}\tdirectory\t#{owner}\t#{group}\t{ctime}#{time.to_s}" - end end describe "when managing links", :unless => Puppet.features.microsoft_windows? do # 'path' is a link that points to 'target' let(:path) { tmpfile('file_serving_metadata_link') } let(:target) { tmpfile('file_serving_metadata_target') } let(:checksum) { Digest::MD5.hexdigest("some content\n") } let(:fmode) { File.lstat(path).mode & 0777 } before :each do File.open(target, "wb") {|f| f.print("some content\n")} set_mode(0644, target) FileUtils.symlink(target, path) end it "should read links instead of returning their checksums" do metadata.destination.should == target end - - pending "should produce tab-separated mode, type, owner, group, and destination for xmlrpc" do - # "We'd like this to be true, but we need to always collect the checksum because in the server/client/server round trip we lose the distintion between manage and follow." - metadata.attributes_with_tabs.should == "#{0755}\tlink\t#{owner}\t#{group}\t#{target}" - end - - it "should produce tab-separated mode, type, owner, group, checksum, and destination for xmlrpc" do - metadata.attributes_with_tabs.should == "#{fmode}\tlink\t#{owner}\t#{group}\t{md5}eb9c2bf0eb63f3a7bc0ea37ef18aeba5\t#{target}" - end end end describe Puppet::FileServing::Metadata, " when finding the file to use for setting attributes" do let(:path) { tmpfile('file_serving_metadata_find_file') } before :each do File.open(path, "wb") {|f| f.print("some content\n")} set_mode(0755, path) end it "should accept a base path to which the file should be relative" do dir = tmpdir('metadata_dir') metadata = described_class.new(dir) metadata.relative_path = 'relative_path' FileUtils.touch(metadata.full_path) metadata.collect end it "should use the set base path if one is not provided" do metadata.collect end it "should raise an exception if the file does not exist" do File.delete(path) proc { metadata.collect}.should raise_error(Errno::ENOENT) end end end describe "on POSIX systems", :if => Puppet.features.posix? do let(:owner) {10} let(:group) {20} before :each do File::Stat.any_instance.stubs(:uid).returns owner File::Stat.any_instance.stubs(:gid).returns group end it_should_behave_like "metadata collector" def set_mode(mode, path) File.chmod(mode, path) end end describe "on Windows systems", :if => Puppet.features.microsoft_windows? do let(:owner) {'S-1-1-50'} let(:group) {'S-1-1-51'} before :each do require 'puppet/util/windows/security' Puppet::Util::Windows::Security.stubs(:get_owner).returns owner Puppet::Util::Windows::Security.stubs(:get_group).returns group end it_should_behave_like "metadata collector" describe "if ACL metadata cannot be collected" do let(:path) { tmpdir('file_serving_metadata_acl') } let(:metadata) do data = described_class.new(path) data.collect data end it "should default owner" do Puppet::Util::Windows::Security.stubs(:get_owner).returns nil metadata.owner.should == 'S-1-5-32-544' end it "should default group" do Puppet::Util::Windows::Security.stubs(:get_group).returns nil metadata.group.should == 'S-1-0-0' end it "should default mode" do Puppet::Util::Windows::Security.stubs(:get_mode).returns nil metadata.mode.should == 0644 end end def set_mode(mode, path) Puppet::Util::Windows::Security.set_mode(mode, path) end end end describe Puppet::FileServing::Metadata, " when pointing to a link", :unless => Puppet.features.microsoft_windows? do describe "when links are managed" do before do @file = Puppet::FileServing::Metadata.new("/base/path/my/file", :links => :manage) File.expects(:lstat).with("/base/path/my/file").returns stub("stat", :uid => 1, :gid => 2, :ftype => "link", :mode => 0755) File.expects(:readlink).with("/base/path/my/file").returns "/some/other/path" @checksum = Digest::MD5.hexdigest("some content\n") # Remove these when :managed links are no longer checksumed. @file.stubs(:md5_file).returns(@checksum) # end it "should store the destination of the link in :destination if links are :manage" do @file.collect @file.destination.should == "/some/other/path" end pending "should not collect the checksum if links are :manage" do # We'd like this to be true, but we need to always collect the checksum because in the server/client/server round trip we lose the distintion between manage and follow. @file.collect @file.checksum.should be_nil end it "should collect the checksum if links are :manage" do # see pending note above @file.collect @file.checksum.should == "{md5}#{@checksum}" end end describe "when links are followed" do before do @file = Puppet::FileServing::Metadata.new("/base/path/my/file", :links => :follow) File.expects(:stat).with("/base/path/my/file").returns stub("stat", :uid => 1, :gid => 2, :ftype => "file", :mode => 0755) File.expects(:readlink).with("/base/path/my/file").never @checksum = Digest::MD5.hexdigest("some content\n") @file.stubs(:md5_file).returns(@checksum) end it "should not store the destination of the link in :destination if links are :follow" do @file.collect @file.destination.should be_nil end it "should collect the checksum if links are :follow" do @file.collect @file.checksum.should == "{md5}#{@checksum}" end end end diff --git a/spec/unit/module_spec.rb b/spec/unit/module_spec.rb index a0f64c6d3..cf7208443 100755 --- a/spec/unit/module_spec.rb +++ b/spec/unit/module_spec.rb @@ -1,598 +1,590 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet_spec/files' describe Puppet::Module do include PuppetSpec::Files before do # This is necessary because of the extra checks we have for the deprecated # 'plugins' directory FileTest.stubs(:exist?).returns false end it "should have a class method that returns a named module from a given environment" do env = mock 'module' env.expects(:module).with("mymod").returns "yep" Puppet::Node::Environment.expects(:new).with("myenv").returns env Puppet::Module.find("mymod", "myenv").should == "yep" end it "should return nil if asked for a named module that doesn't exist" do env = mock 'module' env.expects(:module).with("mymod").returns nil Puppet::Node::Environment.expects(:new).with("myenv").returns env Puppet::Module.find("mymod", "myenv").should be_nil end it "should support a 'version' attribute" do mod = Puppet::Module.new("mymod") mod.version = 1.09 mod.version.should == 1.09 end it "should support a 'source' attribute" do mod = Puppet::Module.new("mymod") mod.source = "http://foo/bar" mod.source.should == "http://foo/bar" end it "should support a 'project_page' attribute" do mod = Puppet::Module.new("mymod") mod.project_page = "http://foo/bar" mod.project_page.should == "http://foo/bar" end it "should support an 'author' attribute" do mod = Puppet::Module.new("mymod") mod.author = "Luke Kanies " mod.author.should == "Luke Kanies " end it "should support a 'license' attribute" do mod = Puppet::Module.new("mymod") mod.license = "GPL2" mod.license.should == "GPL2" end it "should support a 'summary' attribute" do mod = Puppet::Module.new("mymod") mod.summary = "GPL2" mod.summary.should == "GPL2" end it "should support a 'description' attribute" do mod = Puppet::Module.new("mymod") mod.description = "GPL2" mod.description.should == "GPL2" end it "should support specifying a compatible puppet version" do mod = Puppet::Module.new("mymod") mod.puppetversion = "0.25" mod.puppetversion.should == "0.25" end it "should validate that the puppet version is compatible" do mod = Puppet::Module.new("mymod") mod.puppetversion = "0.25" Puppet.expects(:version).returns "0.25" mod.validate_puppet_version end it "should fail if the specified puppet version is not compatible" do mod = Puppet::Module.new("mymod") mod.puppetversion = "0.25" Puppet.stubs(:version).returns "0.24" lambda { mod.validate_puppet_version }.should raise_error(Puppet::Module::IncompatibleModule) end describe "when specifying required modules" do it "should support specifying a required module" do mod = Puppet::Module.new("mymod") mod.requires "foobar" end it "should support specifying multiple required modules" do mod = Puppet::Module.new("mymod") mod.requires "foobar" mod.requires "baz" end it "should support specifying a required module and version" do mod = Puppet::Module.new("mymod") mod.requires "foobar", 1.0 end it "should fail when required modules are missing" do mod = Puppet::Module.new("mymod") mod.requires "foobar" mod.environment.expects(:module).with("foobar").returns nil lambda { mod.validate_dependencies }.should raise_error(Puppet::Module::MissingModule) end it "should fail when required modules are present but of the wrong version" do mod = Puppet::Module.new("mymod") mod.requires "foobar", 1.0 foobar = Puppet::Module.new("foobar") foobar.version = 2.0 mod.environment.expects(:module).with("foobar").returns foobar lambda { mod.validate_dependencies }.should raise_error(Puppet::Module::IncompatibleModule) end it "should have valid dependencies when no dependencies have been specified" do mod = Puppet::Module.new("mymod") lambda { mod.validate_dependencies }.should_not raise_error end it "should fail when some dependencies are present but others aren't" do mod = Puppet::Module.new("mymod") mod.requires "foobar" mod.requires "baz" mod.environment.expects(:module).with("foobar").returns Puppet::Module.new("foobar") mod.environment.expects(:module).with("baz").returns nil lambda { mod.validate_dependencies }.should raise_error(Puppet::Module::MissingModule) end it "should have valid dependencies when all dependencies are met" do mod = Puppet::Module.new("mymod") mod.requires "foobar", 1.0 mod.requires "baz" foobar = Puppet::Module.new("foobar") foobar.version = 1.0 baz = Puppet::Module.new("baz") mod.environment.expects(:module).with("foobar").returns foobar mod.environment.expects(:module).with("baz").returns baz lambda { mod.validate_dependencies }.should_not raise_error end it "should validate its dependendencies on initialization" do Puppet::Module.any_instance.expects(:validate_dependencies) Puppet::Module.new("mymod") end end describe "when managing supported platforms" do it "should support specifying a supported platform" do mod = Puppet::Module.new("mymod") mod.supports "solaris" end it "should support specifying a supported platform and version" do mod = Puppet::Module.new("mymod") mod.supports "solaris", 1.0 end it "should fail when not running on a supported platform" do pending "Not sure how to send client platform to the module" mod = Puppet::Module.new("mymod") Facter.expects(:value).with("operatingsystem").returns "Solaris" mod.supports "hpux" lambda { mod.validate_supported_platform }.should raise_error(Puppet::Module::UnsupportedPlatform) end it "should fail when supported platforms are present but of the wrong version" do pending "Not sure how to send client platform to the module" mod = Puppet::Module.new("mymod") Facter.expects(:value).with("operatingsystem").returns "Solaris" Facter.expects(:value).with("operatingsystemrelease").returns 2.0 mod.supports "Solaris", 1.0 lambda { mod.validate_supported_platform }.should raise_error(Puppet::Module::IncompatiblePlatform) end it "should be considered supported when no supported platforms have been specified" do pending "Not sure how to send client platform to the module" mod = Puppet::Module.new("mymod") lambda { mod.validate_supported_platform }.should_not raise_error end it "should be considered supported when running on a supported platform" do pending "Not sure how to send client platform to the module" mod = Puppet::Module.new("mymod") Facter.expects(:value).with("operatingsystem").returns "Solaris" Facter.expects(:value).with("operatingsystemrelease").returns 2.0 mod.supports "Solaris", 1.0 lambda { mod.validate_supported_platform }.should raise_error(Puppet::Module::IncompatiblePlatform) end it "should be considered supported when running on any of multiple supported platforms" do pending "Not sure how to send client platform to the module" end it "should validate its platform support on initialization" do pending "Not sure how to send client platform to the module" end end it "should return nil if asked for a module whose name is 'nil'" do Puppet::Module.find(nil, "myenv").should be_nil end it "should provide support for logging" do Puppet::Module.ancestors.should be_include(Puppet::Util::Logging) end it "should be able to be converted to a string" do Puppet::Module.new("foo").to_s.should == "Module foo" end it "should add the path to its string form if the module is found" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns "/a" mod.to_s.should == "Module foo(/a)" end it "should fail if its name is not alphanumeric" do lambda { Puppet::Module.new(".something") }.should raise_error(Puppet::Module::InvalidName) end it "should require a name at initialization" do lambda { Puppet::Module.new }.should raise_error(ArgumentError) end it "should convert an environment name into an Environment instance" do Puppet::Module.new("foo", "prod").environment.should be_instance_of(Puppet::Node::Environment) end it "should accept an environment at initialization" do Puppet::Module.new("foo", :prod).environment.name.should == :prod end it "should use the default environment if none is provided" do env = Puppet::Node::Environment.new Puppet::Module.new("foo").environment.should equal(env) end it "should use any provided Environment instance" do env = Puppet::Node::Environment.new Puppet::Module.new("foo", env).environment.should equal(env) end it "should return the path to the first found instance in its environment's module paths as its path" do dir = tmpdir("deep_path") first = File.join(dir, "first") second = File.join(dir, "second") FileUtils.mkdir_p(first) FileUtils.mkdir_p(second) Puppet[:modulepath] = "#{first}#{File::PATH_SEPARATOR}#{second}" modpath = File.join(first, "foo") FileUtils.mkdir_p(modpath) # Make a second one, which we shouldn't find FileUtils.mkdir_p(File.join(second, "foo")) mod = Puppet::Module.new("foo") mod.path.should == modpath end it "should be able to find itself in a directory other than the first directory in the module path" do dir = tmpdir("deep_path") first = File.join(dir, "first") second = File.join(dir, "second") FileUtils.mkdir_p(first) FileUtils.mkdir_p(second) Puppet[:modulepath] = "#{first}#{File::PATH_SEPARATOR}#{second}" modpath = File.join(second, "foo") FileUtils.mkdir_p(modpath) mod = Puppet::Module.new("foo") mod.should be_exist mod.path.should == modpath end it "should be considered existent if it exists in at least one module path" do mod = Puppet::Module.new("foo") mod.expects(:path).returns "/a/foo" mod.should be_exist end it "should be considered nonexistent if it does not exist in any of the module paths" do mod = Puppet::Module.new("foo") mod.expects(:path).returns nil mod.should_not be_exist end [:plugins, :templates, :files, :manifests].each do |filetype| dirname = filetype == :plugins ? "lib" : filetype.to_s it "should be able to return individual #{filetype}" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns "/a/foo" path = File.join("/a/foo", dirname, "my/file") FileTest.expects(:exist?).with(path).returns true mod.send(filetype.to_s.sub(/s$/, ''), "my/file").should == path end it "should consider #{filetype} to be present if their base directory exists" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns "/a/foo" path = File.join("/a/foo", dirname) FileTest.expects(:exist?).with(path).returns true mod.send(filetype.to_s + "?").should be_true end it "should consider #{filetype} to be absent if their base directory does not exist" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns "/a/foo" path = File.join("/a/foo", dirname) FileTest.expects(:exist?).with(path).returns false mod.send(filetype.to_s + "?").should be_false end it "should consider #{filetype} to be absent if the module base directory does not exist" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns nil mod.send(filetype.to_s + "?").should be_false end it "should return nil if asked to return individual #{filetype} that don't exist" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns "/a/foo" path = File.join("/a/foo", dirname, "my/file") FileTest.expects(:exist?).with(path).returns false mod.send(filetype.to_s.sub(/s$/, ''), "my/file").should be_nil end it "should return nil when asked for individual #{filetype} if the module does not exist" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns nil mod.send(filetype.to_s.sub(/s$/, ''), "my/file").should be_nil end it "should return the base directory if asked for a nil path" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns "/a/foo" base = File.join("/a/foo", dirname) FileTest.expects(:exist?).with(base).returns true mod.send(filetype.to_s.sub(/s$/, ''), nil).should == base end end - %w{plugins files}.each do |filetype| - short = filetype.sub(/s$/, '') - dirname = filetype == "plugins" ? "lib" : filetype.to_s - it "should be able to return the #{short} directory" do - Puppet::Module.new("foo").should respond_to(short + "_directory") - end - - it "should return the path to the #{short} directory" do - mod = Puppet::Module.new("foo") - mod.stubs(:path).returns "/a/foo" + it "should return the path to the plugin directory" do + mod = Puppet::Module.new("foo") + mod.stubs(:path).returns "/a/foo" - mod.send(short + "_directory").should == "/a/foo/#{dirname}" - end + mod.plugin_directory.should == "/a/foo/lib" end it "should throw a warning if plugins are in a 'plugins' directory rather than a 'lib' directory" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns "/a/foo" FileTest.expects(:exist?).with("/a/foo/plugins").returns true mod.plugin_directory.should == "/a/foo/plugins" @logs.first.message.should == "using the deprecated 'plugins' directory for ruby extensions; please move to 'lib'" @logs.first.level.should == :warning end it "should default to 'lib' for the plugins directory" do mod = Puppet::Module.new("foo") mod.stubs(:path).returns "/a/foo" mod.plugin_directory.should == "/a/foo/lib" end end describe Puppet::Module, " when building its search path" do it "should use the current environment's search path if no environment is specified" do env = mock 'env' env.expects(:modulepath).returns "eh" Puppet::Node::Environment.expects(:new).with(nil).returns env Puppet::Module.modulepath.should == "eh" end it "should use the specified environment's search path if an environment is specified" do env = mock 'env' env.expects(:modulepath).returns "eh" Puppet::Node::Environment.expects(:new).with("foo").returns env Puppet::Module.modulepath("foo").should == "eh" end end describe Puppet::Module, "when finding matching manifests" do before do @mod = Puppet::Module.new("mymod") @mod.stubs(:path).returns "/a" @pq_glob_with_extension = "yay/*.xx" @fq_glob_with_extension = "/a/manifests/#{@pq_glob_with_extension}" end it "should return all manifests matching the glob pattern" do Dir.expects(:glob).with(@fq_glob_with_extension).returns(%w{foo bar}) FileTest.stubs(:directory?).returns false @mod.match_manifests(@pq_glob_with_extension).should == %w{foo bar} end it "should not return directories" do Dir.expects(:glob).with(@fq_glob_with_extension).returns(%w{foo bar}) FileTest.expects(:directory?).with("foo").returns false FileTest.expects(:directory?).with("bar").returns true @mod.match_manifests(@pq_glob_with_extension).should == %w{foo} end it "should default to the 'init' file if no glob pattern is specified" do Dir.expects(:glob).with("/a/manifests/init.{pp,rb}").returns(%w{/a/manifests/init.pp}) @mod.match_manifests(nil).should == %w{/a/manifests/init.pp} end it "should return all manifests matching the glob pattern in all existing paths" do Dir.expects(:glob).with(@fq_glob_with_extension).returns(%w{a b}) @mod.match_manifests(@pq_glob_with_extension).should == %w{a b} end it "should match the glob pattern plus '.{pp,rb}' if no extention is specified" do Dir.expects(:glob).with("/a/manifests/yay/foo.{pp,rb}").returns(%w{yay}) @mod.match_manifests("yay/foo").should == %w{yay} end it "should return an empty array if no manifests matched" do Dir.expects(:glob).with(@fq_glob_with_extension).returns([]) @mod.match_manifests(@pq_glob_with_extension).should == [] end end describe Puppet::Module do before do Puppet::Module.any_instance.stubs(:path).returns "/my/mod/path" @module = Puppet::Module.new("foo") end it "should use 'License' in its current path as its metadata file" do @module.license_file.should == "/my/mod/path/License" end it "should return nil as its license file when the module has no path" do Puppet::Module.any_instance.stubs(:path).returns nil Puppet::Module.new("foo").license_file.should be_nil end it "should cache the license file" do Puppet::Module.any_instance.expects(:path).once.returns nil mod = Puppet::Module.new("foo") mod.license_file.should == mod.license_file end it "should use 'metadata.json' in its current path as its metadata file" do @module.metadata_file.should == "/my/mod/path/metadata.json" end it "should return nil as its metadata file when the module has no path" do Puppet::Module.any_instance.stubs(:path).returns nil Puppet::Module.new("foo").metadata_file.should be_nil end it "should cache the metadata file" do Puppet::Module.any_instance.expects(:path).once.returns nil mod = Puppet::Module.new("foo") mod.metadata_file.should == mod.metadata_file end it "should have metadata if it has a metadata file and its data is not empty" do FileTest.expects(:exist?).with(@module.metadata_file).returns true File.stubs(:read).with(@module.metadata_file).returns "{\"foo\" : \"bar\"}" @module.should be_has_metadata end it "should have metadata if it has a metadata file and its data is not empty" do FileTest.expects(:exist?).with(@module.metadata_file).returns true File.stubs(:read).with(@module.metadata_file).returns "{\"foo\" : \"bar\"}" @module.should be_has_metadata end it "should not have metadata if has a metadata file and its data is empty" do FileTest.expects(:exist?).with(@module.metadata_file).returns true File.stubs(:read).with(@module.metadata_file).returns "/* +-----------------------------------------------------------------------+ | | | ==> DO NOT EDIT THIS FILE! <== | | | | You should edit the `Modulefile` and run `puppet-module build` | | to generate the `metadata.json` file for your releases. | | | +-----------------------------------------------------------------------+ */ {}" @module.should_not be_has_metadata end it "should know if it is missing a metadata file" do FileTest.expects(:exist?).with(@module.metadata_file).returns false @module.should_not be_has_metadata end it "should be able to parse its metadata file" do @module.should respond_to(:load_metadata) end it "should parse its metadata file on initialization if it is present" do Puppet::Module.any_instance.expects(:has_metadata?).returns true Puppet::Module.any_instance.expects(:load_metadata) Puppet::Module.new("yay") end describe "when loading the medatada file", :if => Puppet.features.pson? do before do @data = { :license => "GPL2", :author => "luke", :version => "1.0", :source => "http://foo/", :puppetversion => "0.25" } @text = @data.to_pson @module = Puppet::Module.new("foo") @module.stubs(:metadata_file).returns "/my/file" File.stubs(:read).with("/my/file").returns @text end %w{source author version license}.each do |attr| it "should set #{attr} if present in the metadata file" do @module.load_metadata @module.send(attr).should == @data[attr.to_sym] end it "should fail if #{attr} is not present in the metadata file" do @data.delete(attr.to_sym) @text = @data.to_pson File.stubs(:read).with("/my/file").returns @text lambda { @module.load_metadata }.should raise_error( Puppet::Module::MissingMetadata, "No #{attr} module metadata provided for foo" ) end end it "should set puppetversion if present in the metadata file" do @module.load_metadata @module.puppetversion.should == @data[:puppetversion] end it "should fail if the discovered name is different than the metadata name" end end diff --git a/spec/unit/network/handler/ca_spec.rb b/spec/unit/network/handler/ca_spec.rb deleted file mode 100644 index 43aa5a721..000000000 --- a/spec/unit/network/handler/ca_spec.rb +++ /dev/null @@ -1,86 +0,0 @@ -require 'spec_helper' - -require 'puppet/network/handler/ca' - -describe Puppet::Network::Handler::CA, :unless => Puppet.features.microsoft_windows? do - include PuppetSpec::Files - - describe "#getcert" do - let(:host) { "testhost" } - let(:x509_name) { OpenSSL::X509::Name.new [['CN', host]] } - let(:key) { Puppet::SSL::Key.new(host).generate } - - let(:csr) do - csr = OpenSSL::X509::Request.new - csr.subject = x509_name - csr.public_key = key.public_key - csr - end - - let(:ca) { Puppet::SSL::CertificateAuthority.new } - let(:cacert) { ca.instance_variable_get(:@certificate) } - - before :each do - Puppet[:confdir] = tmpdir('conf') - - Puppet::SSL::CertificateAuthority.stubs(:ca?).returns true - Puppet::SSL::CertificateAuthority.stubs(:singleton_instance).returns ca - end - - it "should do nothing if the master is not a CA" do - Puppet::SSL::CertificateAuthority.stubs(:ca?).returns false - - csr = OpenSSL::X509::Request.new - subject.getcert(csr.to_pem).should == '' - end - - describe "when a certificate already exists for the host" do - let!(:cert) { ca.generate(host) } - - it "should return the existing cert if it matches the public key of the CSR" do - csr.public_key = cert.content.public_key - - subject.getcert(csr.to_pem).should == [cert.to_s, cacert.to_s] - end - - it "should fail if the public key of the CSR does not match the existing cert" do - expect do - subject.getcert(csr.to_pem) - end.to raise_error(Puppet::Error, /Certificate request does not match existing certificate/) - end - end - - describe "when autosign is enabled" do - before :each do - Puppet[:autosign] = true - end - - it "should return the new cert and the CA cert" do - cert_str, cacert_str = subject.getcert(csr.to_pem) - - returned_cert = Puppet::SSL::Certificate.from_s(cert_str) - returned_cacert = Puppet::SSL::Certificate.from_s(cacert_str) - - returned_cert.name.should == host - returned_cacert.content.subject.cmp(cacert.content.subject).should == 0 - end - end - - describe "when autosign is disabled" do - before :each do - Puppet[:autosign] = false - end - - it "should save the CSR without signing it" do - subject.getcert(csr.to_pem) - - Puppet::SSL::Certificate.indirection.find(host).should be_nil - Puppet::SSL::CertificateRequest.indirection.find(host).should be_a(Puppet::SSL::CertificateRequest) - end - - it "should not return a cert" do - subject.getcert(csr.to_pem).should be_nil - end - end - end -end diff --git a/spec/unit/network/handler/fileserver_spec.rb b/spec/unit/network/handler/fileserver_spec.rb deleted file mode 100755 index 2b8094b8b..000000000 --- a/spec/unit/network/handler/fileserver_spec.rb +++ /dev/null @@ -1,201 +0,0 @@ -#!/usr/bin/env rspec -require 'spec_helper' - -require 'puppet/network/handler/fileserver' - -describe Puppet::Network::Handler::FileServer do - include PuppetSpec::Files - - def create_file(filename) - File.open(filename, "w") { |f| f.puts filename} - end - - def create_nested_file - dirname = File.join(@basedir, "nested_dir") - Dir.mkdir(dirname) - file = File.join(dirname, "nested_dir_file") - create_file(file) - end - - before do - @basedir = tmpdir("test_network_handler") - @file = File.join(@basedir, "aFile") - @link = File.join(@basedir, "aLink") - create_file(@file) - @mount = Puppet::Network::Handler::FileServer::Mount.new("some_path", @basedir) - end - - describe "when parsing the fileserver.conf" do - it "should create a valid mount when a valid conf is read" do - config_file = tmpfile('fileserver.conf') - mountdir = tmpdir('mountdir') - - conf_text = <<-HEREDOC - [mymount] - path #{mountdir} - allow anyone.com - deny nobody.com - HEREDOC - File.open(config_file, 'w') { |f| f.write conf_text } - - fs = Puppet::Network::Handler::FileServer.new(:Config => config_file) - mounts = fs.instance_variable_get(:@mounts) - mount = mounts["mymount"] - mount.path == mountdir - mount.instance_variable_get(:@declarations).map {|d| d.pattern}.should =~ [["com", "nobody"], ["com", "anyone"]] - end - - ['path', 'allow', 'deny'].each do |arg| - it "should error if config file doesn't specify a mount for #{arg} argument" do - config_file = tmpfile('fileserver.conf') - File.open(config_file, 'w') { |f| f.puts "#{arg} 127.0.0.1/24" } - - expect { - Puppet::Network::Handler::FileServer.new(:Config => config_file) - }.should raise_error(Puppet::Network::Handler::FileServerError, "No mount specified for argument #{arg} 127.0.0.1/24") - end - end - end - - it "should list a single directory" do - @mount.list("/", false, false).should == [["/", "directory"]] - end - - it "should list a file within a directory when given the file path" do - @mount.list("/aFile", false, "false").should == [["/", "file"]] - end - - it "should list a file within a directory when given the file path with recursion" do - @mount.list("/aFile", true, "false").should == [["/", "file"]] - end - - it "should return nil for a non-existent path" do - @mount.list("/no_such_file", false, false).should be(nil) - end - - it "should list a symbolic link as a file when given the link path", :unless => Puppet.features.microsoft_windows? do - File.symlink(@file, @link) - @mount.list("/aLink", false, false).should == [["/", "file"]] - end - - it "should return nil for a dangling symbolic link when given the link path", :unless => Puppet.features.microsoft_windows? do - File.symlink("/some/where", @link) - @mount.list("/aLink", false, false).should be(nil) - end - - it "should list directory contents of a flat directory structure when asked to recurse" do - list = @mount.list("/", true, false) - list.should include(["/aFile", "file"]) - list.should include(["/", "directory"]) - list.should have(2).items - end - - it "should list the contents of a nested directory" do - create_nested_file - list = @mount.list("/", true, false) - list.sort.should == [ ["/aFile", "file"], ["/", "directory"] , ["/nested_dir", "directory"], ["/nested_dir/nested_dir_file", "file"]].sort - end - - it "should list the contents of a directory ignoring files that match" do - create_nested_file - list = @mount.list("/", true, "*File") - list.sort.should == [ ["/", "directory"] , ["/nested_dir", "directory"], ["/nested_dir/nested_dir_file", "file"]].sort - end - - it "should list the contents of a directory ignoring directories that match" do - create_nested_file - list = @mount.list("/", true, "*nested_dir") - list.sort.should == [ ["/aFile", "file"], ["/", "directory"] ].sort - end - - it "should list the contents of a directory ignoring all ignore patterns that match" do - create_nested_file - list = @mount.list("/", true, ["*File" , "*nested_dir"]) - list.should == [ ["/", "directory"] ] - end - - it "should list the directory when recursing to a depth of zero" do - create_nested_file - list = @mount.list("/", 0, false) - list.should == [["/", "directory"]] - end - - it "should list the base directory and files and nested directory to a depth of one" do - create_nested_file - list = @mount.list("/", 1, false) - list.sort.should == [ ["/aFile", "file"], ["/nested_dir", "directory"], ["/", "directory"] ].sort - end - - it "should list the base directory and files and nested directory to a depth of two" do - create_nested_file - list = @mount.list("/", 2, false) - list.sort.should == [ ["/aFile", "file"], ["/", "directory"] , ["/nested_dir", "directory"], ["/nested_dir/nested_dir_file", "file"]].sort - end - - it "should list the base directory and files and nested directory to a depth greater than the directory structure" do - create_nested_file - list = @mount.list("/", 42, false) - list.sort.should == [ ["/aFile", "file"], ["/", "directory"] , ["/nested_dir", "directory"], ["/nested_dir/nested_dir_file", "file"]].sort - end - - it "should list a valid symbolic link as a file when recursing base dir", :unless => Puppet.features.microsoft_windows? do - File.symlink(@file, @link) - list = @mount.list("/", true, false) - list.sort.should == [ ["/", "directory"], ["/aFile", "file"], ["/aLink", "file"] ].sort - end - - it "should not error when a dangling symlink is present", :unless => Puppet.features.microsoft_windows? do - File.symlink("/some/where", @link) - lambda { @mount.list("/", true, false) }.should_not raise_error - end - - it "should return the directory contents of valid entries when a dangling symlink is present", :unless => Puppet.features.microsoft_windows? do - File.symlink("/some/where", @link) - list = @mount.list("/", true, false) - list.sort.should == [ ["/aFile", "file"], ["/", "directory"] ].sort - end - - describe Puppet::Network::Handler::FileServer::PluginMount, :'fails_on_ruby_1.9.2' => true do - PLUGINS = Puppet::Network::Handler::FileServer::PLUGINS - - # create a module plugin hierarchy - def create_plugin(mod, plugin) - dirname = File.join(@basedir, mod) - Dir.mkdir(dirname) - plugins = File.join(dirname, PLUGINS) - Dir.mkdir(plugins) - facter = File.join(plugins, plugin) - Dir.mkdir(facter) - create_file(File.join(facter,"fact.rb")) - end - - before :each do - @modules = ["one","two"] - @modules.each { |m| create_plugin(m, "facter") } - - Puppet::Node::Environment.new.stubs(:modulepath).returns @basedir - - @mount = Puppet::Network::Handler::FileServer::PluginMount.new(PLUGINS) - @mount.allow("*") - end - - it "should list a file within a directory when given the file path with recursion" do - @mount.list("facter/fact.rb", true, "false").should == [["/", "file"], ["/", "file"]] - end - - it "should return a merged view of all plugins for all modules" do - list = @mount.list("facter",true,false) - list.should == [["/", "directory"], ["/fact.rb", "file"], ["/", "directory"], ["/fact.rb", "file"]] - end - - it "should not fail for inexistant plugins type" do - @mount.list("puppet/parser",true,false) - end - - end - - after do - FileUtils.rm_rf(@basedir) - end - -end diff --git a/spec/unit/network/http/mongrel/xmlrpc_spec.rb b/spec/unit/network/http/mongrel/xmlrpc_spec.rb deleted file mode 100755 index e69de29bb..000000000 diff --git a/spec/unit/network/http/mongrel_spec.rb b/spec/unit/network/http/mongrel_spec.rb index 9e7e9c485..2e0ff04a0 100755 --- a/spec/unit/network/http/mongrel_spec.rb +++ b/spec/unit/network/http/mongrel_spec.rb @@ -1,121 +1,91 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/network/http' describe "Puppet::Network::HTTP::Mongrel", "after initializing", :if => Puppet.features.mongrel?, :'fails_on_ruby_1.9.2' => true do it "should not be listening", :'fails_on_ruby_1.9.2' => true do require 'puppet/network/http/mongrel' Puppet::Network::HTTP::Mongrel.new.should_not be_listening end end describe "Puppet::Network::HTTP::Mongrel", "when turning on listening", :if => Puppet.features.mongrel?, :'fails_on_ruby_1.9.2' => true do before do require 'puppet/network/http/mongrel' @server = Puppet::Network::HTTP::Mongrel.new @mock_mongrel = mock('mongrel') @mock_mongrel.stubs(:run) @mock_mongrel.stubs(:register) Mongrel::HttpServer.stubs(:new).returns(@mock_mongrel) - @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, :protocols => [ :rest, :xmlrpc ], :xmlrpc_handlers => [ :status, :fileserver ] } + @listen_params = { :address => "127.0.0.1", :port => 31337 } 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 protocol" do - Proc.new { @server.listen(@listen_params.delete_if {|k,v| :protocols == k}) }.should raise_error(ArgumentError) - end - 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 it "should order a mongrel server to start" do @mock_mongrel.expects(:run) @server.listen(@listen_params) end it "should tell mongrel to listen on the specified address and port" do Mongrel::HttpServer.expects(:new).with("127.0.0.1", 31337).returns(@mock_mongrel) @server.listen(@listen_params) end it "should be listening" do Mongrel::HttpServer.expects(:new).returns(@mock_mongrel) @server.listen(@listen_params) @server.should be_listening end describe "when providing REST services" do it "should instantiate a handler at / for handling REST calls" do Puppet::Network::HTTP::MongrelREST.expects(:new).returns "myhandler" @mock_mongrel.expects(:register).with("/", "myhandler") @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 - end - - 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 end describe "Puppet::Network::HTTP::Mongrel", "when turning off listening", :if => Puppet.features.mongrel?, :'fails_on_ruby_1.9.2' => true do before do @mock_mongrel = mock('mongrel httpserver') @mock_mongrel.stubs(:run) @mock_mongrel.stubs(:register) Mongrel::HttpServer.stubs(:new).returns(@mock_mongrel) @server = Puppet::Network::HTTP::Mongrel.new - @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :catalog ], :protocols => [ :rest ] } + @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :catalog ]} end it "should fail unless listening" do Proc.new { @server.unlisten }.should raise_error(RuntimeError) end it "should order mongrel server to stop" do @server.listen(@listen_params) @mock_mongrel.expects(:stop) @server.unlisten end it "should not be listening" do @server.listen(@listen_params) @mock_mongrel.stubs(:stop) @server.unlisten @server.should_not be_listening end end diff --git a/spec/unit/network/http/rack/xmlrpc_spec.rb b/spec/unit/network/http/rack/xmlrpc_spec.rb deleted file mode 100755 index 9173438a6..000000000 --- a/spec/unit/network/http/rack/xmlrpc_spec.rb +++ /dev/null @@ -1,155 +0,0 @@ -#!/usr/bin/env rspec -require 'spec_helper' -require 'puppet/network/handler' -require 'puppet/network/http/rack' if Puppet.features.rack? -require 'puppet/network/http/rack/xmlrpc' if Puppet.features.rack? - -describe "Puppet::Network::HTTP::RackXMLRPC", :if => Puppet.features.rack? do - describe "when initializing" do - it "should create an Puppet::Network::XMLRPCServer" do - Puppet::Network::XMLRPCServer.expects(:new).returns stub_everything - Puppet::Network::HTTP::RackXMLRPC.new([]) - end - - it "should create each handler" do - handler = stub_everything 'handler' - Puppet::Network::XMLRPCServer.any_instance.stubs(:add_handler) - Puppet::Network::Handler.expects(:handler).returns(handler).times(2) - Puppet::Network::HTTP::RackXMLRPC.new([:foo, :bar]) - end - - it "should add each handler to the XMLRPCserver" do - handler = stub_everything 'handler' - Puppet::Network::Handler.stubs(:handler).returns(handler) - Puppet::Network::XMLRPCServer.any_instance.expects(:add_handler).times(2) - Puppet::Network::HTTP::RackXMLRPC.new([:foo, :bar]) - end - end - - describe "when serving a request" do - - before :each do - foo_handler = stub_everything 'foo_handler' - Puppet::Network::Handler.stubs(:handler).with(:foo).returns foo_handler - Puppet::Network::XMLRPCServer.any_instance.stubs(:add_handler) - Puppet::Network::XMLRPCServer.any_instance.stubs(:process).returns('') - @handler = Puppet::Network::HTTP::RackXMLRPC.new([:foo]) - end - - before :each do - @response = Rack::Response.new - end - - def mk_req(opts = {}) - opts[:method] = 'POST' if !opts[:method] - opts['CONTENT_TYPE'] = 'text/xml; foo=bar' if !opts['CONTENT_TYPE'] - env = Rack::MockRequest.env_for('/RPC2', opts) - Rack::Request.new(env) - end - - it "should reject non-POST requests" do - req = mk_req :method => 'PUT' - @handler.process(req, @response) - @response.status.should == 405 - end - - it "should reject non text/xml requests" do - req = mk_req 'CONTENT_TYPE' => 'yadda/plain' - end - - it "should create a ClientRequest" do - cr = Puppet::Network::ClientRequest.new(nil, '127.0.0.1', false) - Puppet::Network::ClientRequest.expects(:new).returns cr - req = mk_req - @handler.process(req, @response) - end - - it "should let xmlrpcserver process the request" do - Puppet::Network::XMLRPCServer.any_instance.expects(:process).returns('yay') - req = mk_req - @handler.process(req, @response) - end - - it "should report the response as OK" do - req = mk_req - @handler.process(req, @response) - @response.status.should == 200 - end - - it "should report the response with the correct content type" do - req = mk_req - @handler.process(req, @response) - @response['Content-Type'].should == 'text/xml; charset=utf-8' - end - - it "should set 'authenticated' to false if no certificate is present" do - req = mk_req - Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| authenticated == false } - @handler.process(req, @response) - end - - it "should use the client's ip address" do - req = mk_req 'REMOTE_ADDR' => 'ipaddress' - Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| ip == 'ipaddress' } - @handler.process(req, @response) - end - - describe "with pre-validated certificates" do - - it "should use the :ssl_client_header to determine the parameter when looking for the certificate" do - Puppet.settings.stubs(:value).returns "eh" - Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" - req = mk_req "myheader" => "/CN=host.domain.com" - @handler.process(req, @response) - end - - it "should retrieve the hostname by matching the certificate parameter" do - Puppet.settings.stubs(:value).returns "eh" - Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" - Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| node == "host.domain.com" } - req = mk_req "myheader" => "/CN=host.domain.com" - @handler.process(req, @response) - end - - it "should use the :ssl_client_header to determine the parameter for checking whether the host certificate is valid" do - Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" - Puppet.settings.expects(:value).with(:ssl_client_verify_header).returns "myheader" - req = mk_req "myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com" - @handler.process(req, @response) - end - - it "should consider the host authenticated if the validity parameter contains 'SUCCESS'" do - Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" - Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" - Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| authenticated == true } - req = mk_req "myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com" - @handler.process(req, @response) - end - - it "should consider the host unauthenticated if the validity parameter does not contain 'SUCCESS'" do - Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" - Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" - Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| authenticated == false } - req = mk_req "myheader" => "whatever", "certheader" => "/CN=host.domain.com" - @handler.process(req, @response) - end - - it "should consider the host unauthenticated if no certificate information is present" do - Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" - Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" - Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| authenticated == false } - req = mk_req "myheader" => nil, "certheader" => "/CN=host.domain.com" - @handler.process(req, @response) - end - - it "should resolve the node name with an ip address look-up if no certificate is present" do - Puppet.settings.stubs(:value).returns "eh" - Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" - Resolv.any_instance.expects(:getname).returns("host.domain.com") - Puppet::Network::ClientRequest.expects(:new).with { |node,ip,authenticated| node == "host.domain.com" } - req = mk_req "myheader" => nil - @handler.process(req, @response) - end - end - end -end diff --git a/spec/unit/network/http/rack_spec.rb b/spec/unit/network/http/rack_spec.rb index 9e1ee3d1e..5f148091a 100755 --- a/spec/unit/network/http/rack_spec.rb +++ b/spec/unit/network/http/rack_spec.rb @@ -1,100 +1,43 @@ #!/usr/bin/env rspec require 'spec_helper' -require 'puppet/network/handler' require 'puppet/network/http/rack' if Puppet.features.rack? describe "Puppet::Network::HTTP::Rack", :if => Puppet.features.rack? do - describe "while initializing" do - - it "should require a protocol specification" do - Proc.new { Puppet::Network::HTTP::Rack.new({}) }.should raise_error(ArgumentError) - end - - it "should not accept imaginary protocols" do - Proc.new { Puppet::Network::HTTP::Rack.new({:protocols => [:foo]}) }.should raise_error(ArgumentError) - end - - it "should accept the REST protocol" do - Proc.new { Puppet::Network::HTTP::Rack.new({:protocols => [:rest]}) }.should_not raise_error(ArgumentError) - end - - it "should create a RackREST instance" do - Puppet::Network::HTTP::RackREST.expects(:new) - Puppet::Network::HTTP::Rack.new({:protocols => [:rest]}) - end - - describe "with XMLRPC enabled" do - - it "should require XMLRPC handlers" do - Proc.new { Puppet::Network::HTTP::Rack.new({:protocols => [:xmlrpc]}) }.should raise_error(ArgumentError) - end - - it "should create a RackXMLRPC instance" do - Puppet::Network::HTTP::RackXMLRPC.expects(:new) - Puppet::Network::HTTP::Rack.new({:protocols => [:xmlrpc], :xmlrpc_handlers => [:Status]}) - end - - end - - end - describe "when called" do - before :all do - @app = Puppet::Network::HTTP::Rack.new({:protocols => [:rest]}) + @app = Puppet::Network::HTTP::Rack.new() # let's use Rack::Lint to verify that we're OK with the rack specification @linted = Rack::Lint.new(@app) end before :each do @env = Rack::MockRequest.env_for('/') end it "should create a Request object" do request = Rack::Request.new(@env) Rack::Request.expects(:new).returns request @linted.call(@env) end it "should create a Response object" do Rack::Response.expects(:new).returns stub_everything @app.call(@env) # can't lint when Rack::Response is a stub end it "should let RackREST process the request" do Puppet::Network::HTTP::RackREST.any_instance.expects(:process).once @linted.call(@env) end it "should catch unhandled exceptions from RackREST" do Puppet::Network::HTTP::RackREST.any_instance.expects(:process).raises(ArgumentError, 'test error') Proc.new { @linted.call(@env) }.should_not raise_error end it "should finish() the Response" do Rack::Response.any_instance.expects(:finish).once @app.call(@env) # can't lint when finish is a stub end - - end - - describe "when serving XMLRPC" do - - before :all do - @app = Puppet::Network::HTTP::Rack.new({:protocols => [:rest, :xmlrpc], :xmlrpc_handlers => [:Status]}) - @linted = Rack::Lint.new(@app) - end - - before :each do - @env = Rack::MockRequest.env_for('/RPC2', :method => 'POST') - end - - it "should use RackXMLRPC to serve /RPC2 requests" do - Puppet::Network::HTTP::RackXMLRPC.any_instance.expects(:process).once - @linted.call(@env) - end - end - end - diff --git a/spec/unit/network/http/webrick/xmlrpc_spec.rb b/spec/unit/network/http/webrick/xmlrpc_spec.rb deleted file mode 100755 index e69de29bb..000000000 diff --git a/spec/unit/network/http/webrick_spec.rb b/spec/unit/network/http/webrick_spec.rb index f84e78e24..ec04c77e0 100755 --- a/spec/unit/network/http/webrick_spec.rb +++ b/spec/unit/network/http/webrick_spec.rb @@ -1,336 +1,260 @@ #!/usr/bin/env rspec require 'spec_helper' -require 'puppet/network/handler' require 'puppet/network/http' require 'puppet/network/http/webrick' describe Puppet::Network::HTTP::WEBrick, "after initializing", :unless => Puppet.features.microsoft_windows? do it "should not be listening" do Puppet::Network::HTTP::WEBrick.new.should_not be_listening end end describe Puppet::Network::HTTP::WEBrick, "when turning on listening", :unless => Puppet.features.microsoft_windows? do before do @mock_webrick = stub('webrick', :[] => {}, :listeners => [], :status => :Running) [: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, :xmlrpc_handlers => [], :protocols => [ :rest ] } + @listen_params = { :address => "127.0.0.1", :port => 31337, :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 protocol" do - Proc.new { @server.listen(@listen_params.delete_if {|k,v| :protocols == k}) }.should raise_error(ArgumentError) - end - 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 it "should order a webrick server to start in a separate thread" do @mock_webrick.expects(:start) # If you remove this you'll sometimes get race condition problems Thread.expects(:new).yields @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 describe "when the REST protocol is requested" do it "should register the REST handler at /" do # We don't care about the options here. @mock_webrick.expects(:mount).with { |path, klass, options| path == "/" and klass == Puppet::Network::HTTP::WEBrickREST } @server.listen(@listen_params.merge(:protocols => [:rest])) end end - - 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 -end - - -describe Puppet::Network::HTTP::WEBrick, "when looking up the class to handle a protocol", :unless => Puppet.features.microsoft_windows? 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 end describe Puppet::Network::HTTP::WEBrick, "when turning off listening", :unless => Puppet.features.microsoft_windows? do before do @mock_webrick = stub('webrick', :[] => {}, :listeners => [], :status => :Running) [: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, :unless => Puppet.features.microsoft_windows? 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 run_mode is master" do Puppet.run_mode.stubs(:master?).returns(true) 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 run_mode is not master" do Puppet.run_mode.stubs(:master?).returns(false) 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.indirection.stubs(:find).with('ca').returns @cert Puppet::SSL::Host.stubs(:localhost).returns @host end it "should use the key from the localhost SSL::Host instance" do Puppet::SSL::Host.expects(:localhost).returns @host @host.expects(:key).returns @key @server.setup_ssl[:SSLPrivateKey].should == "mykey" 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.indirection.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/server_spec.rb b/spec/unit/network/server_spec.rb index b38e82b93..b769472f3 100755 --- a/spec/unit/network/server_spec.rb +++ b/spec/unit/network/server_spec.rb @@ -1,523 +1,427 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/network/server' -require 'puppet/network/handler' describe Puppet::Network::Server do before do @mock_http_server_class = mock('http server class') Puppet.settings.stubs(:use) Puppet.settings.stubs(:value).with(:name).returns("me") Puppet.settings.stubs(:value).with(:servertype).returns(:suparserver) Puppet.settings.stubs(:value).with(:bindaddress).returns("") Puppet.settings.stubs(:value).with(:masterport).returns(8140) Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class) Puppet.settings.stubs(:value).with(:servertype).returns(:suparserver) @server = Puppet::Network::Server.new(:port => 31337) end describe "when initializing" do before do Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') - Puppet::Network::Handler.stubs(:handler).returns mock('xmlrpc_handler') Puppet.settings.stubs(:value).with(:bindaddress).returns("") Puppet.settings.stubs(:value).with(:masterport).returns('') end it 'should fail if an unknown option is provided' do lambda { Puppet::Network::Server.new(:foo => 31337) }.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 :bindaddress setting to determine the 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 it "should set the bind address to '127.0.0.1' if the default address is an empty string and the server type is mongrel" do Puppet.settings.stubs(:value).with(:servertype).returns("mongrel") Puppet.settings.expects(:value).with(:bindaddress).returns("") @server = Puppet::Network::Server.new @server.address.should == '127.0.0.1' end it "should set the bind address to '0.0.0.0' if the default address is an empty string and the server type is webrick" do Puppet.settings.stubs(:value).with(:servertype).returns("webrick") Puppet.settings.expects(:value).with(:bindaddress).returns("") @server = Puppet::Network::Server.new @server.address.should == '0.0.0.0' 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 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(: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.settings.expects(:value).with(:servertype).returns(nil) lambda { Puppet::Network::Server.new(: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(: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(:port => 31337) }.should raise_error(ArgumentError) end it "should allow registering REST handlers" do @server = Puppet::Network::Server.new(: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(: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(: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(:port => 31337, :xmlrpc_handlers => [ :foo, :bar, :baz]) + @server = Puppet::Network::Server.new(:port => 31337) 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(:port => 31337, :xmlrpc_handlers => [ :foo, :bar, :baz]) + @server = Puppet::Network::Server.new(:port => 31337) end end # 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 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 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 describe "when creating its pidfile" do it "should use an exclusive mutex" do Puppet.settings.expects(:value).with(:name).returns "me" Puppet::Util.expects(:synchronize_on).with("me",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 describe "when removing its pidfile" do it "should use an exclusive mutex" do Puppet.settings.expects(:value).with(:name).returns "me" Puppet::Util.expects(:synchronize_on).with("me",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 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 provide a means of determining which protocols are in use" do - @server.should respond_to(:protocols) - end - - 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 it "should allow for multiple configurations, each handling different indirections" do Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') @server2 = Puppet::Network::Server.new(: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 - describe "when managing xmlrpc registrations" do - before do - Puppet::Network::Handler.stubs(:handler).returns mock('xmlrpc_handler') - 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 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 - 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 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 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(:port => 31337, :handlers => [:node], :xmlrpc_handlers => [:master]) + @server = Puppet::Network::Server.new(:port => 31337, :handlers => [:node]) @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 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 diff --git a/test/lib/puppettest/servertest.rb b/test/lib/puppettest/servertest.rb index 82483004d..852c0ec27 100644 --- a/test/lib/puppettest/servertest.rb +++ b/test/lib/puppettest/servertest.rb @@ -1,73 +1,32 @@ require 'puppettest' module PuppetTest::ServerTest include PuppetTest def setup super if defined?(@@port) @@port += 1 else @@port = 20000 end end # create a simple manifest that just creates a file def mktestmanifest file = File.join(Puppet[:confdir], "#{(self.class.to_s + "test")}site.pp") #@createdfile = File.join(tmpdir, self.class.to_s + "manifesttesting" + # "_#{@method_name}") @createdfile = tempfile File.open(file, "w") { |f| f.puts "file { \"%s\": ensure => file, mode => 755 }\n" % @createdfile } @@tmpfiles << @createdfile @@tmpfiles << file file end - - # create a server, forked into the background - def mkserver(handlers = nil) - Puppet[:name] = "puppetmasterd" - # our default handlers - unless handlers - handlers = { - :CA => {}, # so that certs autogenerate - :Master => { - :Manifest => mktestmanifest, - :UseNodes => false - }, - } - end - - # then create the actual server - server = nil - assert_nothing_raised { - - server = Puppet::Network::HTTPServer::WEBrick.new( - - :Port => @@port, - - :Handlers => handlers - ) - } - - # fork it - spid = fork { - trap(:INT) { server.shutdown } - server.start - } - - # and store its pid for killing - @@tmppids << spid - - # give the server a chance to do its thing - sleep 1 - spid - end - end diff --git a/test/network/handler/fileserver.rb b/test/network/handler/fileserver.rb deleted file mode 100755 index b76f8e199..000000000 --- a/test/network/handler/fileserver.rb +++ /dev/null @@ -1,1260 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') - -require 'puppettest' -require 'puppet/network/handler/fileserver' - -class TestFileServer < Test::Unit::TestCase - include PuppetTest - - def mkmount(path = nil) - mount = nil - name = "yaytest" - base = path || tempfile - Dir.mkdir(base) unless FileTest.exists?(base) - # Create a test file - File.open(File.join(base, "file"), "w") { |f| f.puts "bazoo" } - assert_nothing_raised { - mount = Puppet::Network::Handler.fileserver::Mount.new(name, base) - } - - mount - end - # make a simple file source - def mktestdir - testdir = File.join(tmpdir, "remotefilecopytesting") - @@tmpfiles << testdir - - # create a tmpfile - pattern = "tmpfile" - tmpfile = File.join(testdir, pattern) - assert_nothing_raised { - Dir.mkdir(testdir) - File.open(tmpfile, "w") { |f| - 3.times { f.puts rand(100) } - } - } - - [testdir, %r{#{pattern}}, tmpfile] - end - - # make a bunch of random test files - def mktestfiles(testdir) - @@tmpfiles << testdir - assert_nothing_raised { - files = %w{a b c d e}.collect { |l| - name = File.join(testdir, "file#{l}") - File.open(name, "w") { |f| - f.puts rand(100) - } - - name - } - - return files - } - end - - def assert_describe(base, file, server) - file = File.basename(file) - assert_nothing_raised { - desc = server.describe(base + file) - assert(desc, "Got no description for #{file}") - assert(desc != "", "Got no description for #{file}") - assert_match(/^\d+/, desc, "Got invalid description #{desc}") - } - end - - # test for invalid names - def test_namefailures - server = nil - assert_nothing_raised { - - server = Puppet::Network::Handler.fileserver.new( - - :Local => true, - - :Config => false - ) - } - - [" ", "=" "+", "&", "#", "*"].each do |char| - assert_raise(Puppet::Network::Handler::FileServerError, "'#{char}' did not throw a failure in fileserver module names") { - server.mount("/tmp", "invalid#{char}name") - } - end - end - - # verify that listing the root behaves as expected - def test_listroot - server = nil - testdir, pattern, tmpfile = mktestdir - - file = nil - checks = Puppet::Network::Handler.fileserver::CHECKPARAMS - - # and make our fileserver - assert_nothing_raised { - - server = Puppet::Network::Handler.fileserver.new( - - :Local => true, - - :Config => false - ) - } - - # mount the testdir - assert_nothing_raised { - server.mount(testdir, "test") - } - - # and verify different iterations of 'root' return the same value - list = nil - assert_nothing_raised { - list = server.list("/test/", :manage, true, false) - } - - assert(list =~ pattern) - - assert_nothing_raised { - list = server.list("/test", :manage, true, false) - } - assert(list =~ pattern) - - end - - # test listing individual files - def test_getfilelist - server = nil - testdir, pattern, tmpfile = mktestdir - - file = nil - - assert_nothing_raised { - - server = Puppet::Network::Handler.fileserver.new( - - :Local => true, - - :Config => false - ) - } - - assert_nothing_raised { - server.mount(testdir, "test") - } - - # get our listing - list = nil - sfile = "/test/tmpfile" - assert_nothing_raised { - list = server.list(sfile, :manage, true, false) - } - - output = "/\tfile" - - # verify it got listed as a file - assert_equal(output, list) - - # verify we got all fields - assert(list !~ /\t\t/) - - # verify that we didn't get the directory itself - list.split("\n").each { |line| - assert(line !~ %r{remotefile}) - } - - # and then verify that the contents match - contents = File.read(tmpfile) - - ret = nil - assert_nothing_raised { - ret = server.retrieve(sfile) - } - - assert_equal(contents, ret) - end - - # check that the fileserver is seeing newly created files - def test_seenewfiles - server = nil - testdir, pattern, tmpfile = mktestdir - - - newfile = File.join(testdir, "newfile") - - # go through the whole schtick again... - file = nil - checks = Puppet::Network::Handler.fileserver::CHECKPARAMS - - assert_nothing_raised { - - server = Puppet::Network::Handler.fileserver.new( - - :Local => true, - - :Config => false - ) - } - - assert_nothing_raised { - server.mount(testdir, "test") - } - - list = nil - sfile = "/test/" - assert_nothing_raised { - list = server.list(sfile, :manage, true, false) - } - - # create the new file - File.open(newfile, "w") { |f| - 3.times { f.puts rand(100) } - } - - newlist = nil - assert_nothing_raised { - newlist = server.list(sfile, :manage, true, false) - } - - # verify the list has changed - assert(list != newlist) - - # and verify that we are specifically seeing the new file - assert(newlist =~ /newfile/) - end - - # verify we can mount /, which is what local file servers will - # normally do - def test_mountroot - server = nil - assert_nothing_raised { - - server = Puppet::Network::Handler.fileserver.new( - - :Local => true, - - :Config => false - ) - } - - assert_nothing_raised { - server.mount("/", "root") - } - - testdir, pattern, tmpfile = mktestdir - - list = nil - assert_nothing_raised { - list = server.list("/root/#{testdir}", :manage, true, false) - } - - assert(list =~ pattern) - assert_nothing_raised { - list = server.list("/root#{testdir}", :manage, true, false) - } - - assert(list =~ pattern) - end - - # verify that we're correctly recursing the right number of levels - def test_recursionlevels - server = nil - assert_nothing_raised { - - server = Puppet::Network::Handler.fileserver.new( - - :Local => true, - - :Config => false - ) - } - - # make our deep recursion - basedir = File.join(tmpdir, "recurseremotetesting") - testdir = "#{basedir}/with/some/sub/directories/for/the/purposes/of/testing" - oldfile = File.join(testdir, "oldfile") - assert_nothing_raised { - system("mkdir -p #{testdir}") - File.open(oldfile, "w") { |f| - 3.times { f.puts rand(100) } - } - @@tmpfiles << basedir - } - - assert_nothing_raised { - server.mount(basedir, "test") - } - - # get our list - list = nil - assert_nothing_raised { - list = server.list("/test/with", :manage, false, false) - } - - # make sure we only got one line, since we're not recursing - assert(list !~ /\n/) - - # for each level of recursion, make sure we get the right list - [0, 1, 2].each { |num| - assert_nothing_raised { - list = server.list("/test/with", :manage, num, false) - } - - count = 0 - while list =~ /\n/ - list.sub!(/\n/, '') - count += 1 - end - assert_equal(num, count) - } - end - - # verify that we're not seeing the dir we ask for; i.e., that our - # list is relative to that dir, not it's parent dir - def test_listedpath - server = nil - assert_nothing_raised { - - server = Puppet::Network::Handler.fileserver.new( - - :Local => true, - - :Config => false - ) - } - - - # create a deep dir - basedir = tempfile - testdir = "#{basedir}/with/some/sub/directories/for/testing" - oldfile = File.join(testdir, "oldfile") - assert_nothing_raised { - system("mkdir -p #{testdir}") - File.open(oldfile, "w") { |f| - 3.times { f.puts rand(100) } - } - @@tmpfiles << basedir - } - - # mounty mounty - assert_nothing_raised { - server.mount(basedir, "localhost") - } - - list = nil - # and then check a few dirs - assert_nothing_raised { - list = server.list("/localhost/with", :manage, false, false) - } - - assert(list !~ /with/) - - assert_nothing_raised { - list = server.list("/localhost/with/some/sub", :manage, true, false) - } - - assert(list !~ /sub/) - end - - # test many dirs, not necessarily very deep - def test_widelists - server = nil - assert_nothing_raised { - - server = Puppet::Network::Handler.fileserver.new( - - :Local => true, - - :Config => false - ) - } - - basedir = tempfile - dirs = %w{a set of directories} - assert_nothing_raised { - Dir.mkdir(basedir) - dirs.each { |dir| - Dir.mkdir(File.join(basedir, dir)) - } - @@tmpfiles << basedir - } - - assert_nothing_raised { - server.mount(basedir, "localhost") - } - - list = nil - assert_nothing_raised { - list = server.list("/localhost/", :manage, 1, false) - } - assert_instance_of(String, list, "Server returned %s instead of string") - list = list.split("\n") - - assert_equal(dirs.length + 1, list.length) - end - - # verify that 'describe' works as advertised - def test_describe - server = nil - testdir = tstdir - files = mktestfiles(testdir) - - file = nil - checks = Puppet::Network::Handler.fileserver::CHECKPARAMS - - assert_nothing_raised { - - server = Puppet::Network::Handler.fileserver.new( - - :Local => true, - - :Config => false - ) - } - - assert_nothing_raised { - server.mount(testdir, "test") - } - - # get our list - list = nil - sfile = "/test/" - assert_nothing_raised { - list = server.list(sfile, :manage, true, false) - } - - # and describe each file in the list - assert_nothing_raised { - list.split("\n").each { |line| - file, type = line.split("\t") - - desc = server.describe(sfile + file) - } - } - - # and then make sure we can describe everything that we know is there - files.each { |file| - assert_describe(sfile, file, server) - } - - # And then describe some files that we know aren't there - retval = nil - assert_nothing_raised("Describing non-existent files raised an error") { - retval = server.describe(sfile + "noexisties") - } - - assert_equal("", retval, "Description of non-existent files returned a value") - - # Now try to describe some sources that don't even exist - retval = nil - - assert_raise( - Puppet::Network::Handler::FileServerError, - - "Describing non-existent mount did not raise an error") { - retval = server.describe("/notmounted/noexisties") - } - - assert_nil(retval, "Description of non-existent mounts returned a value") - end - - def test_describe_does_not_fail_when_mount_does_not_find_file - server = Puppet::Network::Handler.fileserver.new(:Local => true, :Config => false) - - assert_nothing_raised("Failed when describing missing plugins") do - server.describe "/plugins" - end - end - - # test that our config file is parsing and working as planned - def test_configfile - server = nil - basedir = File.join(tmpdir, "fileserverconfigfiletesting") - @@tmpfiles << basedir - - # make some dirs for mounting - Dir.mkdir(basedir) - mounts = {} - %w{thing thus the-se those}.each { |dir| - path = File.join(basedir, dir) - Dir.mkdir(path) - mounts[dir] = mktestfiles(path) - - } - - # create an example file with each of them - conffile = tempfile - @@tmpfiles << conffile - - File.open(conffile, "w") { |f| - f.print "# a test config file - -[thing] - path #{basedir}/thing - allow 192.168.0.* - -[thus] - path #{basedir}/thus - allow *.madstop.com, *.kanies.com - deny *.sub.madstop.com - -[the-se] - path #{basedir}/the-se - -[those] - path #{basedir}/those - -" - } - - - # create a server with the file - assert_nothing_raised { - - server = Puppet::Network::Handler.fileserver.new( - - :Local => false, - - :Config => conffile - ) - } - - list = nil - # run through once with no host/ip info, to verify everything is working - mounts.each { |mount, files| - mount = "/#{mount}/" - assert_nothing_raised { - list = server.list(mount, :manage, true, false) - } - - assert_nothing_raised { - list.split("\n").each { |line| - file, type = line.split("\t") - - desc = server.describe(mount + file) - } - } - - files.each { |f| - assert_describe(mount, f, server) - } - } - - # now let's check that things are being correctly forbidden - # this is just a map of names and expected results - { - "thing" => { - :deny => [ - ["hostname.com", "192.168.1.0"], - ["hostname.com", "192.158.0.0"] - ], - :allow => [ - ["hostname.com", "192.168.0.0"], - ["hostname.com", "192.168.0.245"], - ] - }, - "thus" => { - :deny => [ - ["hostname.com", "192.168.1.0"], - ["name.sub.madstop.com", "192.158.0.0"] - ], - :allow => [ - ["luke.kanies.com", "192.168.0.0"], - ["luke.madstop.com", "192.168.0.245"], - ] - } - }.each { |mount, hash| - mount = "/#{mount}/" - - # run through the map - hash.each { |type, ary| - ary.each { |sub| - host, ip = sub - - case type - when :deny - - assert_raise( - Puppet::AuthorizationError, - - "Host #{host}, ip #{ip}, allowed #{mount}") { - list = server.list(mount, :manage, true, false, host, ip) - } - when :allow - assert_nothing_raised("Host #{host}, ip #{ip}, denied #{mount}") { - list = server.list(mount, :manage, true, false, host, ip) - } - end - } - } - } - - end - - # Test that we smoothly handle invalid config files - def test_configfailures - # create an example file with each of them - conffile = tempfile - - invalidmounts = { - "noexist" => "[noexist] - path /this/path/does/not/exist - allow 192.168.0.* -" -} - - invalidconfigs = [ - "[not valid] - path /this/path/does/not/exist - allow 192.168.0.* -", -"[valid] - invalidstatement - path /etc - allow 192.168.0.* -", -"[valid] - allow 192.168.0.* -" -] - - invalidmounts.each { |mount, text| - File.open(conffile, "w") { |f| - f.print text - } - - - # create a server with the file - server = nil - assert_nothing_raised { - - server = Puppet::Network::Handler.fileserver.new( - - :Local => true, - - :Config => conffile - ) - } - - - assert_raise( - Puppet::Network::Handler::FileServerError, - - "Invalid mount was mounted") { - server.list(mount, :manage) - } - } - - invalidconfigs.each_with_index { |text, i| - File.open(conffile, "w") { |f| - f.print text - } - - - # create a server with the file - server = nil - - assert_raise( - Puppet::Network::Handler::FileServerError, - - "Invalid config #{i} did not raise error") { - - server = Puppet::Network::Handler.fileserver.new( - - :Local => true, - - :Config => conffile - ) - } - } - end - - # verify we reread the config file when it changes - def test_filereread - server = nil - - conffile = tempfile - dir = tstdir - - files = mktestfiles(dir) - File.open(conffile, "w") { |f| - f.print "# a test config file - -[thing] - path #{dir} - allow test1.domain.com -" - } - - # Reset the timeout, so we reload faster - Puppet[:filetimeout] = 0.5 - - # start our server with a fast timeout - assert_nothing_raised { - - server = Puppet::Network::Handler.fileserver.new( - - :Local => false, - - :Config => conffile - ) - } - - list = nil - assert_nothing_raised { - - list = server.list( - "/thing/", :manage, false, false, - - "test1.domain.com", "127.0.0.1") - } - assert(list != "", "List returned nothing in rereard test") - - assert_raise(Puppet::AuthorizationError, "List allowed invalid host") { - list = server.list("/thing/", :manage, false, false, "test2.domain.com", "127.0.0.1") - } - - sleep 1 - File.open(conffile, "w") { |f| - f.print "# a test config file - -[thing] - path #{dir} - allow test2.domain.com -" - } - - assert_raise(Puppet::AuthorizationError, "List allowed invalid host") { - list = server.list("/thing/", :manage, false, false, "test1.domain.com", "127.0.0.1") - } - - assert_nothing_raised { - list = server.list("/thing/", :manage, false, false, "test2.domain.com", "127.0.0.1") - } - - assert(list != "", "List returned nothing in rereard test") - - list = nil - end - - # Verify that we get converted to the right kind of string - def test_mountstring - mount = nil - name = "yaytest" - path = tmpdir - assert_nothing_raised { - mount = Puppet::Network::Handler.fileserver::Mount.new(name, path) - } - - assert_equal("mount[#{name}]", mount.to_s) - end - - def test_servinglinks - # Disable the checking, so changes propagate immediately. - Puppet[:filetimeout] = -5 - server = nil - source = tempfile - file = File.join(source, "file") - link = File.join(source, "link") - Dir.mkdir(source) - File.open(file, "w") { |f| f.puts "yay" } - File.symlink(file, link) - assert_nothing_raised { - - server = Puppet::Network::Handler.fileserver.new( - - :Local => true, - - :Config => false - ) - } - - assert_nothing_raised { - server.mount(source, "mount") - } - - # First describe the link when following - results = {} - assert_nothing_raised { - server.describe("/mount/link", :follow).split("\t").zip( - Puppet::Network::Handler.fileserver::CHECKPARAMS - ).each { |v,p| results[p] = v } - } - - assert_equal("file", results[:type]) - - # Then not - results = {} - assert_nothing_raised { - server.describe("/mount/link", :manage).split("\t").zip( - Puppet::Network::Handler.fileserver::CHECKPARAMS - ).each { |v,p| results[p] = v } - } - - assert_equal("link", results[:type]) - - results.each { |p,v| - assert(v, "#{p} has no value") - assert(v != "", "#{p} has no value") - } - end - - # Test that substitution patterns in the path are exapanded - # properly. Disabled, because it was testing too much of the process - # and in a non-portable way. This is a thorough enough test that it should - # be kept, but it should be done in a way that is clearly portable (e.g., - # no md5 sums of file paths). - def test_host_specific - client1 = "client1.example.com" - client2 = "client2.example.com" - ip = "127.0.0.1" - - # Setup a directory hierarchy for the tests - fsdir = File.join(tmpdir, "host-specific") - @@tmpfiles << fsdir - hostdir = File.join(fsdir, "host") - fqdndir = File.join(fsdir, "fqdn") - client1_hostdir = File.join(hostdir, "client1") - client2_fqdndir = File.join(fqdndir, client2) - contents = { - client1_hostdir => "client1\n", - client2_fqdndir => client2 + "\n" - } - [fsdir, hostdir, fqdndir, client1_hostdir, client2_fqdndir].each { |d| Dir.mkdir(d) } - - [client1_hostdir, client2_fqdndir].each do |d| - File.open(File.join(d, "file.txt"), "w") do |f| - f.print contents[d] - end - end - conffile = tempfile - File.open(conffile, "w") do |f| - f.print(" -[host] -path #{hostdir}/%h -allow * -[fqdn] -path #{fqdndir}/%H -allow * -") - end - - server = nil - assert_nothing_raised { - - server = Puppet::Network::Handler.fileserver.new( - - :Local => true, - - :Config => conffile - ) - } - - # check that list returns the correct thing for the two clients - list = nil - sfile = "/host/file.txt" - assert_nothing_raised { - list = server.list(sfile, :manage, true, false, client1, ip) - } - assert_equal("/\tfile", list) - assert_nothing_raised { - list = server.list(sfile, :manage, true, false, client2, ip) - } - assert_equal("", list) - - sfile = "/fqdn/file.txt" - assert_nothing_raised { - list = server.list(sfile, :manage, true, false, client1, ip) - } - assert_equal("", list) - assert_nothing_raised { - list = server.list(sfile, :manage, true, false, client2, ip) - } - assert_equal("/\tfile", list) - - # check describe - sfile = "/host/file.txt" - assert_nothing_raised { - list = server.describe(sfile, :manage, client1, ip).split("\t") - } - assert_equal(5, list.size) - assert_equal("file", list[1]) - md5 = Digest::MD5.hexdigest(contents[client1_hostdir]) - assert_equal("{md5}#{md5}", list[4]) - - assert_nothing_raised { - list = server.describe(sfile, :manage, client2, ip).split("\t") - } - assert_equal([], list) - - sfile = "/fqdn/file.txt" - assert_nothing_raised { - list = server.describe(sfile, :manage, client1, ip).split("\t") - } - assert_equal([], list) - - assert_nothing_raised { - list = server.describe(sfile, :manage, client2, ip).split("\t") - } - assert_equal(5, list.size) - assert_equal("file", list[1]) - md5 = Digest::MD5.hexdigest(contents[client2_fqdndir]) - assert_equal("{md5}#{md5}", list[4]) - - # Check retrieve - sfile = "/host/file.txt" - assert_nothing_raised { - list = server.retrieve(sfile, :manage, client1, ip).chomp - } - assert_equal(contents[client1_hostdir].chomp, list) - - assert_nothing_raised { - list = server.retrieve(sfile, :manage, client2, ip).chomp - } - assert_equal("", list) - - sfile = "/fqdn/file.txt" - assert_nothing_raised { - list = server.retrieve(sfile, :manage, client1, ip).chomp - } - assert_equal("", list) - - assert_nothing_raised { - list = server.retrieve(sfile, :manage, client2, ip).chomp - } - assert_equal(contents[client2_fqdndir].chomp, list) - end - - # Make sure the 'subdir' method in Mount works. - def test_mount_subdir - mount = nil - base = tempfile - Dir.mkdir(base) - subdir = File.join(base, "subdir") - Dir.mkdir(subdir) - [base, subdir].each do |d| - File.open(File.join(d, "file"), "w") { |f| f.puts "bazoo" } - end - mount = mkmount(base) - - assert_equal(base, mount.subdir, "Did not default to base path") - assert_equal(subdir, mount.subdir("subdir"), "Did not default to base path") - end - - # Make sure mounts get correctly marked expandable or not, depending on - # the path. - def test_expandable - name = "yaytest" - dir = tempfile - Dir.mkdir(dir) - - mount = mkmount - assert_nothing_raised { - mount.path = dir - } - - assert(! mount.expandable?, "Mount incorrectly called expandable") - - assert_nothing_raised { - mount.path = "/dir/a%a" - } - assert(mount.expandable?, "Mount not called expandable") - - # This isn't a valid replacement pattern, so it should throw an error - # because the dir doesn't exist - assert_raise(Puppet::Network::Handler::FileServerError) { - mount.path = "/dir/a%" - } - - # Now send it back to a normal path - assert_nothing_raised { - mount.path = dir - } - # Make sure it got reverted - assert(! mount.expandable?, "Mount incorrectly called expandable") - - - end - - def test_mount_expand - mount = mkmount - - check = proc do |client, pattern, repl| - path = "/my/#{pattern}/file" - assert_equal("/my/#{repl}/file", mount.expand(path, client)) - end - - # Do a round of checks with a fake client - client = "host.domain.com" - {"%h" => "host", # Short name - "%H" => client, # Full name - "%d" => "domain.com", # domain - "%%" => "%", # escape - "%o" => "%o" # other - }.each do |pat, repl| - result = check.call(client, pat, repl) - end - - # Now, check that they use Facter info - client = nil - Facter.stubs(:value).with { |v| v.to_s == "hostname" }.returns("myhost") - Facter.stubs(:value).with { |v| v.to_s == "domain" }.returns("mydomain.com") - - - Facter.stubs(:to_hash).returns( - { - :ipaddress => "127.0.0.1", - :hostname => "myhost", - :domain => "mydomain.com", - - }) - - - {"%h" => "myhost", # Short name - "%H" => "myhost.mydomain.com", # Full name - "%d" => "mydomain.com", # domain - "%%" => "%", # escape - "%o" => "%o" # other - }.each do |pat, repl| - check.call(client, pat, repl) - end - - end - - # Test that the fileserver expands the %h and %d things. - def test_fileserver_expansion - server = nil - assert_nothing_raised { - - server = Puppet::Network::Handler.fileserver.new( - - :Local => true, - - :Config => false - ) - } - - dir = tempfile - - # When mocks attack, part 2 - kernel_fact = Facter.value(:kernel) - - ip = '127.0.0.1' - - - Facter.stubs(:to_hash).returns( - { - :kernel => kernel_fact, - :ipaddress => "127.0.0.1", - :hostname => "myhost", - :domain => "mydomain.com", - - }) - - Dir.mkdir(dir) - host = "myhost.mydomain.com" - { - "%H" => "myhost.mydomain.com", "%h" => "myhost", "%d" => "mydomain.com" - }.each do |pattern, string| - file = File.join(dir, string) - mount = File.join(dir, pattern) - File.open(file, "w") do |f| f.puts "yayness: #{string}" end - name = "name" - obj = nil - assert_nothing_raised { - obj = server.mount(mount, name) - } - obj.allow "*" - - ret = nil - assert_nothing_raised do - ret = server.list("/name", :manage, false, false, host, ip) - end - - assert_equal("/\tfile", ret) - - assert_nothing_raised do - ret = server.describe("/name", :manage, host, ip) - end - assert(ret =~ /\tfile\t/, "Did not get valid a description (#{ret.inspect})") - - assert_nothing_raised do - ret = server.retrieve("/name", :manage, host, ip) - end - - assert_equal(ret, File.read(file)) - - server.umount(name) - - File.unlink(file) - end - end - - # Test the default modules fileserving - def test_modules_default - moddir = tempfile - Dir.mkdir(moddir) - mounts = {} - Puppet[:modulepath] = moddir - - mods = %w{green red}.collect do |name| - path = File::join(moddir, name, Puppet::Module::FILES) - FileUtils::mkdir_p(path) - if name == "green" - file = File::join(path, "test.txt") - File::open(file, "w") { |f| f.print name } - end - - Puppet::Module::find(name) - end - - conffile = tempfile - - File.open(conffile, "w") { |f| f.puts "# a test config file" } - - # create a server with the file - server = nil - assert_nothing_raised { - - server = Puppet::Network::Handler::FileServer.new( - - :Local => false , - - :Config => conffile - ) - } - - mods.each do |mod| - mount = "/#{mod.name}/" - list = nil - assert_nothing_raised { - list = server.list(mount, :manage, true, false) - } - list = list.split("\n") - if mod.name == "green" - assert_equal(2, list.size) - assert_equal("/\tdirectory", list[0]) - assert_equal("/test.txt\tfile", list[1]) - else - assert_equal(1, list.size) - assert_equal("/\tdirectory", list[0]) - end - - assert_nothing_raised("Host 'allow' denied #{mount}") { - server.list(mount, :manage, true, false, 'allow.example.com', "192.168.0.1") - } - end - end - - # Test that configuring deny/allow for modules works - def test_modules_config - moddir = tempfile - Dir.mkdir(moddir) - mounts = {} - Puppet[:modulepath] = moddir - - path = File::join(moddir, "amod", Puppet::Module::FILES) - file = File::join(path, "test.txt") - FileUtils::mkdir_p(path) - File::open(file, "w") { |f| f.print "Howdy" } - - mod = Puppet::Module::find("amod") - - conffile = tempfile - @@tmpfiles << conffile - - File.open(conffile, "w") { |f| - f.print "# a test config file -[modules] - path #{basedir}/thing - allow 192.168.0.* -" - } - - # create a server with the file - server = nil - assert_nothing_raised { - - server = Puppet::Network::Handler::FileServer.new( - - :Local => false, - - :Config => conffile - ) - } - - list = nil - mount = "/#{mod.name}/" - assert_nothing_raised { - list = server.list(mount, :manage, true, false) - } - - assert_nothing_raised { - list.split("\n").each { |line| - file, type = line.split("\t") - server.describe(mount + file) - } - } - - assert_describe(mount, file, server) - - # now let's check that things are being correctly forbidden - - assert_raise( - Puppet::AuthorizationError, - - "Host 'deny' allowed #{mount}") { - server.list(mount, :manage, true, false, 'deny.example.com', "192.168.1.1") - } - assert_nothing_raised("Host 'allow' denied #{mount}") { - server.list(mount, :manage, true, false, 'allow.example.com', "192.168.0.1") - } - end - - # Make sure we successfully throw errors -- someone ran into this with - # 0.22.4. - def test_failures - # create a server with the file - server = nil - - config = tempfile - [ - "[this is invalid]\nallow one.two.com", # invalid name - "[valid]\nallow *.testing something.com", # invalid allow - "[valid]\nallow one.two.com\ndeny *.testing something.com", # invalid deny - ].each do |failer| - File.open(config, "w") { |f| f.puts failer } - assert_raise(Puppet::Network::Handler::FileServerError, "Did not fail on #{failer.inspect}") { - - server = Puppet::Network::Handler::FileServer.new( - - :Local => false, - - :Config => config - ) - } - end - end - - def test_can_start_without_configuration - Puppet[:fileserverconfig] = tempfile - assert_nothing_raised("Could not create fileserver when configuration is absent") do - server = Puppet::Network::Handler::FileServer.new( - :Local => false - ) - end - end - - def test_creates_default_mounts_when_no_configuration_is_available - Puppet[:fileserverconfig] = tempfile - server = Puppet::Network::Handler::FileServer.new(:Local => false) - - assert(server.mounted?("plugins"), "Did not create default plugins mount when missing configuration file") - assert(server.mounted?("modules"), "Did not create default modules mount when missing configuration file") - end -end - - diff --git a/test/network/handler/report.rb b/test/network/handler/report.rb deleted file mode 100755 index d0c223878..000000000 --- a/test/network/handler/report.rb +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') - -require 'puppettest' -require 'puppet/network/handler/report' -require 'puppettest/reporttesting' - -class TestReportServer < Test::Unit::TestCase - include PuppetTest - include PuppetTest::Reporttesting - - Report = Puppet::Network::Handler.report - Puppet::Util.logmethods(self) - - def mkserver - server = nil - assert_nothing_raised { - server = Puppet::Network::Handler.report.new - } - server - end - - def mkclient(server = nil) - server ||= mkserver - client = nil - assert_nothing_raised { - client = Puppet::Network::Client.report.new(:Report => server) - } - - client - end - - def test_process - server = Puppet::Network::Handler.report.new - - # We have to run multiple reports to make sure there's no conflict - reports = [] - $run = [] - 2.times do |i| - name = "processtest#{i}" - reports << name - - Report.newreport(name) do - def process - $run << self.report_name - end - end - end - Puppet[:reports] = reports.collect { |r| r.to_s }.join(",") - - report = fakereport - - retval = nil - assert_nothing_raised { - retval = server.send(:process, YAML.dump(report)) - } - - reports.each do |name| - assert($run.include?(name.intern), "Did not run #{name}") - end - - # Now make sure our server doesn't die on missing reports - Puppet[:reports] = "fakereport" - assert_nothing_raised { - retval = server.send(:process, YAML.dump(report)) - } - end - - def test_reports - Puppet[:reports] = "myreport" - - # Create a server - server = Puppet::Network::Handler.report.new - - {"myreport" => ["myreport"], - " fake, another, yay " => ["fake", "another", "yay"] - }.each do |str, ary| - Puppet[:reports] = str - assert_equal(ary, server.send(:reports)) - end - end -end diff --git a/test/network/handler/runner.rb b/test/network/handler/runner.rb deleted file mode 100755 index d03fb2c89..000000000 --- a/test/network/handler/runner.rb +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') - -require 'puppettest' -require 'puppet/network/handler/runner' - -class TestHandlerRunner < Test::Unit::TestCase - include PuppetTest - - def test_it_calls_agent_runner - runner = mock 'runner' - Puppet::Run.expects(:new).with(:tags => "mytags", :ignoreschedules => true, :background => false).returns runner - runner.expects(:run) - runner.expects(:status).returns "yay" - - - assert_equal("yay", Puppet::Network::Handler.runner.new.run("mytags", true, true)) - end -end diff --git a/test/network/xmlrpc/processor.rb b/test/network/xmlrpc/processor.rb deleted file mode 100755 index 379b34a85..000000000 --- a/test/network/xmlrpc/processor.rb +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') - -require 'puppettest' -require 'puppet/network/xmlrpc/processor' -require 'mocha' - -class TestXMLRPCProcessor < Test::Unit::TestCase - include PuppetTest - class BaseProcessor - def add_handler(interface, handler) - @handlers ||= {} - @handlers[interface] = handler - end - end - - # We use a base class just so super() works with add_handler. - class Processor < BaseProcessor - include Puppet::Network::XMLRPCProcessor - - def set_service_hook(&block) - meta_def(:service, &block) - end - end - - def setup - super - Puppet::Util::SUIDManager.stubs(:asuser).yields - @processor = Processor.new - end - - def test_handlers - ca = Puppet::Network::Handler.ca - @processor.send(:setup_processor) - assert(! @processor.handler_loaded?(:ca), "already have ca handler loaded") - assert_nothing_raised do - @processor.add_handler(ca.interface, ca.new) - end - - assert(@processor.handler_loaded?(:puppetca), "ca handler not loaded by symbol") - assert(@processor.handler_loaded?("puppetca"), "ca handler not loaded by string") - end - - def test_process - ca = Puppet::Network::Handler.ca - @processor.send(:setup_processor) - assert_nothing_raised do - @processor.add_handler(ca.interface, ca.new) - end - - fakeparser = Class.new do - def parseMethodCall(data) - data - end - end - - request = Puppet::Network::ClientRequest.new("fake", "192.168.0.1", false) - request.handler = "myhandler" - request.method = "mymethod" - - @processor.expects(:parser).returns(fakeparser.new) - - request.expects(:handler=).with("myhandler") - request.expects(:method=).with("mymethod") - - @processor.stubs(:verify) - @processor.expects(:handle).with(request.call, "params", request.name, request.ip) - - @processor.send(:process, ["myhandler.mymethod", ["params"]], request) - end - - def test_setup_processor - @processor.expects(:set_service_hook) - @processor.send(:setup_processor) - end -end - - diff --git a/test/network/xmlrpc/server.rb b/test/network/xmlrpc/server.rb deleted file mode 100755 index 0653f009e..000000000 --- a/test/network/xmlrpc/server.rb +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') - -require 'puppettest' -require 'puppet/network/xmlrpc/server' -require 'mocha' - -class TestXMLRPCServer < Test::Unit::TestCase - def setup - super - assert_nothing_raised do - @server = Puppet::Network::XMLRPCServer.new - end - end - - def test_initialize - assert(@server.get_service_hook, "no service hook defined") - - assert_nothing_raised("Did not init @loadedhandlers") do - assert(! @server.handler_loaded?(:puppetca), "server thinks handlers are loaded") - end - end -end - - diff --git a/test/network/xmlrpc/webrick_servlet.rb b/test/network/xmlrpc/webrick_servlet.rb deleted file mode 100755 index f2faf09ec..000000000 --- a/test/network/xmlrpc/webrick_servlet.rb +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') - -require 'puppettest' -require 'puppettest/support/utils' -require 'puppet/network/xmlrpc/webrick_servlet' -require 'mocha' - -class TestXMLRPCWEBrickServlet < Test::Unit::TestCase - include PuppetTest - def test_basics - # Make sure we're doing things as our user info, rather than puppet/puppet - setme - set_mygroup - Puppet[:user] = @me - Puppet[:group] = @mygroup - servlet = nil - ca = Puppet::Network::Handler.ca.new - - assert_nothing_raised("Could not create servlet") do - servlet = Puppet::Network::XMLRPC::WEBrickServlet.new([ca]) - end - - assert(servlet.get_service_hook, "service hook was not set up") - - - assert( - servlet.handler_loaded?(:puppetca), - - "Did not load handler") - end -end - -