diff --git a/lib/puppet/application/kick.rb b/lib/puppet/application/kick.rb index 4f3ed1802..72f0608f6 100644 --- a/lib/puppet/application/kick.rb +++ b/lib/puppet/application/kick.rb @@ -1,337 +1,335 @@ require 'puppet/application' class Puppet::Application::Kick < Puppet::Application should_not_parse_config attr_accessor :hosts, :tags, :classes option("--all","-a") option("--foreground","-f") option("--debug","-d") option("--ping","-P") option("--test") option("--host HOST") do |arg| @hosts << arg end option("--tag TAG", "-t") do |arg| @tags << arg end option("--class CLASS", "-c") do |arg| @classes << arg end option("--no-fqdn", "-n") do |arg| options[:fqdn] = false end option("--parallel PARALLEL", "-p") do |arg| begin options[:parallel] = Integer(arg) rescue $stderr.puts "Could not convert #{arg.inspect} to an integer" exit(23) end end def help <<-HELP puppet-kick(8) -- Remotely control puppet agent ======== SYNOPSIS -------- Trigger a puppet agent run on a set of hosts. USAGE ----- puppet kick [-a|--all] [-c|--class ] [-d|--debug] [-f|--foreground] [-h|--help] [--host ] [--no-fqdn] [--ignoreschedules] [-t|--tag ] [--test] [-p|--ping] [ [...]] DESCRIPTION ----------- This script can be used to connect to a set of machines running 'puppet agent' and trigger them to run their configurations. The most common usage would be to specify a class of hosts and a set of tags, and 'puppet kick' would look up in LDAP all of the hosts matching that class, then connect to each host and trigger a run of all of the objects with the specified tags. If you are not storing your host configurations in LDAP, you can specify hosts manually. You will most likely have to run 'puppet kick' as root to get access to the SSL certificates. 'puppet kick' reads 'puppet master''s configuration file, so that it can copy things like LDAP settings. USAGE NOTES ----------- Puppet kick is useless unless puppet agent is listening for incoming connections and allowing access to the `run` endpoint. This entails starting the agent with `listen = true` in its puppet.conf file, and allowing access to the `/run` path in its auth.conf file; see `http://docs.puppetlabs.com/guides/rest_auth_conf.html` for more details. Additionally, due to a known bug, you must make sure a namespaceauth.conf file exists in puppet agent's $confdir. This file will not be consulted, and may be left empty. 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/latest/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'. * --all: Connect to all available hosts. Requires LDAP support at this point. * --class: Specify a class of machines to which to connect. This only works if you have LDAP configured, at the moment. * --debug: Enable full debugging. * --foreground: Run each configuration in the foreground; that is, when connecting to a host, do not return until the host has finished its run. The default is false. * --help: Print this help message * --host: A specific host to which to connect. This flag can be specified more than once. * --ignoreschedules: Whether the client should ignore schedules when running its configuration. This can be used to force the client to perform work it would not normally perform so soon. The default is false. * --parallel: How parallel to make the connections. Parallelization is provided by forking for each client to which to connect. The default is 1, meaning serial execution. * --tag: Specify a tag for selecting the objects to apply. Does not work with the --test option. * --test: Print the hosts you would connect to but do not actually connect. This option requires LDAP support at this point. * --ping: Do a ICMP echo against the target host. Skip hosts that don't respond to ping. EXAMPLE ------- $ sudo puppet kick -p 10 -t remotefile -t webserver host1 host2 AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end def run_command @hosts += command_line.args options[:test] ? test : main end def test puts "Skipping execution in test mode" exit(0) end def main - require 'puppet/network/client' - Puppet.warning "Failed to load ruby LDAP library. LDAP functionality will not be available" unless Puppet.features.ldap? require 'puppet/util/ldap/connection' todo = @hosts.dup failures = [] # Now do the actual work go = true while go # If we don't have enough children in process and we still have hosts left to # do, then do the next host. if @children.length < options[:parallel] and ! todo.empty? host = todo.shift pid = fork do run_for_host(host) end @children[pid] = host else # Else, see if we can reap a process. begin pid = Process.wait if host = @children[pid] # Remove our host from the list of children, so the parallelization # continues working. @children.delete(pid) failures << host if $CHILD_STATUS.exitstatus != 0 print "#{host} finished with exit code #{$CHILD_STATUS.exitstatus}\n" else $stderr.puts "Could not find host for PID #{pid} with status #{$CHILD_STATUS.exitstatus}" end rescue Errno::ECHILD # There are no children left, so just exit unless there are still # children left to do. next unless todo.empty? if failures.empty? puts "Finished" exit(0) else puts "Failed: #{failures.join(", ")}" exit(3) end end end end end def run_for_host(host) if options[:ping] out = %x{ping -c 1 #{host}} unless $CHILD_STATUS == 0 $stderr.print "Could not contact #{host}\n" exit($CHILD_STATUS) end end require 'puppet/run' Puppet::Run.indirection.terminus_class = :rest port = Puppet[:puppetport] url = ["https://#{host}:#{port}", "production", "run", host].join('/') print "Triggering #{host}\n" begin run_options = { :tags => @tags, :background => ! options[:foreground], :ignoreschedules => options[:ignoreschedules] } run = Puppet::Run.indirection.save(Puppet::Run.new( run_options ), url) puts "Getting status" result = run.status puts "status is #{result}" rescue => detail puts detail.backtrace if Puppet[:trace] $stderr.puts "Host #{host} failed: #{detail}\n" exit(2) end case result when "success"; exit(0) when "running" $stderr.puts "Host #{host} is already running" exit(3) else $stderr.puts "Host #{host} returned unknown answer '#{result}'" exit(12) end end def initialize(*args) super @hosts = [] @classes = [] @tags = [] end def preinit [:INT, :TERM].each do |signal| Signal.trap(signal) do $stderr.puts "Cancelling" exit(1) end end options[:parallel] = 1 options[:verbose] = true options[:fqdn] = true options[:ignoreschedules] = false options[:foreground] = false end def setup if options[:debug] Puppet::Util::Log.level = :debug else Puppet::Util::Log.level = :info end # Now parse the config Puppet.parse_config if Puppet[:node_terminus] == "ldap" and (options[:all] or @classes) if options[:all] @hosts = Puppet::Node.indirection.search("whatever", :fqdn => options[:fqdn]).collect { |node| node.name } puts "all: #{@hosts.join(", ")}" else @hosts = [] @classes.each do |klass| list = Puppet::Node.indirection.search("whatever", :fqdn => options[:fqdn], :class => klass).collect { |node| node.name } puts "#{klass}: #{list.join(", ")}" @hosts += list end end elsif ! @classes.empty? $stderr.puts "You must be using LDAP to specify host classes" exit(24) end @children = {} # If we get a signal, then kill all of our children and get out. [:INT, :TERM].each do |signal| Signal.trap(signal) do Puppet.notice "Caught #{signal}; shutting down" @children.each do |pid, host| Process.kill("INT", pid) end waitall exit(1) end end end end diff --git a/lib/puppet/network/client.rb b/lib/puppet/network/client.rb deleted file mode 100644 index f9c4c5fea..000000000 --- a/lib/puppet/network/client.rb +++ /dev/null @@ -1,174 +0,0 @@ -# the available clients - -require 'puppet' -require 'puppet/network/xmlrpc/client' -require 'puppet/util/subclass_loader' -require 'puppet/util/methodhelper' -require 'puppet/sslcertificates/support' - -require 'puppet/network/handler' - -require 'net/http' - -# Some versions of ruby don't have this method defined, which basically causes -# us to never use ssl. Yay. -class Net::HTTP - def use_ssl? - if defined?(@use_ssl) - @use_ssl - else - false - end - end - - # JJM: This is a "backport" of sorts to older ruby versions which - # do not have this accessor. See #896 for more information. - attr_accessor :enable_post_connection_check unless Net::HTTP.method_defined? "enable_post_connection_check" -end - -# The base class for all of the clients. Many clients just directly -# call methods, but some of them need to do some extra work or -# provide a different interface. -class Puppet::Network::Client - Client = self - include Puppet::Util - extend Puppet::Util::SubclassLoader - include Puppet::Util::MethodHelper - - # This handles reading in the key and such-like. - include Puppet::SSLCertificates::Support - - attr_accessor :schedule, :lastrun, :local, :stopping - - attr_reader :driver - - # Set up subclass loading - handle_subclasses :client, "puppet/network/client" - - # Determine what clients look for when being passed an object for local - # client/server stuff. E.g., you could call Client::CA.new(:CA => ca). - def self.drivername - @drivername ||= self.name - end - - # Figure out the handler for our client. - def self.handler - @handler ||= Puppet::Network::Handler.handler(self.name) - end - - # The class that handles xmlrpc interaction for us. - def self.xmlrpc_client - @xmlrpc_client ||= Puppet::Network::XMLRPCClient.handler_class(self.handler) - end - - # Create our client. - def initialize(hash) - # to whom do we connect? - @server = nil - - if hash.include?(:Cache) - @cache = hash[:Cache] - else - @cache = true - end - - driverparam = self.class.drivername - if hash.include?(:Server) - args = {:Server => hash[:Server]} - @server = hash[:Server] - args[:Port] = hash[:Port] || Puppet[:masterport] - - @driver = self.class.xmlrpc_client.new(args) - - self.read_cert - - @local = false - elsif hash.include?(driverparam) - @driver = hash[driverparam] - if @driver == true - @driver = self.class.handler.new - end - @local = true - else - raise Puppet::Network::ClientError, "#{self.class} must be passed a Server or #{driverparam}" - end - end - - # Are we a local client? - def local? - if @local - true - else - false - end - end - - # Make sure we set the driver up when we read the cert in. - def recycle_connection - @driver.recycle_connection if @driver.respond_to?(:recycle_connection) - end - - # A wrapper method to run and then store the last run time - def runnow - if self.stopping - Puppet.notice "In shutdown progress; skipping run" - return - end - begin - self.run - self.lastrun = Time.now.to_i - rescue => detail - puts detail.backtrace if Puppet[:trace] - Puppet.err "Could not run #{self.class}: #{detail}" - end - end - - def run - raise Puppet::DevError, "Client type #{self.class} did not override run" - end - - def scheduled? - if sched = self.schedule - return sched.match?(self.lastrun) - else - return true - end - end - - def shutdown - if self.stopping - Puppet.notice "Already in shutdown" - else - self.stopping = true - Puppet::Util::Storage.store if self.respond_to? :running? and self.running? - rmpidfile - end - end - - # Start listening for events. We're pretty much just listening for - # timer events here. - def start - # Create our timer. Puppet will handle observing it and such. - - timer = Puppet.newtimer( - - :interval => Puppet[:runinterval], - :tolerance => 1, - - :start? => true - ) do - begin - self.runnow if self.scheduled? - rescue => detail - puts detail.backtrace if Puppet[:trace] - Puppet.err "Could not run client; got otherwise uncaught exception: #{detail}" - end - end - - # Run once before we start following the timer - self.runnow - end - - require 'puppet/network/client/proxy' -end - diff --git a/lib/puppet/network/client/ca.rb b/lib/puppet/network/client/ca.rb deleted file mode 100644 index 4c0177dfe..000000000 --- a/lib/puppet/network/client/ca.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'puppet/network/client' - -# Request a certificate from the remote system. -class Puppet::Network::Client::CA < Puppet::Network::Client - class InvalidCertificate < Puppet::Error; end - - def initialize(options = {}) - options = symbolize_options(options) - unless options.include?(:Server) or options.include?(:CA) - options[:Server] = Puppet[:ca_server] - options[:Port] = Puppet[:ca_port] - end - super(options) - end - - # This client is really only able to request certificates for the - # current host. It uses the Puppet.settings settings to figure everything out. - def request_cert - Puppet.settings.use(:main, :ssl) - - if cert = read_cert - return cert - end - - begin - cert, cacert = @driver.getcert(csr.to_pem) - rescue => detail - puts detail.backtrace if Puppet[:trace] - raise Puppet::Error.new("Certificate retrieval failed: #{detail}") - end - - if cert.nil? or cert == "" - return nil - end - - begin - @cert = OpenSSL::X509::Certificate.new(cert) - @cacert = OpenSSL::X509::Certificate.new(cacert) - rescue => detail - raise InvalidCertificate.new( - "Invalid certificate: #{detail}" - ) - end - - unless @cert.check_private_key(key) - raise InvalidCertificate, "Certificate does not match private key. Try 'puppetca --clean #{Puppet[:certname]}' on the server." - end - - # Only write the cert out if it passes validating. - Puppet.settings.write(:hostcert) do |f| f.print cert end - Puppet.settings.write(:localcacert) do |f| f.print cacert end - - @cert - end -end - diff --git a/lib/puppet/network/client/file.rb b/lib/puppet/network/client/file.rb deleted file mode 100644 index caafb750c..000000000 --- a/lib/puppet/network/client/file.rb +++ /dev/null @@ -1,6 +0,0 @@ -class Puppet::Network::Client::File < Puppet::Network::Client::ProxyClient - @handler = Puppet::Network::Handler.handler(:fileserver) - @drivername = :FileServer - self.mkmethods -end - diff --git a/lib/puppet/network/client/proxy.rb b/lib/puppet/network/client/proxy.rb deleted file mode 100644 index 1d565a83a..000000000 --- a/lib/puppet/network/client/proxy.rb +++ /dev/null @@ -1,27 +0,0 @@ -# unlike the other client classes (again, this design sucks) this class -# is basically just a proxy class -- it calls its methods on the driver -# and that's about it -class Puppet::Network::Client::ProxyClient < Puppet::Network::Client - def self.mkmethods - interface = self.handler.interface - namespace = interface.prefix - - - interface.methods.each { |ary| - method = ary[0] - Puppet.debug "#{self}: defining #{namespace}.#{method}" - define_method(method) { |*args| - begin - @driver.send(method, *args) - rescue XMLRPC::FaultException => detail - #Puppet.err "Could not call %s.%s: %s" % - # [namespace, method, detail.faultString] - #raise NetworkClientError, - # "XMLRPC Error: #{detail.faultString}" - raise NetworkClientError, detail.faultString - end - } - } - end -end - diff --git a/lib/puppet/network/client/report.rb b/lib/puppet/network/client/report.rb deleted file mode 100644 index c9afaf969..000000000 --- a/lib/puppet/network/client/report.rb +++ /dev/null @@ -1,26 +0,0 @@ -class Puppet::Network::Client::Report < Puppet::Network::Client - @handler = Puppet::Network::Handler.handler(:report) - - def initialize(hash = {}) - hash[:Report] = self.class.handler.new if hash.include?(:Report) - - super(hash) - end - - # Send our report. We get the transaction report and convert it to YAML - # as appropriate. - def report(transreport) - report = YAML.dump(transreport) - - report = CGI.escape(report) unless self.local - - # Now send the report - file = nil - benchmark(:info, "Sent transaction report") do - file = @driver.report(report) - end - - file - end -end - diff --git a/lib/puppet/network/client/runner.rb b/lib/puppet/network/client/runner.rb deleted file mode 100644 index a4596a753..000000000 --- a/lib/puppet/network/client/runner.rb +++ /dev/null @@ -1,10 +0,0 @@ -class Puppet::Network::Client::Runner < Puppet::Network::Client::ProxyClient - self.mkmethods - - def initialize(hash = {}) - hash[:Runner] = self.class.handler.new if hash.include?(:Runner) - - super(hash) - end -end - diff --git a/lib/puppet/network/client/status.rb b/lib/puppet/network/client/status.rb deleted file mode 100644 index c24c7e3d0..000000000 --- a/lib/puppet/network/client/status.rb +++ /dev/null @@ -1,4 +0,0 @@ -class Puppet::Network::Client::Status < Puppet::Network::Client::ProxyClient - self.mkmethods -end - diff --git a/lib/puppet/network/handler/runner.rb b/lib/puppet/network/handler/runner.rb index b02d3a548..24b3dea97 100755 --- a/lib/puppet/network/handler/runner.rb +++ b/lib/puppet/network/handler/runner.rb @@ -1,31 +1,32 @@ require 'puppet/run' +require 'puppet/network/handler' 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/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 ce0401ad2..000000000 --- a/lib/puppet/network/http_server/mongrel.rb +++ /dev/null @@ -1,130 +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/http_server/webrick.rb b/lib/puppet/network/http_server/webrick.rb deleted file mode 100644 index 1f4b3b0e7..000000000 --- a/lib/puppet/network/http_server/webrick.rb +++ /dev/null @@ -1,155 +0,0 @@ -require 'puppet' -require 'webrick' -require 'webrick/https' -require 'fcntl' - -require 'puppet/sslcertificates/support' -require 'puppet/network/xmlrpc/webrick_servlet' -require 'puppet/network/http_server' -require 'puppet/network/client' -require 'puppet/network/handler' - -module Puppet - class ServerError < RuntimeError; end - module Network - # The old-school, pure ruby webrick server, which is the default serving - # mechanism. - class HTTPServer::WEBrick < WEBrick::HTTPServer - include Puppet::SSLCertificates::Support - - # Read the CA cert and CRL and populate an OpenSSL::X509::Store - # with them, with flags appropriate for checking client - # certificates for revocation - def x509store - unless File.exist?(Puppet[:cacrl]) - # No CRL, no store needed - return nil - end - crl = OpenSSL::X509::CRL.new(File.read(Puppet[:cacrl])) - store = OpenSSL::X509::Store.new - store.purpose = OpenSSL::X509::PURPOSE_ANY - store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK if Puppet.settings[:certificate_revocation] - raise Puppet::Error, "Could not find CA certificate" unless self.ca_cert - - store.add_file(Puppet[:localcacert]) - store.add_crl(crl) - store - end - - # Set up the http log. - def httplog - args = [] - - # yuck; separate http logs - file = nil - 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 = open(file, "a+") - file_io.sync - file_io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) - - args << file_io - args << WEBrick::Log::DEBUG if Puppet[:debug] - - log = WEBrick::Log.new(*args) - - - log - end - - # Create our server, yo. - def initialize(hash = {}) - Puppet.info "Starting server for Puppet version #{Puppet.version}" - - if handlers = hash[:Handlers] - handler_instances = setup_handlers(handlers) - else - raise ServerError, "A server must have handlers" - end - - unless self.read_cert - if ca = handler_instances.find { |handler| handler.is_a?(Puppet::Network::Handler.ca) } - request_cert(ca) - else - raise Puppet::Error, "No certificate and no CA; cannot get cert" - end - end - - setup_webrick(hash) - - begin - super(hash) - rescue => detail - puts detail.backtrace if Puppet[:trace] - raise Puppet::Error, "Could not start WEBrick: #{detail}" - end - - # make sure children don't inherit the sockets - listeners.each { |sock| - sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) - } - - Puppet.info "Listening on port #{hash[:Port]}" - - # this creates a new servlet for every connection, - # but all servlets have the same list of handlers - # thus, the servlets can have their own state -- passing - # around the requests and such -- but the handlers - # have a global state - - # mount has to be called after the server is initialized - servlet = Puppet::Network::XMLRPC::WEBrickServlet.new( handler_instances) - self.mount("/RPC2", servlet) - end - - # Create a ca client to set up our cert for us. - def request_cert(ca) - client = Puppet::Network::Client.ca.new(:CA => ca) - raise Puppet::Error, "Could get certificate" unless client.request_cert - end - - # Create all of our handler instances. - def setup_handlers(handlers) - raise ServerError, "Handlers must have arguments" unless handlers.is_a?(Hash) - - handlers.collect { |handler, args| - hclass = nil - unless hclass = Puppet::Network::Handler.handler(handler) - raise ServerError, "Invalid handler #{handler}" - end - hclass.new(args) - } - end - - # Handle all of the many webrick arguments. - def setup_webrick(hash) - hash[:Port] ||= Puppet[:masterport] - hash[:Logger] ||= self.httplog - hash[:AccessLog] ||= [ - [ self.httplog, WEBrick::AccessLog::COMMON_LOG_FORMAT ], - [ self.httplog, WEBrick::AccessLog::REFERER_LOG_FORMAT ] - ] - - hash[:SSLCertificateStore] = x509store - hash[:SSLCertificate] = self.cert - hash[:SSLPrivateKey] = self.key - hash[:SSLStartImmediately] = true - hash[:SSLEnable] = true - hash[:SSLCACertificateFile] = Puppet[:localcacert] - hash[:SSLVerifyClient] = OpenSSL::SSL::VERIFY_PEER - hash[:SSLCertName] = nil - - if addr = Puppet[:bindaddress] and addr != "" - hash[:BindAddress] = addr - end - end - end - end -end - diff --git a/lib/puppet/network/xmlrpc/client.rb b/lib/puppet/network/xmlrpc/client.rb deleted file mode 100644 index ca6ac60ef..000000000 --- a/lib/puppet/network/xmlrpc/client.rb +++ /dev/null @@ -1,211 +0,0 @@ -require 'puppet/sslcertificates' -require 'puppet/network/http_pool' -require 'openssl' -require 'puppet/external/base64' - -require 'xmlrpc/client' -require 'net/https' -require 'yaml' - -module Puppet::Network - class ClientError < Puppet::Error; end - class XMLRPCClientError < Puppet::Error; end - class XMLRPCClient < ::XMLRPC::Client - - attr_accessor :puppet_server, :puppet_port - @clients = {} - - class << self - include Puppet::Util - include Puppet::Util::ClassGen - end - - # Create a netclient for each handler - def self.mkclient(handler) - interface = handler.interface - namespace = interface.prefix - - # Create a subclass for every client type. This is - # so that all of the methods are on their own class, - # so that their namespaces can define the same methods if - # they want. - constant = handler.name.to_s.capitalize - name = namespace.downcase - newclient = genclass(name, :hash => @clients, :constant => constant) - - interface.methods.each { |ary| - method = ary[0] - newclient.send(:define_method,method) { |*args| - make_rpc_call(namespace, method, *args) - } - } - - newclient - end - - def self.handler_class(handler) - @clients[handler] || self.mkclient(handler) - end - - class ErrorHandler - def initialize(&block) - singleton_class.define_method(:execute, &block) - end - end - - # Use a class variable so all subclasses have access to it. - @@error_handlers = {} - - def self.error_handler(exception) - if handler = @@error_handlers[exception.class] - return handler - else - return @@error_handlers[:default] - end - end - - def self.handle_error(*exceptions, &block) - handler = ErrorHandler.new(&block) - - exceptions.each do |exception| - @@error_handlers[exception] = handler - end - end - - handle_error(OpenSSL::SSL::SSLError) do |client, detail, namespace, method| - if detail.message =~ /bad write retry/ - Puppet.warning "Transient SSL write error; restarting connection and retrying" - client.recycle_connection - return :retry - end - ["certificate verify failed", "hostname was not match", "hostname not match"].each do |str| - Puppet.warning "Certificate validation failed; consider using the certname configuration option" if detail.message.include?(str) - end - raise XMLRPCClientError, "Certificates were not trusted: #{detail}" - end - - handle_error(:default) do |client, detail, namespace, method| - if detail.message.to_s =~ /^Wrong size\. Was \d+, should be \d+$/ - Puppet.warning "XMLRPC returned wrong size. Retrying." - return :retry - end - Puppet.err "Could not call #{namespace}.#{method}: #{detail.inspect}" - error = XMLRPCClientError.new(detail.to_s) - error.set_backtrace detail.backtrace - raise error - end - - handle_error(OpenSSL::SSL::SSLError) do |client, detail, namespace, method| - if detail.message =~ /bad write retry/ - Puppet.warning "Transient SSL write error; restarting connection and retrying" - client.recycle_connection - return :retry - end - ["certificate verify failed", "hostname was not match", "hostname not match"].each do |str| - Puppet.warning "Certificate validation failed; consider using the certname configuration option" if detail.message.include?(str) - end - raise XMLRPCClientError, "Certificates were not trusted: #{detail}" - end - - handle_error(::XMLRPC::FaultException) do |client, detail, namespace, method| - raise XMLRPCClientError, detail.faultString - end - - handle_error(Errno::ECONNREFUSED) do |client, detail, namespace, method| - msg = "Could not connect to #{client.host} on port #{client.port}" - raise XMLRPCClientError, msg - end - - handle_error(SocketError) do |client, detail, namespace, method| - Puppet.err "Could not find server #{@host}: #{detail}" - error = XMLRPCClientError.new("Could not find server #{client.host}") - error.set_backtrace detail.backtrace - raise error - end - - handle_error(Errno::EPIPE, EOFError) do |client, detail, namespace, method| - Puppet.info "Other end went away; restarting connection and retrying" - client.recycle_connection - return :retry - end - - handle_error(Timeout::Error) do |client, detail, namespace, method| - Puppet.err "Connection timeout calling #{namespace}.#{method}: #{detail}" - error = XMLRPCClientError.new("Connection Timeout") - error.set_backtrace(detail.backtrace) - raise error - end - - def make_rpc_call(namespace, method, *args) - Puppet.debug "Calling #{namespace}.#{method}" - begin - call("#{namespace}.#{method}",*args) - rescue SystemExit,NoMemoryError - raise - rescue Exception => detail - retry if self.class.error_handler(detail).execute(self, detail, namespace, method) == :retry - end - ensure - http.finish if http.started? - end - - def http - @http ||= Puppet::Network::HttpPool.http_instance(host, port, true) - end - - attr_reader :host, :port - - def initialize(hash = {}) - hash[:Path] ||= "/RPC2" - hash[:Server] ||= Puppet[:server] - hash[:Port] ||= Puppet[:masterport] - hash[:HTTPProxyHost] ||= Puppet[:http_proxy_host] - hash[:HTTPProxyPort] ||= Puppet[:http_proxy_port] - - if "none" == hash[:HTTPProxyHost] - hash[:HTTPProxyHost] = nil - hash[:HTTPProxyPort] = nil - end - - - super( - - hash[:Server], - hash[:Path], - hash[:Port], - hash[:HTTPProxyHost], - hash[:HTTPProxyPort], - - nil, # user - nil, # password - true, # use_ssl - Puppet[:configtimeout] # use configured timeout (#1176) - ) - @http = Puppet::Network::HttpPool.http_instance(@host, @port) - end - - # Get rid of our existing connection, replacing it with a new one. - # This should only happen if we lose our connection somehow (e.g., an EPIPE) - # or we've just downloaded certs and we need to create new http instances - # with the certs added. - def recycle_connection - http.finish if http.started? - @http = nil - self.http # force a new one - end - - def start - @http.start unless @http.started? - rescue => detail - Puppet.err "Could not connect to server: #{detail}" - end - - def local - false - end - - def local? - false - end - end -end diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb index bfe61144d..152f63a15 100644 --- a/lib/puppet/type/file.rb +++ b/lib/puppet/type/file.rb @@ -1,803 +1,802 @@ 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/network/client' 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.expand_path ::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 path = Pathname(self[:path]) if !path.root? # Start at our parent, to avoid autorequiring ourself parents = path.parent.enum_for(:ascend) found = parents.find { |p| catalog.resource(:file, p.to_s) } found and found.to_s end 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] 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 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 # We have to hack this just a little bit, because otherwise we'll get # an error when the target and the contents are created as properties on # the far side. def to_trans(retrieve = true) obj = super obj.delete(:target) if obj[:target] == :notlink obj 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, 'w', 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/client_spec.rb b/spec/integration/network/client_spec.rb deleted file mode 100755 index 72174c75c..000000000 --- a/spec/integration/network/client_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env rspec -require 'spec_helper' - -require 'puppet/network/client' - -describe Puppet::Network::Client do - %w{ca file report runner status}.each do |name| - it "should have a #{name} client" do - Puppet::Network::Client.client(name).should be_instance_of(Class) - end - - [:name, :handler, :drivername].each do |data| - it "should have a #{data} value for the #{name} client" do - Puppet::Network::Client.client(name).send(data).should_not be_nil - end - end - end -end diff --git a/spec/integration/network/handler_spec.rb b/spec/integration/network/handler_spec.rb index dc0837c13..e6e607872 100755 --- a/spec/integration/network/handler_spec.rb +++ b/spec/integration/network/handler_spec.rb @@ -1,24 +1,24 @@ #!/usr/bin/env rspec require 'spec_helper' -require 'puppet/network/client' +require 'puppet/network/handler' describe Puppet::Network::Handler do %w{ca filebucket fileserver master 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/unit/network/xmlrpc/client_spec.rb b/spec/unit/network/xmlrpc/client_spec.rb deleted file mode 100755 index b9be0a906..000000000 --- a/spec/unit/network/xmlrpc/client_spec.rb +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env rspec -require 'puppet/network/client' - -require 'spec_helper' - -describe Puppet::Network::XMLRPCClient do - describe "when performing the rpc call" do - before do - Puppet::SSL::Host.any_instance.stubs(:certificate_matches_key?).returns true - @client = Puppet::Network::Client.report.xmlrpc_client.new - @client.stubs(:call).returns "foo" - end - - it "should call the specified namespace and method, with the specified arguments" do - @client.expects(:call).with("puppetreports.report", "eh").returns "foo" - @client.report("eh") - end - - it "should return the results from the call" do - @client.expects(:call).returns "foo" - @client.report("eh").should == "foo" - end - - it "should always close the http connection if it is still open after the call" do - http = mock 'http' - @client.stubs(:http).returns http - - http.expects(:started?).returns true - http.expects(:finish) - - @client.report("eh").should == "foo" - end - - it "should always close the http connection if it is still open after a call that raises an exception" do - http = mock 'http' - @client.stubs(:http).returns http - - @client.expects(:call).raises RuntimeError - - http.expects(:started?).returns true - http.expects(:finish) - - lambda { @client.report("eh") }.should raise_error - end - - describe "when returning the http instance" do - it "should use the http pool to create the instance" do - @client.instance_variable_set("@http", nil) - @client.expects(:host).returns "myhost" - @client.expects(:port).returns "myport" - Puppet::Network::HttpPool.expects(:http_instance).with("myhost", "myport", true).returns "http" - - @client.http.should == "http" - end - - it "should reuse existing instances" do - @client.http.should equal(@client.http) - end - end - - describe "when recycling the connection" do - it "should close the existing instance if it's open" do - http = mock 'http' - @client.stubs(:http).returns http - - http.expects(:started?).returns true - http.expects(:finish) - - @client.recycle_connection - end - - it "should force creation of a new instance" do - Puppet::Network::HttpPool.expects(:http_instance).returns "second_http" - - @client.recycle_connection - - @client.http.should == "second_http" - end - end - - describe "and an exception is raised" do - it "should raise XMLRPCClientError if XMLRPC::FaultException is raised" do - error = XMLRPC::FaultException.new("foo", "bar") - - @client.expects(:call).raises(error) - - lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) - end - - it "should raise XMLRPCClientError if Errno::ECONNREFUSED is raised" do - @client.expects(:call).raises(Errno::ECONNREFUSED) - - lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) - end - - it "should log and raise XMLRPCClientError if Timeout::Error is raised" do - Puppet.expects(:err) - @client.expects(:call).raises(Timeout::Error) - - lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) - end - - it "should log and raise XMLRPCClientError if SocketError is raised" do - Puppet.expects(:err) - @client.expects(:call).raises(SocketError) - - lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) - end - - it "should log, recycle the connection, and retry if Errno::EPIPE is raised" do - @client.expects(:call).times(2).raises(Errno::EPIPE).then.returns "eh" - - Puppet.expects(:info) - @client.expects(:recycle_connection) - - @client.report("eh") - end - - it "should log, recycle the connection, and retry if EOFError is raised" do - @client.expects(:call).times(2).raises(EOFError).then.returns "eh" - - Puppet.expects(:info) - @client.expects(:recycle_connection) - - @client.report("eh") - end - - it "should log and retry if an exception containing 'Wrong size' is raised" do - error = RuntimeError.new("Wrong size. Was 15, should be 30") - @client.expects(:call).times(2).raises(error).then.returns "eh" - - Puppet.expects(:warning) - - @client.report("eh") - end - - it "should raise XMLRPCClientError if OpenSSL::SSL::SSLError is raised" do - @client.expects(:call).raises(OpenSSL::SSL::SSLError) - - lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) - end - - it "should log and raise XMLRPCClientError if OpenSSL::SSL::SSLError is raised with certificate issues" do - error = OpenSSL::SSL::SSLError.new("hostname was not match") - @client.expects(:call).raises(error) - - Puppet.expects(:warning) - - lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) - end - - it "should log, recycle the connection, and retry if OpenSSL::SSL::SSLError is raised containing 'bad write retry'" do - error = OpenSSL::SSL::SSLError.new("bad write retry") - @client.expects(:call).times(2).raises(error).then.returns "eh" - - @client.expects(:recycle_connection) - - Puppet.expects(:warning) - - @client.report("eh") - end - - it "should log and raise XMLRPCClientError if any other exception is raised" do - @client.expects(:call).raises(RuntimeError) - - Puppet.expects(:err) - - lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) - end - end - end -end diff --git a/test/certmgr/certmgr.rb b/test/certmgr/certmgr.rb deleted file mode 100755 index 11ecd6307..000000000 --- a/test/certmgr/certmgr.rb +++ /dev/null @@ -1,308 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') - -require 'puppet' -require 'puppet/sslcertificates.rb' -require 'puppettest' -require 'puppettest/certificates' -require 'mocha' - -class TestCertMgr < Test::Unit::TestCase - include PuppetTest::Certificates - def setup - super - #@dir = File.join(Puppet[:certdir], "testing") - @dir = File.join(@configpath, "certest") - system("mkdir -p #{@dir}") - - Puppet::Util::SUIDManager.stubs(:asuser).yields - end - - def testCreateSelfSignedCertificate - cert = nil - name = "testing" - newcert = proc { - - Puppet::SSLCertificates::Certificate.new( - - :name => name, - - :selfsign => true - ) - } - assert_nothing_raised { - cert = newcert.call - } - assert_nothing_raised { - cert.mkselfsigned - } - - assert_raise(Puppet::Error) { - cert.mkselfsigned - } - - assert_nothing_raised { - cert.write - } - - assert(FileTest.exists?(cert.certfile)) - - assert_nothing_raised { - cert.delete - } - - assert_nothing_raised { - cert = newcert.call - } - assert_nothing_raised { - cert.mkselfsigned - } - - assert_nothing_raised { - cert.delete - } - - end - - def disabled_testCreateEncryptedSelfSignedCertificate - cert = nil - name = "testing" - keyfile = mkPassFile - assert_nothing_raised { - - cert = Puppet::SSLCertificates::Certificate.new( - - :name => name, - :selfsign => true, - - :capass => keyfile - ) - } - assert_nothing_raised { - cert.mkselfsigned - } - assert_nothing_raised { - cert.mkhash - } - - assert_raise(Puppet::Error) { - cert.mkselfsigned - } - - assert(FileTest.exists?(cert.certfile)) - assert(FileTest.exists?(cert.hash)) - - assert_nothing_raised { - cert.delete - } - - assert_nothing_raised { - cert.mkselfsigned - } - - assert_nothing_raised { - cert.delete - } - - end - - def testCreateCA - ca = nil - assert_nothing_raised { - ca = Puppet::SSLCertificates::CA.new - } - - # make the CA again and verify it doesn't fail because everything - # still exists - assert_nothing_raised { - ca = Puppet::SSLCertificates::CA.new - } - - end - - def testSignCert - ca = mkCA() - - cert = nil - assert_nothing_raised { - - cert = Puppet::SSLCertificates::Certificate.new( - - :name => "signedcertest", - :property => "TN", - :city => "Nashville", - :country => "US", - :email => "luke@madstop.com", - :org => "Puppet", - :ou => "Development", - - :encrypt => mkPassFile() - ) - - } - - assert_nothing_raised { - cert.mkcsr - } - - signedcert = nil - cacert = nil - - assert_nothing_raised { - signedcert, cacert = ca.sign(cert.csr) - } - - assert_instance_of(OpenSSL::X509::Certificate, signedcert) - assert_instance_of(OpenSSL::X509::Certificate, cacert) - - assert_nothing_raised { - cert.cert = signedcert - cert.cacert = cacert - cert.write - } - #system("find #{Puppet[:ssldir]}") - #system("cp -R #{Puppet[:ssldir]} /tmp/ssltesting") - - output = nil - assert_nothing_raised { - output = %x{openssl verify -CAfile #{Puppet[:cacert]} -purpose sslserver #{cert.certfile}} - #output = %x{openssl verify -CApath #{Puppet[:certdir]} -purpose sslserver #{cert.certfile}} - } - - assert_equal($CHILD_STATUS,0) - assert_equal(File.join(Puppet[:certdir], "signedcertest.pem: OK\n"), output) - end - - - def test_interactiveca - ca = nil - - assert_nothing_raised { - ca = Puppet::SSLCertificates::CA.new - } - - # basic initialization - hostname = "test.hostname.com" - cert = mkcert(hostname) - - # create the csr - csr = nil - assert_nothing_raised { - csr = cert.mkcsr - } - - assert_nothing_raised { - ca.storeclientcsr(csr) - } - - # store it - pulledcsr = nil - assert_nothing_raised { - pulledcsr = ca.getclientcsr(hostname) - } - - assert_equal(csr.to_pem, pulledcsr.to_pem) - - signedcert = nil - assert_nothing_raised { - signedcert, cacert = ca.sign(csr) - } - - assert_instance_of(OpenSSL::X509::Certificate, signedcert) - newsignedcert = nil - assert_nothing_raised { - newsignedcert, cacert = ca.getclientcert(hostname) - } - - assert(newsignedcert) - - assert_equal(signedcert.to_pem, newsignedcert.to_pem) - end - - def test_cafailures - ca = mkCA() - cert = cacert = nil - assert_nothing_raised { - cert, cacert = ca.getclientcert("nohost") - } - assert_nil(cert) - end - - def test_crl - ca = mkCA() - h1 = mksignedcert(ca, "host1.example.com") - h2 = mksignedcert(ca, "host2.example.com") - - assert(ca.cert.verify(ca.cert.public_key)) - assert(h1.verify(ca.cert.public_key)) - assert(h2.verify(ca.cert.public_key)) - - crl = ca.crl - assert_not_nil(crl) - - store = mkStore(ca) - assert( store.verify(ca.cert)) - assert( store.verify(h1, [ca.cert])) - assert( store.verify(h2, [ca.cert])) - - ca.revoke(h1.serial) - - oldcert = File.read(Puppet.settings[:cacert]) - oldserial = File.read(Puppet.settings[:serial]) - - # Recreate the CA from disk - ca = mkCA() - newcert = File.read(Puppet.settings[:cacert]) - newserial = File.read(Puppet.settings[:serial]) - assert_equal(oldcert, newcert, "The certs are not equal after making a new CA.") - assert_equal(oldserial, newserial, "The serials are not equal after making a new CA.") - store = mkStore(ca) - assert( store.verify(ca.cert), "Could not verify CA certs after reloading certs.") - assert(!store.verify(h1, [ca.cert]), "Incorrectly verified revoked cert.") - assert( store.verify(h2, [ca.cert]), "Could not verify certs with reloaded CA.") - - ca.revoke(h2.serial) - assert_equal(1, ca.crl.extensions.size) - - # Recreate the CA from disk - ca = mkCA() - store = mkStore(ca) - assert( store.verify(ca.cert)) - assert(!store.verify(h1, [ca.cert]), "first revoked cert passed") - assert(!store.verify(h2, [ca.cert]), "second revoked cert passed") - end - - def test_ttl - cert = mksignedcert - assert_equal(5 * 365 * 24 * 60 * 60, cert.not_after - cert.not_before) - - Puppet[:ca_ttl] = 7 * 24 * 60 * 60 - cert = mksignedcert - assert_equal(7 * 24 * 60 * 60, cert.not_after - cert.not_before) - - Puppet[:ca_ttl] = "2y" - cert = mksignedcert - assert_equal(2 * 365 * 24 * 60 * 60, cert.not_after - cert.not_before) - - Puppet[:ca_ttl] = "2y" - cert = mksignedcert - assert_equal(2 * 365 * 24 * 60 * 60, cert.not_after - cert.not_before) - - Puppet[:ca_ttl] = "1h" - cert = mksignedcert - assert_equal(60 * 60, cert.not_after - cert.not_before) - - Puppet[:ca_ttl] = "900s" - cert = mksignedcert - assert_equal(900, cert.not_after - cert.not_before) - - # This needs to be last, to make sure that setting ca_days - # overrides setting ca_ttl - Puppet[:ca_days] = 3 - cert = mksignedcert - assert_equal(3 * 24 * 60 * 60, cert.not_after - cert.not_before) - - end -end - diff --git a/test/certmgr/inventory.rb b/test/certmgr/inventory.rb deleted file mode 100755 index fa612b2c1..000000000 --- a/test/certmgr/inventory.rb +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') - -require 'puppet' -require 'puppettest/certificates' -require 'puppet/sslcertificates/inventory.rb' -require 'mocha' - -class TestCertInventory < Test::Unit::TestCase - include PuppetTest::Certificates - - Inventory = Puppet::SSLCertificates::Inventory - - def setup - super - Puppet::Util::SUIDManager.stubs(:asuser).yields - end - - def test_format - cert = mksignedcert - - format = nil - assert_nothing_raised do - format = Inventory.format(cert) - end - - - assert( - format =~ /^0x0001 \S+ \S+ #{cert.subject}/, - - "Did not create correct format") - end - - def test_init - # First create a couple of certificates - ca = mkCA - - cert1 = mksignedcert(ca, "host1.madstop.com") - cert2 = mksignedcert(ca, "host2.madstop.com") - - init = nil - assert_nothing_raised do - init = Inventory.init - end - - [cert1, cert2].each do |cert| - assert(init.include?(cert.subject.to_s), "Did not catch #{cert.subject}") - end - end - - def test_add - ca = mkCA - cert = mksignedcert(ca, "host.domain.com") - - assert_nothing_raised do - file = mock - file.expects(:puts).with do |written| - written.include? cert.subject.to_s - end - Puppet::Util::Settings.any_instance.stubs(:write) - Puppet::Util::Settings.any_instance.expects(:write). - with(:cert_inventory, 'a').yields(file) - - Puppet::SSLCertificates::Inventory.add(cert) - end - end -end - diff --git a/test/certmgr/support.rb b/test/certmgr/support.rb deleted file mode 100755 index f85d54a8a..000000000 --- a/test/certmgr/support.rb +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') - -require 'puppettest' -require 'puppet/sslcertificates/support' -require 'mocha' - -class TestCertSupport < Test::Unit::TestCase - include PuppetTest - MissingCertificate = Puppet::SSLCertificates::Support::MissingCertificate - - class CertUser - include Puppet::SSLCertificates::Support - end - - def setup - super - Puppet::Util::SUIDManager.stubs(:asuser).yields - @user = CertUser.new - @ca = Puppet::SSLCertificates::CA.new - @client = Puppet::Network::Client.ca.new(:CA => @ca) - end - - # Yay, metaprogramming - def test_keytype - [:key, :csr, :cert, :ca_cert].each do |name| - assert(Puppet::SSLCertificates::Support.method_defined?(name), "No retrieval method for #{name}") - maker = "mk_#{name}" - assert(Puppet::SSLCertificates::Support.method_defined?(maker), "No maker method for #{name}") - end - end - - def test_keys - keys = [:hostprivkey, :hostpubkey].each { |n| Puppet[n] = tempfile } - - key = nil - assert_nothing_raised do - key = @user.key - end - - assert_logged(:info, /Creating a new SSL/, "Did not log about new key") - keys.each do |file| - - assert( - FileTest.exists?(Puppet[file]), - - "Did not create #{file} key file") - end - - # Make sure it's a valid key - assert_nothing_raised("Created key is invalid") do - OpenSSL::PKey::RSA.new(File.read(Puppet[:hostprivkey])) - end - - # now make sure we can read it in - other = CertUser.new - assert_nothing_raised("Could not read key in") do - other.key - end - - assert_equal(@user.key.to_s, other.key.to_s, "Keys are not equal") - end - - def test_csr - csr = nil - assert_nothing_raised("Could not create csr") do - csr = @user.csr - end - - assert(FileTest.exists?(Puppet[:hostcsr]), "did not create csr file") - assert_instance_of(OpenSSL::X509::Request, csr) - end - - def test_cacert - @user = CertUser.new - - assert_raise(MissingCertificate, "Did not fail when missing cacert") do - @user.ca_cert - end - end - - # Fixing #1382. This test will always fail on Darwin, because its - # FS is case-insensitive. - unless Facter.value(:operatingsystem) == "Darwin" - def test_uppercase_files_are_renamed_and_read - # Write a key out to disk in a file containing upper-case. - key = OpenSSL::PKey::RSA.new(32) - should_path = Puppet[:hostprivkey] - - dir, file = File.split(should_path) - newfile = file.sub(/^([-a-z.0-9]+)\./) { $1.upcase + "."} - upper_path = File.join(dir, newfile) -p upper_path - File.open(upper_path, "w") { |f| f.print key.to_s } - - user = CertUser.new - - assert_equal(key.to_s, user.read_key.to_s, "Did not read key in from disk") - assert(! FileTest.exist?(upper_path), "Upper case file was not removed") - assert(FileTest.exist?(should_path), "File was not renamed to lower-case file") - assert_equal(key.to_s, user.read_key.to_s, "Did not read key in from disk") - end - end -end diff --git a/test/language/functions.rb b/test/language/functions.rb index 84b1b3861..b571da23b 100755 --- a/test/language/functions.rb +++ b/test/language/functions.rb @@ -1,541 +1,540 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') require 'puppet' require 'puppet/parser/parser' -require 'puppet/network/client' require 'puppettest' require 'puppettest/resourcetesting' class TestLangFunctions < Test::Unit::TestCase include PuppetTest::ParserTesting include PuppetTest::ResourceTesting def test_functions Puppet::Node::Environment.stubs(:current).returns nil assert_nothing_raised do Puppet::Parser::AST::Function.new( :name => "fakefunction", :arguments => AST::ASTArray.new( :children => [nameobj("avalue")] ) ) end assert_raise(Puppet::ParseError) do func = Puppet::Parser::AST::Function.new( :name => "fakefunction", :arguments => AST::ASTArray.new( :children => [nameobj("avalue")] ) ) func.evaluate(mkscope) end assert_nothing_raised do Puppet::Parser::Functions.newfunction(:fakefunction, :type => :rvalue) do |input| return "output #{input[0]}" end end func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "fakefunction", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [nameobj("avalue")] ) ) end scope = mkscope val = nil assert_nothing_raised do val = func.evaluate(scope) end assert_equal("output avalue", val) end def test_taggedfunction scope = mkscope scope.resource.tag("yayness") # Make sure the ast stuff does what it's supposed to {"yayness" => true, "booness" => false}.each do |tag, retval| func = taggedobj(tag, :rvalue) val = nil assert_nothing_raised do val = func.evaluate(scope) end assert_equal(retval, val, "'tagged' returned #{val} for #{tag}") end # Now make sure we correctly get tags. scope.resource.tag("resourcetag") assert(scope.function_tagged("resourcetag"), "tagged function did not catch resource tags") scope.compiler.catalog.tag("configtag") assert(scope.function_tagged("configtag"), "tagged function did not catch catalog tags") end def test_failfunction func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "fail", :ftype => :statement, :arguments => AST::ASTArray.new( :children => [stringobj("this is a failure"), stringobj("and another")] ) ) end scope = mkscope val = nil assert_raise(Puppet::ParseError) do val = func.evaluate(scope) end end def test_multipletemplates Dir.mkdir(Puppet[:templatedir]) onep = File.join(Puppet[:templatedir], "one") twop = File.join(Puppet[:templatedir], "two") File.open(onep, "w") do |f| f.puts "<%- if @one.nil? then raise '@one undefined' end -%>template <%= @one %>" end File.open(twop, "w") do |f| f.puts "template <%= @two %>" end func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "template", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [stringobj("one"), stringobj("two")] ) ) end ast = varobj("output", func) scope = mkscope # Test that our manual exception throw fails the parse assert_raise(Puppet::ParseError) do ast.evaluate(scope) end # Test that our use of an undefined instance variable does not throw # an exception, but only safely continues. scope.setvar("one", "One") assert_nothing_raised do ast.evaluate(scope) end # Ensure that we got the output we expected from that evaluation. assert_equal("template One\ntemplate \n", scope.lookupvar("output"), "Undefined template variables do not raise exceptions") # Now, fill in the last variable and make sure the whole thing # evaluates correctly. scope.setvar("two", "Two") scope.unsetvar("output") assert_nothing_raised do ast.evaluate(scope) end assert_equal( "template One\ntemplate Two\n", scope.lookupvar("output"), "Templates were not handled correctly") end # Now make sure we can fully qualify files, and specify just one def test_singletemplates template = tempfile File.open(template, "w") do |f| f.puts "template <%= @yay.nil?() ? raise('yay undefined') : @yay %>" end func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "template", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [stringobj(template)] ) ) end ast = varobj("output", func) scope = mkscope assert_raise(Puppet::ParseError) do ast.evaluate(scope) end scope.setvar("yay", "this is yay") assert_nothing_raised do ast.evaluate(scope) end assert_equal( "template this is yay\n", scope.lookupvar("output"), "Templates were not handled correctly") end # Make sure that legacy template variable access works as expected. def test_legacyvariables template = tempfile File.open(template, "w") do |f| f.puts "template <%= deprecated %>" end func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "template", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [stringobj(template)] ) ) end ast = varobj("output", func) # Verify that we get an exception using old-style accessors. scope = mkscope assert_raise(Puppet::ParseError) do ast.evaluate(scope) end # Verify that we evaluate and return their value correctly. scope.setvar("deprecated", "deprecated value") assert_nothing_raised do ast.evaluate(scope) end assert_equal( "template deprecated value\n", scope.lookupvar("output"), "Deprecated template variables were not handled correctly") end # Make sure that problems with kernel method visibility still exist. def test_kernel_module_shadows_deprecated_var_lookup template = tempfile File.open(template, "w").puts("<%= binding %>") func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "template", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [stringobj(template)] ) ) end ast = varobj("output", func) # Verify that Kernel methods still shadow deprecated variable lookups. scope = mkscope assert_nothing_raised("No exception for Kernel shadowed variable names") do ast.evaluate(scope) end end def test_tempatefunction_cannot_see_scopes template = tempfile File.open(template, "w") do |f| f.puts "<%= lookupvar('myvar') %>" end func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "template", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [stringobj(template)] ) ) end ast = varobj("output", func) scope = mkscope scope.setvar("myvar", "this is yayness") assert_raise(Puppet::ParseError) do ast.evaluate(scope) end end def test_template_reparses template = tempfile File.open(template, "w") do |f| f.puts "original text" end file = tempfile Puppet[:code] = %{file { "#{file}": content => template("#{template}") }} Puppet[:environment] = "yay" node = mknode node.stubs(:environment).returns Puppet::Node::Environment.new Puppet[:environment] = "yay" catalog = Puppet::Parser::Compiler.new(node).compile version = catalog.version fileobj = catalog.vertices.find { |r| r.title == file } assert(fileobj, "File was not in catalog") assert_equal( "original text\n", fileobj["content"], "Template did not work") Puppet[:filetimeout] = -5 # Have to sleep because one second is the fs's time granularity. sleep(1) # Now modify the template File.open(template, "w") do |f| f.puts "new text" end newversion = Puppet::Parser::Compiler.new(node).compile.version assert(version != newversion, "Parse date did not change") end def test_template_defined_vars template = tempfile File.open(template, "w") do |f| f.puts "template <%= @yayness %>" end func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => "template", :ftype => :rvalue, :arguments => AST::ASTArray.new( :children => [stringobj(template)] ) ) end ast = varobj("output", func) { "" => "", false => "false", }.each do |string, value| scope = mkscope scope.setvar("yayness", string) assert_equal(string, scope.lookupvar("yayness")) assert_nothing_raised("An empty string was not a valid variable value") do ast.evaluate(scope) end assert_equal( "template #{value}\n", scope.lookupvar("output"), "#{string.inspect} did not get evaluated correctly") end end def test_autoloading_functions #assert_equal(false, Puppet::Parser::Functions.function(:autofunc), # "Got told autofunc already exists") dir = tempfile $LOAD_PATH << dir newpath = File.join(dir, "puppet", "parser", "functions") FileUtils.mkdir_p(newpath) File.open(File.join(newpath, "autofunc.rb"), "w") { |f| f.puts %{ Puppet::Parser::Functions.newfunction(:autofunc, :type => :rvalue) do |vals| Puppet.wanring vals.inspect end } } Puppet::Node::Environment.stubs(:current).returns nil obj = nil assert_nothing_raised { obj = Puppet::Parser::Functions.function(:autofunc) } assert(obj, "Did not autoload function") assert(Puppet::Parser::Functions.environment_module.method_defined?(:function_autofunc), "Did not set function correctly") end def test_search scope = mkscope fun = scope.known_resource_types.add Puppet::Resource::Type.new(:definition, "yay::ness") foo = scope.known_resource_types.add Puppet::Resource::Type.new(:definition, "foo::bar") search = Puppet::Parser::Functions.function(:search) scope.function_search(["foo", "yay"]) ffun = ffoo = nil assert_nothing_raised("Search path change did not work") do ffun = scope.find_definition("ness") ffoo = scope.find_definition('bar') end assert(ffun, "Could not find definition in 'fun' namespace") assert(ffoo, "Could not find definition in 'foo' namespace") end def test_include scope = mkscope parser = mkparser include = Puppet::Parser::Functions.function(:include) assert_raise(Puppet::Error, "did not throw error on missing class") do scope.function_include("nosuchclass") end scope.known_resource_types.add Puppet::Resource::Type.new(:hostclass, "myclass", {}) scope.compiler.expects(:evaluate_classes).with(%w{myclass otherclass}, scope, false).returns(%w{myclass otherclass}) assert_nothing_raised do scope.function_include(["myclass", "otherclass"]) end end def test_file parser = mkparser scope = mkscope(:parser => parser) file = Puppet::Parser::Functions.function(:file) file1 = tempfile file2 = tempfile file3 = tempfile File.open(file2, "w") { |f| f.puts "yaytest" } val = nil assert_nothing_raised("Failed to call file with one arg") do val = scope.function_file([file2]) end assert_equal("yaytest\n", val, "file() failed") assert_nothing_raised("Failed to call file with two args") do val = scope.function_file([file1, file2]) end assert_equal("yaytest\n", val, "file() failed") assert_raise(Puppet::ParseError, "did not fail when files are missing") do val = scope.function_file([file1, file3]) end end def test_generate command = tempfile sh = %x{which sh} File.open(command, "w") do |f| f.puts %{#!#{sh} if [ -n "$1" ]; then echo "yay-$1" else echo yay fi } end File.chmod(0755, command) assert_equal("yay\n", %x{#{command}}, "command did not work") assert_equal("yay-foo\n", %x{#{command} foo}, "command did not work") Puppet::Node::Environment.stubs(:current).returns nil generate = Puppet::Parser::Functions.function(:generate) scope = mkscope parser = mkparser val = nil assert_nothing_raised("Could not call generator with no args") do val = scope.function_generate([command]) end assert_equal("yay\n", val, "generator returned wrong results") assert_nothing_raised("Could not call generator with args") do val = scope.function_generate([command, "foo"]) end assert_equal("yay-foo\n", val, "generator returned wrong results") assert_raise(Puppet::ParseError, "Did not fail with an unqualified path") do val = scope.function_generate([File.basename(command), "foo"]) end assert_raise(Puppet::ParseError, "Did not fail when command failed") do val = scope.function_generate([%x{which touch}.chomp, "/this/dir/does/not/exist"]) end fake = File.join(File.dirname(command), "..") dir = File.dirname(command) dirname = File.basename(dir) bad = File.join(dir, "..", dirname, File.basename(command)) assert_raise(Puppet::ParseError, "Did not fail when command failed") do val = scope.function_generate([bad]) end end end diff --git a/test/language/snippets.rb b/test/language/snippets.rb index bfc14fa2d..f34e3684e 100755 --- a/test/language/snippets.rb +++ b/test/language/snippets.rb @@ -1,522 +1,513 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') require 'puppet' require 'puppet/parser/parser' -require 'puppet/network/client' -require 'puppet/network/handler' require 'puppettest' class TestSnippets < Test::Unit::TestCase include PuppetTest def setup super @file = Puppet::Type.type(:file) Facter.stubs(:to_hash).returns({}) Facter.stubs(:value).returns("whatever") end def self.snippetdir PuppetTest.datadir "snippets" end def assert_file(path, msg = nil) unless file = @catalog.resource(:file, path) msg ||= "Could not find file #{path}" raise msg end end def assert_not_file(path, msg = nil) if file = @catalog.resource(:file, path) msg ||= "File #{path} exists!" raise msg end end def assert_mode_equal(mode, path) if mode.is_a? Integer mode = mode.to_s(8) end unless file = @catalog.resource(:file, path) raise "Could not find file #{path}" end unless mode == file.should(:mode) raise "Mode for %s is incorrect: %o vs %o" % [path, mode, file.should(:mode)] end end def snippet(name) File.join(self.class.snippetdir, name) end def file2ast(file) parser = Puppet::Parser::Parser.new parser.file = file ast = parser.parse ast end def snippet2ast(text) parser = Puppet::Parser::Parser.new parser.string = text ast = parser.parse ast end - def client - args = { - :Listen => false - } - Puppet::Network::Client.new(args) - end - def ast2scope(ast) scope = Puppet::Parser::Scope.new ast.evaluate(scope) scope end def scope2objs(scope) objs = scope.to_trans end def snippet2scope(snippet) ast = snippet2ast(snippet) scope = ast2scope(ast) end def snippet2objs(snippet) ast = snippet2ast(snippet) scope = ast2scope(ast) objs = scope2objs(scope) end def properties(type) properties = type.validproperties end def metaparams(type) mparams = [] Puppet::Type.eachmetaparam { |param| mparams.push param } mparams end def params(type) params = [] type.parameters.each { |name,property| params.push name } params end def randthing(thing,type) list = self.send(thing,type) list[rand(list.length)] end def randeach(type) [:properties, :metaparams, :parameters].collect { |thing| randthing(thing,type) } end @@snippets = { true => [ %{File { mode => 755 }} ], } def disabled_test_defaults Puppet::Type.eachtype { |type| next if type.name == :puppet or type.name == :component rands = randeach(type) name = type.name.to_s.capitalize [0..1, 0..2].each { |range| params = rands[range] paramstr = params.collect { |param| "#{param} => fake" }.join(", ") str = "#{name} { #{paramstr} }" scope = nil assert_nothing_raised { scope = snippet2scope(str) } defaults = nil assert_nothing_raised { defaults = scope.lookupdefaults(name) } p defaults params.each { |param| puts "#{name} => '#{param}'" assert(defaults.include?(param)) } } } end # this is here in case no tests get defined; otherwise we get a warning def test_nothing end def snippet_filecreate %w{a b c d}.each { |letter| path = "/tmp/create#{letter}test" assert_file(path) assert_mode_equal(0755, path) if %w{a b}.include?(letter) } end def snippet_simpledefaults path = "/tmp/defaulttest" assert_file(path) assert_mode_equal(0755, path) end def snippet_simpleselector files = %w{a b c d}.collect { |letter| path = "/tmp/snippetselect#{letter}test" assert_file(path) assert_mode_equal(0755, path) } end def snippet_classpathtest path = "/tmp/classtest" file = @catalog.resource(:file, path) assert(file, "did not create file #{path}") assert_equal( "/Stage[main]/Testing/Mytype[componentname]/File[/tmp/classtest]", file.path) end def snippet_argumentdefaults path1 = "/tmp/argumenttest1" path2 = "/tmp/argumenttest2" file1 = @catalog.resource(:file, path1) file2 = @catalog.resource(:file, path2) assert_file(path1) assert_mode_equal(0755, path1) assert_file(path2) assert_mode_equal(0644, path2) end def snippet_casestatement paths = %w{ /tmp/existsfile /tmp/existsfile2 /tmp/existsfile3 /tmp/existsfile4 /tmp/existsfile5 /tmp/existsfile6 } paths.each { |path| file = @catalog.resource(:file, path) assert(file, "File #{path} is missing") assert_mode_equal(0755, path) } end def snippet_implicititeration paths = %w{a b c d e f g h}.collect { |l| "/tmp/iteration#{l}test" } paths.each { |path| file = @catalog.resource(:file, path) assert_file(path) assert_mode_equal(0755, path) } end def snippet_multipleinstances paths = %w{a b c}.collect { |l| "/tmp/multipleinstances#{l}" } paths.each { |path| assert_file(path) assert_mode_equal(0755, path) } end def snippet_namevartest file = "/tmp/testfiletest" dir = "/tmp/testdirtest" assert_file(file) assert_file(dir) assert_equal(:directory, @catalog.resource(:file, dir).should(:ensure), "Directory is not set to be a directory") end def snippet_scopetest file = "/tmp/scopetest" assert_file(file) assert_mode_equal(0755, file) end def snippet_selectorvalues nums = %w{1 2 3 4 5 6 7} files = nums.collect { |n| "/tmp/selectorvalues#{n}" } files.each { |f| assert_file(f) assert_mode_equal(0755, f) } end def snippet_singleselector nums = %w{1 2 3} files = nums.collect { |n| "/tmp/singleselector#{n}" } files.each { |f| assert_file(f) assert_mode_equal(0755, f) } end def snippet_falsevalues file = "/tmp/falsevaluesfalse" assert_file(file) end def disabled_snippet_classargtest [1,2].each { |num| file = "/tmp/classargtest#{num}" assert_file(file) assert_mode_equal(0755, file) } end def snippet_classheirarchy [1,2,3].each { |num| file = "/tmp/classheir#{num}" assert_file(file) assert_mode_equal(0755, file) } end def snippet_singleary [1,2,3,4].each { |num| file = "/tmp/singleary#{num}" assert_file(file) } end def snippet_classincludes [1,2,3].each { |num| file = "/tmp/classincludes#{num}" assert_file(file) assert_mode_equal(0755, file) } end def snippet_componentmetaparams ["/tmp/component1", "/tmp/component2"].each { |file| assert_file(file) } end def snippet_aliastest %w{/tmp/aliastest /tmp/aliastest2 /tmp/aliastest3}.each { |file| assert_file(file) } end def snippet_singlequote { 1 => 'a $quote', 2 => 'some "\yayness\"' }.each { |count, str| path = "/tmp/singlequote#{count}" assert_file(path) assert_equal(str, @catalog.resource(:file, path).parameter(:content).actual_content) } end # There's no way to actually retrieve the list of classes from the # transaction. def snippet_tag end # Make sure that set tags are correctly in place, yo. def snippet_tagged tags = {"testing" => true, "yayness" => false, "both" => false, "bothtrue" => true, "define" => true} tags.each do |tag, retval| assert_file("/tmp/tagged#{tag}#{retval.to_s}") end end def snippet_defineoverrides file = "/tmp/defineoverrides1" assert_file(file) assert_mode_equal(0755, file) end def snippet_deepclassheirarchy 5.times { |i| i += 1 file = "/tmp/deepclassheir#{i}" assert_file(file) } end def snippet_emptyclass # There's nothing to check other than that it works end def snippet_emptyexec assert(@catalog.resource(:exec, "touch /tmp/emptyexectest"), "Did not create exec") end def snippet_multisubs path = "/tmp/multisubtest" assert_file(path) file = @catalog.resource(:file, path) assert_equal("{md5}5fbef65269a99bddc2106251dd89b1dc", file.should(:content), "sub2 did not override content") assert_mode_equal(0755, path) end def snippet_collection assert_file("/tmp/colltest1") assert_nil(@catalog.resource(:file, "/tmp/colltest2"), "Incorrectly collected file") end def snippet_virtualresources %w{1 2 3 4}.each do |num| assert_file("/tmp/virtualtest#{num}") end end def snippet_componentrequire %w{1 2}.each do |num| assert_file( "/tmp/testing_component_requires#{num}", "#{num} does not exist") end end def snippet_realize_defined_types assert_file("/tmp/realize_defined_test1") assert_file("/tmp/realize_defined_test2") end def snippet_collection_within_virtual_definitions assert_file("/tmp/collection_within_virtual_definitions1_foo.txt") assert_file("/tmp/collection_within_virtual_definitions2_foo2.txt") end def snippet_fqparents assert_file("/tmp/fqparent1", "Did not make file from parent class") assert_file("/tmp/fqparent2", "Did not make file from subclass") end def snippet_fqdefinition assert_file("/tmp/fqdefinition", "Did not make file from fully-qualified definition") end def snippet_subclass_name_duplication assert_file("/tmp/subclass_name_duplication1", "Did not make first file from duplicate subclass names") assert_file("/tmp/subclass_name_duplication2", "Did not make second file from duplicate subclass names") end def snippet_funccomma assert_file("/tmp/funccomma1", "Did not make first file from trailing function comma") assert_file("/tmp/funccomma2", "Did not make second file from trailing function comma") end def snippet_arraytrailingcomma assert_file("/tmp/arraytrailingcomma1", "Did not make first file from array") assert_file("/tmp/arraytrailingcomma2", "Did not make second file from array") end def snippet_multipleclass assert_file("/tmp/multipleclassone", "one") assert_file("/tmp/multipleclasstwo", "two") end def snippet_multilinecomments assert_not_file("/tmp/multilinecomments","Did create a commented resource"); end def snippet_collection_override path = "/tmp/collection" assert_file(path) assert_mode_equal(0600, path) end def snippet_ifexpression assert_file("/tmp/testiftest","if test"); end def snippet_hash assert_file("/tmp/myhashfile1","hash test 1"); assert_file("/tmp/myhashfile2","hash test 2"); assert_file("/tmp/myhashfile3","hash test 3"); assert_file("/tmp/myhashfile4","hash test 4"); end # Iterate across each of the snippets and create a test. Dir.entries(snippetdir).sort.each { |file| next if file =~ /^\./ mname = "snippet_" + file.sub(/\.pp$/, '') if self.method_defined?(mname) #eval("alias #{testname} #{mname}") testname = ("test_#{mname}").intern self.send(:define_method, testname) { Puppet[:manifest] = snippet(file) facts = { "hostname" => "testhost", "domain" => "domain.com", "ipaddress" => "127.0.0.1", "fqdn" => "testhost.domain.com" } node = Puppet::Node.new("testhost") node.merge(facts) catalog = nil assert_nothing_raised("Could not compile catalog") { catalog = Puppet::Resource::Catalog.indirection.find(node) } assert_nothing_raised("Could not convert catalog") { catalog = catalog.to_ral } @catalog = catalog assert_nothing_raised { self.send(mname) } } mname = mname.intern end } end diff --git a/test/lib/puppettest/servertest.rb b/test/lib/puppettest/servertest.rb index 4174c1a3d..82483004d 100644 --- a/test/lib/puppettest/servertest.rb +++ b/test/lib/puppettest/servertest.rb @@ -1,74 +1,73 @@ require 'puppettest' -require 'puppet/network/http_server/webrick' 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/client/ca.rb b/test/network/client/ca.rb deleted file mode 100755 index fcb950174..000000000 --- a/test/network/client/ca.rb +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') - -require 'mocha' -require 'puppettest' -require 'puppet/network/client/ca' -require 'puppet/sslcertificates/support' - -class TestClientCA < Test::Unit::TestCase - include PuppetTest::ServerTest - - def setup - Puppet::Util::SUIDManager.stubs(:asuser).yields - super - @ca = Puppet::Network::Handler.ca.new - @client = Puppet::Network::Client.ca.new :CA => @ca - end - - def test_request_cert - assert_nothing_raised("Could not request cert") do - @client.request_cert - end - - [:hostprivkey, :hostcert, :localcacert].each do |name| - assert(FileTest.exists?(Puppet.settings[name]), "Did not create cert #{name}") - end - end - - # Make sure the ca defaults to specific ports and names - def test_ca_server - Puppet.settings.stubs(:value).returns "eh" - Puppet.settings.expects(:value).with(:ca_server).returns("myca") - Puppet.settings.expects(:value).with(:ca_port).returns(321) - Puppet.settings.stubs(:value).with(:http_proxy_host).returns(nil) - Puppet.settings.stubs(:value).with(:http_proxy_port).returns(nil) - Puppet.settings.stubs(:value).with(:http_keepalive).returns(false) - Puppet.settings.stubs(:value).with(:configtimeout).returns(180) - - # Just throw an error; the important thing is the values, not what happens next. - Net::HTTP.stubs(:new).with("myca", 321, nil, nil).raises(ArgumentError) - assert_raise(ArgumentError) { Puppet::Network::Client.ca.new } - end - - # #578 - def test_invalid_certs_are_not_written - # Run the get once, which should be valid - - assert_nothing_raised("Could not get a certificate") do - @client.request_cert - end - - # Now remove the cert and keys, so we get a broken cert - File.unlink(Puppet[:hostcert]) - File.unlink(Puppet[:localcacert]) - File.unlink(Puppet[:hostprivkey]) - - @client = Puppet::Network::Client.ca.new :CA => @ca - @ca.expects(:getcert).returns("yay") # not a valid cert - # Now make sure it fails, since we'll get the old cert but have new keys - assert_raise(Puppet::Network::Client::CA::InvalidCertificate, "Did not fail on invalid cert") do - @client.request_cert - end - - # And then make sure the cert isn't written to disk - assert(! FileTest.exists?(Puppet[:hostcert]), "Invalid cert got written to disk") - end -end - diff --git a/test/network/client/dipper.rb b/test/network/client/dipper.rb deleted file mode 100755 index 45f3a7a5c..000000000 --- a/test/network/client/dipper.rb +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') - -require 'puppettest' -require 'puppet/file_bucket/dipper' - -class TestDipperClient < Test::Unit::TestCase - include PuppetTest::ServerTest - - def setup - super - @dipper = Puppet::FileBucket::Dipper.new(:Path => tempfile) - end - - # Make sure we can create a new file with 'restore'. - def test_restore_to_new_file - file = tempfile - text = "asdf;lkajseofiqwekj" - File.open(file, "w") { |f| f.puts text } - md5 = nil - assert_nothing_raised("Could not send file") do - md5 = @dipper.backup(file) - end - - newfile = tempfile - assert_nothing_raised("could not restore to new path") do - @dipper.restore(newfile, md5) - end - - assert_equal(File.read(file), File.read(newfile), "did not restore correctly") - end -end - diff --git a/test/network/handler/ca.rb b/test/network/handler/ca.rb index e27e9c29b..0b33bab7a 100755 --- a/test/network/handler/ca.rb +++ b/test/network/handler/ca.rb @@ -1,273 +1,220 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') require 'puppettest' require 'puppet/network/handler/ca' require 'mocha' $short = (ARGV.length > 0 and ARGV[0] == "short") class TestCA < Test::Unit::TestCase include PuppetTest::ServerTest def setup Puppet::Util::SUIDManager.stubs(:asuser).yields super end # Verify that we're autosigning. We have to autosign a "different" machine, # since we always autosign the CA server's certificate. def test_autocertgeneration ca = nil # create our ca assert_nothing_raised { ca = Puppet::Network::Handler.ca.new(:autosign => true) } # create a cert with a fake name key = nil csr = nil cert = nil hostname = "test.domain.com" assert_nothing_raised { cert = Puppet::SSLCertificates::Certificate.new( :name => "test.domain.com" ) } # make the request assert_nothing_raised { cert.mkcsr } # and get it signed certtext = nil cacerttext = nil assert_nothing_raised { certtext, cacerttext = ca.getcert(cert.csr.to_s) } # they should both be strings assert_instance_of(String, certtext) assert_instance_of(String, cacerttext) # and they should both be valid certs assert_nothing_raised { OpenSSL::X509::Certificate.new(certtext) } assert_nothing_raised { OpenSSL::X509::Certificate.new(cacerttext) } # and pull it again, just to make sure we're getting the same thing newtext = nil assert_nothing_raised { newtext, cacerttext = ca.getcert( cert.csr.to_s, "test.reductivelabs.com", "127.0.0.1" ) } assert_equal(certtext,newtext) end # this time don't use autosign def test_storeAndSign ca = nil caserv = nil # make our CA server assert_nothing_raised { caserv = Puppet::Network::Handler.ca.new(:autosign => false) } # retrieve the actual ca object assert_nothing_raised { ca = caserv.ca } # make our test cert again key = nil csr = nil cert = nil hostname = "test.domain.com" assert_nothing_raised { cert = Puppet::SSLCertificates::Certificate.new( :name => "anothertest.domain.com" ) } # and the CSR assert_nothing_raised { cert.mkcsr } # retrieve them certtext = nil assert_nothing_raised { certtext, cacerttext = caserv.getcert( cert.csr.to_s, "test.reductivelabs.com", "127.0.0.1" ) } # verify we got nothing back, since autosign is off assert_equal("", certtext) # now sign it manually, with the CA object x509 = nil assert_nothing_raised { x509, cacert = ca.sign(cert.csr) } # and write it out cert.cert = x509 assert_nothing_raised { cert.write } assert(File.exists?(cert.certfile)) # now get them again, and verify that we actually get them newtext = nil assert_nothing_raised { newtext, cacerttext = caserv.getcert(cert.csr.to_s) } assert(newtext) assert_nothing_raised { OpenSSL::X509::Certificate.new(newtext) } # Now verify that we can clean a given host's certs assert_nothing_raised { ca.clean("anothertest.domain.com") } assert(!File.exists?(cert.certfile), "Cert still exists after clean") end # and now test the autosign file def test_autosign autosign = File.join(tmpdir, "autosigntesting") @@tmpfiles << autosign File.open(autosign, "w") { |f| f.puts "hostmatch.domain.com" f.puts "*.other.com" } caserv = nil assert_nothing_raised { caserv = Puppet::Network::Handler.ca.new(:autosign => autosign) } # make sure we know what's going on assert(caserv.autosign?("hostmatch.domain.com")) assert(caserv.autosign?("fakehost.other.com")) assert(!caserv.autosign?("kirby.reductivelabs.com")) assert(!caserv.autosign?("culain.domain.com")) end # verify that things aren't autosigned by default def test_nodefaultautosign caserv = nil assert_nothing_raised { caserv = Puppet::Network::Handler.ca.new } # make sure we know what's going on assert(!caserv.autosign?("hostmatch.domain.com")) assert(!caserv.autosign?("fakehost.other.com")) assert(!caserv.autosign?("kirby.reductivelabs.com")) assert(!caserv.autosign?("culain.domain.com")) end - # We want the CA to autosign its own certificate, because otherwise - # the puppetmasterd CA does not autostart. - def test_caautosign - server = nil - Puppet.stubs(:master?).returns true - assert_nothing_raised { - - server = Puppet::Network::HTTPServer::WEBrick.new( - - :Port => @@port, - - :Handlers => { - :CA => {}, # so that certs autogenerate - :Status => nil - } - ) - } - end - # Make sure true/false causes the file to be ignored. def test_autosign_true_beats_file caserv = nil assert_nothing_raised { caserv = Puppet::Network::Handler.ca.new } host = "hostname.domain.com" # Create an autosign file file = tempfile Puppet[:autosign] = file File.open(file, "w") { |f| f.puts host } # Start with "false" Puppet[:autosign] = false assert(! caserv.autosign?(host), "Host was incorrectly autosigned") # Then set it to true Puppet[:autosign] = true assert(caserv.autosign?(host), "Host was not autosigned") # And try a different host assert(caserv.autosign?("other.yay.com"), "Host was not autosigned") # And lastly the file Puppet[:autosign] = file assert(caserv.autosign?(host), "Host was not autosigned") # And try a different host assert(! caserv.autosign?("other.yay.com"), "Host was autosigned") end - - # Make sure that a CSR created with keys that don't match the existing - # cert throws an exception on the server. - def test_mismatched_public_keys_throws_exception - ca = Puppet::Network::Handler.ca.new - - # First initialize the server - client = Puppet::Network::Client.ca.new :CA => ca - client.request_cert - File.unlink(Puppet[:hostcsr]) - - # Now use a different cert name - Puppet[:certname] = "my.host.com" - client = Puppet::Network::Client.ca.new :CA => ca - firstcsr = client.csr - File.unlink(Puppet[:hostcsr]) if FileTest.exists?(Puppet[:hostcsr]) - - assert_nothing_raised("Could not get cert") do - ca.getcert(firstcsr.to_s) - end - - # Now get rid of the public key, forcing a new csr - File.unlink(Puppet[:hostprivkey]) - - client = Puppet::Network::Client.ca.new :CA => ca - - second_csr = client.csr - - assert(firstcsr.to_s != second_csr.to_s, "CSR did not change") - - assert_raise(Puppet::Error, "CA allowed mismatched keys") do - ca.getcert(second_csr.to_s) - end - end end diff --git a/test/network/server/mongrel_test.rb b/test/network/server/mongrel_test.rb deleted file mode 100755 index ca215ee25..000000000 --- a/test/network/server/mongrel_test.rb +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') - -require 'puppettest' -require 'mocha' - -class TestMongrelServer < PuppetTest::TestCase - confine "Missing mongrel" => Puppet.features.mongrel? - - include PuppetTest::ServerTest - - def mkserver(handlers = nil) - handlers ||= { :Status => nil } - mongrel = Puppet::Network::HTTPServer::Mongrel.new(handlers) - end - - # Make sure client info is correctly extracted. - def test_client_info - obj = Object.new - obj.singleton_class.send(:attr_accessor, :params) - params = {} - obj.params = params - - mongrel = mkserver - - ip = Facter.value(:ipaddress) - params["REMOTE_ADDR"] = ip - params[Puppet[:ssl_client_header]] = "" - params[Puppet[:ssl_client_verify_header]] = "failure" - info = nil - Resolv.expects(:getname).with(ip).returns("host.domain.com").times(4) - assert_nothing_raised("Could not call client_info") do - info = mongrel.send(:client_info, obj) - end - assert(! info.authenticated?, "Client info object was marked valid even though headers were missing") - assert_equal(ip, info.ip, "Did not copy over ip correctly") - - assert_equal("host.domain.com", info.name, "Did not copy over hostname correctly") - - # Now pass the X-Forwarded-For header and check it is preferred over REMOTE_ADDR - params["REMOTE_ADDR"] = '127.0.0.1' - params["HTTP_X_FORWARDED_FOR"] = ip - info = nil - assert_nothing_raised("Could not call client_info") do - info = mongrel.send(:client_info, obj) - end - assert(! info.authenticated?, "Client info object was marked valid even though headers were missing") - assert_equal(ip, info.ip, "Did not copy over ip correctly") - - assert_equal("host.domain.com", info.name, "Did not copy over hostname correctly") - - # Now add a valid auth header. - params["REMOTE_ADDR"] = ip - params["HTTP_X_FORWARDED_FOR"] = nil - params[Puppet[:ssl_client_header]] = "/CN=host.domain.com" - assert_nothing_raised("Could not call client_info") do - info = mongrel.send(:client_info, obj) - end - assert(! info.authenticated?, "Client info object was marked valid even though the verify header was fals") - assert_equal(ip, info.ip, "Did not copy over ip correctly") - assert_equal("host.domain.com", info.name, "Did not copy over hostname correctly") - - # Now change the verify header to be true - params[Puppet[:ssl_client_verify_header]] = "SUCCESS" - assert_nothing_raised("Could not call client_info") do - info = mongrel.send(:client_info, obj) - end - - assert(info.authenticated?, "Client info object was not marked valid even though all headers were correct") - assert_equal(ip, info.ip, "Did not copy over ip correctly") - assert_equal("host.domain.com", info.name, "Did not copy over hostname correctly") - - # Now try it with a different header name - params.delete(Puppet[:ssl_client_header]) - Puppet[:ssl_client_header] = "header_testing" - params["header_testing"] = "/CN=other.domain.com" - info = nil - assert_nothing_raised("Could not call client_info with other header") do - info = mongrel.send(:client_info, obj) - end - - assert(info.authenticated?, "Client info object was not marked valid even though the header was present") - assert_equal(ip, info.ip, "Did not copy over ip correctly") - assert_equal("other.domain.com", info.name, "Did not copy over hostname correctly") - - # Now make sure it's considered invalid without that header - params.delete("header_testing") - info = nil - assert_nothing_raised("Could not call client_info with no header") do - info = mongrel.send(:client_info, obj) - end - - assert(! info.authenticated?, "Client info object was marked valid without header") - assert_equal(ip, info.ip, "Did not copy over ip correctly") - assert_equal(Resolv.getname(ip), info.name, "Did not look up hostname correctly") - end -end - diff --git a/test/network/server/webrick.rb b/test/network/server/webrick.rb deleted file mode 100755 index 4d7bf32f4..000000000 --- a/test/network/server/webrick.rb +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') - -require 'puppettest' -require 'puppet/network/http_server/webrick' -require 'mocha' - -class TestWebrickServer < Test::Unit::TestCase - include PuppetTest::ServerTest - - def setup - Puppet::Util::SUIDManager.stubs(:asuser).yields - Puppet::SSL::Host.instance_variable_set(:@localhost, nil) - super - end - - # Make sure we can create a server, and that it knows how to create its - # certs by default. - def test_basics - server = nil - assert_raise(Puppet::Error, "server succeeded with no cert") do - server = Puppet::Network::HTTPServer::WEBrick.new( - :Port => @@port, - :Handlers => { - :Status => nil - } - ) - end - - assert_nothing_raised("Could not create simple server") do - server = Puppet::Network::HTTPServer::WEBrick.new( - :Port => @@port, - :Handlers => { - :CA => {}, # so that certs autogenerate - :Status => nil - } - ) - end - - assert(server, "did not create server") - - assert(server.cert, "did not retrieve cert") - end - - # test that we can connect to the server - # we have to use fork here, because we apparently can't use threads - # to talk to other threads - def test_connect_with_fork - Puppet[:autosign] = true - serverpid, server = mk_status_server - - # create a status client, and verify it can talk - client = mk_status_client - - assert(client.cert, "did not get cert for client") - - retval = nil - assert_nothing_raised("Could not connect to server") { - retval = client.status - } - assert_equal(1, retval) - end - - def mk_status_client - client = nil - - assert_nothing_raised { - client = Puppet::Network::Client.status.new( - :Server => "localhost", - :Port => @@port - ) - } - client - end - - def mk_status_server - server = nil - Puppet[:master_dns_alt_names] = "localhost" - assert_nothing_raised { - server = Puppet::Network::HTTPServer::WEBrick.new( - :Port => @@port, - :Handlers => { - :CA => {}, # so that certs autogenerate - :Status => nil - } - ) - } - - pid = fork { - Puppet.run_mode.stubs(:master?).returns true - assert_nothing_raised { - trap(:INT) { server.shutdown } - server.start - } - } - @@tmppids << pid - [pid, server] - end - - def kill_and_wait(pid, file) - %x{kill -INT #{pid} 2>/dev/null} - count = 0 - while count < 30 && File::exist?(file) - count += 1 - sleep(1) - end - assert(count < 30, "Killing server #{pid} failed") - end -end - diff --git a/test/network/xmlrpc/client.rb b/test/network/xmlrpc/client.rb deleted file mode 100755 index 73159a994..000000000 --- a/test/network/xmlrpc/client.rb +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env ruby - -require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') - -require 'puppettest' -require 'puppet/network/xmlrpc/client' -require 'mocha' - -class TestXMLRPCClient < Test::Unit::TestCase - include PuppetTest - - def setup - Puppet::Util::SUIDManager.stubs(:asuser).yields - super - end - - def test_set_backtrace - error = Puppet::Network::XMLRPCClientError.new("An error") - assert_nothing_raised do - error.set_backtrace ["caller"] - end - assert_equal(["caller"], error.backtrace) - end - - # Make sure we correctly generate a netclient - def test_handler_class - # Create a test handler - klass = Puppet::Network::XMLRPCClient - yay = Class.new(Puppet::Network::Handler) do - @interface = XMLRPC::Service::Interface.new("yay") { |iface| - iface.add_method("array getcert(csr)") - } - - @name = :Yay - end - Object.const_set("Yay", yay) - - net = nil - assert_nothing_raised("Failed when retrieving client for handler") do - net = klass.handler_class(yay) - end - - assert(net, "did not get net client") - end -end diff --git a/test/rails/rails.rb b/test/rails/rails.rb index 75987b95e..a9a750ffd 100755 --- a/test/rails/rails.rb +++ b/test/rails/rails.rb @@ -1,25 +1,24 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') require 'puppet' require 'puppet/rails' require 'puppet/parser/parser' -require 'puppet/network/client' require 'puppettest' require 'puppettest/parsertesting' require 'puppettest/resourcetesting' require 'puppettest/railstesting' class TestRails < Test::Unit::TestCase include PuppetTest::ParserTesting include PuppetTest::ResourceTesting include PuppetTest::RailsTesting def test_includerails assert_nothing_raised { require 'puppet/rails' } end end diff --git a/test/ral/type/filesources.rb b/test/ral/type/filesources.rb index be8c40d35..9cb7efbf0 100755 --- a/test/ral/type/filesources.rb +++ b/test/ral/type/filesources.rb @@ -1,501 +1,446 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../../lib/puppettest') require 'puppettest' require 'puppettest/support/utils' require 'cgi' require 'fileutils' require 'mocha' class TestFileSources < Test::Unit::TestCase include PuppetTest::Support::Utils include PuppetTest::FileTesting def setup super if defined?(@port) @port += 1 else @port = 12345 end @file = Puppet::Type.type(:file) Puppet[:filetimeout] = -1 Puppet::Util::SUIDManager.stubs(:asuser).yields Facter.stubs(:to_hash).returns({}) end def teardown super end def use_storage initstorage rescue system("rm -rf #{Puppet[:statefile]}") end def initstorage Puppet::Util::Storage.init Puppet::Util::Storage.load end # Make a simple recursive tree. def mk_sourcetree source = tempfile sourcefile = File.join(source, "file") Dir.mkdir source File.open(sourcefile, "w") { |f| f.puts "yay" } dest = tempfile destfile = File.join(dest, "file") return source, dest, sourcefile, destfile end def recursive_source_test(fromdir, todir) initstorage tofile = nil trans = nil tofile = Puppet::Type.type(:file).new( :path => todir, :recurse => true, :backup => false, :source => fromdir ) catalog = mk_catalog(tofile) catalog.apply assert(FileTest.exists?(todir), "Created dir #{todir} does not exist") end def run_complex_sources(networked = false) path = tempfile # first create the source directory FileUtils.mkdir_p path # okay, let's create a directory structure fromdir = File.join(path,"fromdir") Dir.mkdir(fromdir) FileUtils.cd(fromdir) { File.open("one", "w") { |f| f.puts "onefile"} File.open("two", "w") { |f| f.puts "twofile"} } todir = File.join(path, "todir") source = fromdir source = "puppet://localhost/#{networked}#{fromdir}" if networked recursive_source_test(source, todir) [fromdir,todir, File.join(todir, "one"), File.join(todir, "two")] end def test_complex_sources_twice fromdir, todir, one, two = run_complex_sources assert_trees_equal(fromdir,todir) recursive_source_test(fromdir, todir) assert_trees_equal(fromdir,todir) # Now remove the whole tree and try it again. [one, two].each do |f| File.unlink(f) end Dir.rmdir(todir) recursive_source_test(fromdir, todir) assert_trees_equal(fromdir,todir) end def test_sources_with_deleted_destfiles fromdir, todir, one, two = run_complex_sources assert(FileTest.exists?(todir)) # then delete a file File.unlink(two) # and run recursive_source_test(fromdir, todir) assert(FileTest.exists?(two), "Deleted file was not recopied") # and make sure they're still equal assert_trees_equal(fromdir,todir) end def test_sources_with_readonly_destfiles fromdir, todir, one, two = run_complex_sources assert(FileTest.exists?(todir)) File.chmod(0600, one) recursive_source_test(fromdir, todir) # and make sure they're still equal assert_trees_equal(fromdir,todir) # Now try it with the directory being read-only File.chmod(0111, todir) recursive_source_test(fromdir, todir) # and make sure they're still equal assert_trees_equal(fromdir,todir) end def test_sources_with_modified_dest_files fromdir, todir, one, two = run_complex_sources assert(FileTest.exists?(todir)) # Modify a dest file File.open(two, "w") { |f| f.puts "something else" } recursive_source_test(fromdir, todir) # and make sure they're still equal assert_trees_equal(fromdir,todir) end def test_sources_with_added_destfiles fromdir, todir = run_complex_sources assert(FileTest.exists?(todir)) # and finally, add some new files add_random_files(todir) recursive_source_test(fromdir, todir) fromtree = file_list(fromdir) totree = file_list(todir) assert(fromtree != totree, "Trees are incorrectly equal") # then remove our new files FileUtils.cd(todir) { %x{find . 2>/dev/null}.chomp.split(/\n/).each { |file| if file =~ /file[0-9]+/ FileUtils.rm_rf(file) end } } # and make sure they're still equal assert_trees_equal(fromdir,todir) end # Make sure added files get correctly caught during recursion def test_RecursionWithAddedFiles basedir = tempfile Dir.mkdir(basedir) @@tmpfiles << basedir file1 = File.join(basedir, "file1") file2 = File.join(basedir, "file2") subdir1 = File.join(basedir, "subdir1") file3 = File.join(subdir1, "file") File.open(file1, "w") { |f| f.puts "yay" } rootobj = nil assert_nothing_raised { rootobj = Puppet::Type.type(:file).new( :name => basedir, :recurse => true, :check => %w{type owner}, :mode => 0755 ) } assert_apply(rootobj) assert_equal(0755, filemode(file1)) File.open(file2, "w") { |f| f.puts "rah" } assert_apply(rootobj) assert_equal(0755, filemode(file2)) Dir.mkdir(subdir1) File.open(file3, "w") { |f| f.puts "foo" } assert_apply(rootobj) assert_equal(0755, filemode(file3)) end def mkfileserverconf(mounts) file = tempfile File.open(file, "w") { |f| mounts.each { |path, name| f.puts "[#{name}]\n\tpath #{path}\n\tallow *\n" } } @@tmpfiles << file file end - def test_unmountedNetworkSources - server = nil - mounts = { - "/" => "root", - "/noexistokay" => "noexist" - } - - fileserverconf = mkfileserverconf(mounts) - - Puppet[:autosign] = true - Puppet[:masterport] = @port - Puppet[:master_dns_alt_names] = "localhost" - - serverpid = nil - assert_nothing_raised("Could not start on port #{@port}") { - server = Puppet::Network::HTTPServer::WEBrick. - new(:Port => @port, - :Handlers => { - :CA => {}, # so that certs autogenerate - :FileServer => { - :Config => fileserverconf - } - }) - } - - serverpid = fork { - assert_nothing_raised { - #trap(:INT) { server.shutdown; Kernel.exit! } - trap(:INT) { server.shutdown } - server.start - } - } - @@tmppids << serverpid - - sleep(1) - - name = File.join(tmpdir, "nosourcefile") - - file = Puppet::Type.type(:file).new( - - :source => "puppet://localhost/noexist/file", - - :name => name - ) - - assert_raise Puppet::Error do - file.retrieve - end - - comp = mk_catalog(file) - comp.apply - - assert(!FileTest.exists?(name), "File with no source exists anyway") - end - def test_sourcepaths files = [] 3.times { files << tempfile } to = tempfile File.open(files[-1], "w") { |f| f.puts "yee-haw" } file = nil assert_nothing_raised { file = Puppet::Type.type(:file).new( :name => to, :source => files ) } comp = mk_catalog(file) assert_events([:file_created], comp) assert(File.exists?(to), "File does not exist") txt = nil File.open(to) { |f| txt = f.read.chomp } assert_equal("yee-haw", txt, "Contents do not match") end # Make sure that source-copying updates the checksum on the same run def test_sourcebeatsensure source = tempfile dest = tempfile File.open(source, "w") { |f| f.puts "yay" } file = nil assert_nothing_raised { file = Puppet::Type.type(:file).new( :name => dest, :ensure => "file", :source => source ) } file.retrieve assert_events([:file_created], file) file.retrieve assert_events([], file) assert_events([], file) end def test_sourcewithlinks source = tempfile link = tempfile dest = tempfile File.open(source, "w") { |f| f.puts "yay" } File.symlink(source, link) file = Puppet::Type.type(:file).new(:name => dest, :source => link) catalog = mk_catalog(file) # Default to managing links catalog.apply assert(FileTest.symlink?(dest), "Did not create link") # Now follow the links file[:links] = :follow catalog.apply assert(FileTest.file?(dest), "Destination is not a file") end # Make sure files aren't replaced when replace is false, but otherwise # are. def test_replace dest = tempfile file = Puppet::Type.newfile( :path => dest, :content => "foobar", :recurse => true ) assert_apply(file) File.open(dest, "w") { |f| f.puts "yayness" } file[:replace] = false assert_apply(file) # Make sure it doesn't change. assert_equal("yayness\n", File.read(dest), "File got replaced when :replace was false") file[:replace] = true assert_apply(file) # Make sure it changes. assert_equal("foobar", File.read(dest), "File was not replaced when :replace was true") end def test_sourceselect dest = tempfile sources = [] 2.times { |i| i = i + 1 source = tempfile sources << source file = File.join(source, "file#{i}") Dir.mkdir(source) File.open(file, "w") { |f| f.print "yay" } } file1 = File.join(dest, "file1") file2 = File.join(dest, "file2") file3 = File.join(dest, "file3") # Now make different files with the same name in each source dir sources.each_with_index do |source, i| File.open(File.join(source, "file3"), "w") { |f| f.print i.to_s } end obj = Puppet::Type.newfile( :path => dest, :recurse => true, :source => sources) assert_equal(:first, obj[:sourceselect], "sourceselect has the wrong default") # First, make sure we default to just copying file1 assert_apply(obj) assert(FileTest.exists?(file1), "File from source 1 was not copied") assert(! FileTest.exists?(file2), "File from source 2 was copied") assert(FileTest.exists?(file3), "File from source 1 was not copied") assert_equal("0", File.read(file3), "file3 got wrong contents") # Now reset sourceselect assert_nothing_raised do obj[:sourceselect] = :all end File.unlink(file1) File.unlink(file3) Puppet.err :yay assert_apply(obj) assert(FileTest.exists?(file1), "File from source 1 was not copied") assert(FileTest.exists?(file2), "File from source 2 was copied") assert(FileTest.exists?(file3), "File from source 1 was not copied") assert_equal("0", File.read(file3), "file3 got wrong contents") end def test_recursive_sourceselect dest = tempfile source1 = tempfile source2 = tempfile files = [] [source1, source2, File.join(source1, "subdir"), File.join(source2, "subdir")].each_with_index do |dir, i| Dir.mkdir(dir) # Make a single file in each directory file = File.join(dir, "file#{i}") File.open(file, "w") { |f| f.puts "yay#{i}"} # Now make a second one in each directory file = File.join(dir, "second-file#{i}") File.open(file, "w") { |f| f.puts "yaysecond-#{i}"} files << file end obj = Puppet::Type.newfile(:path => dest, :source => [source1, source2], :sourceselect => :all, :recurse => true) assert_apply(obj) ["file0", "file1", "second-file0", "second-file1", "subdir/file2", "subdir/second-file2", "subdir/file3", "subdir/second-file3"].each do |file| path = File.join(dest, file) assert(FileTest.exists?(path), "did not create #{file}") assert_equal("yay#{File.basename(file).sub("file", '')}\n", File.read(path), "file was not copied correctly") end end # #594 def test_purging_missing_remote_files source = tempfile dest = tempfile s1 = File.join(source, "file1") s2 = File.join(source, "file2") d1 = File.join(dest, "file1") d2 = File.join(dest, "file2") Dir.mkdir(source) [s1, s2].each { |name| File.open(name, "w") { |file| file.puts "something" } } # We have to add a second parameter, because that's the only way to expose the "bug". file = Puppet::Type.newfile(:path => dest, :source => source, :recurse => true, :purge => true, :mode => "755") assert_apply(file) assert(FileTest.exists?(d1), "File1 was not copied") assert(FileTest.exists?(d2), "File2 was not copied") File.unlink(s2) assert_apply(file) assert(FileTest.exists?(d1), "File1 was not kept") assert(! FileTest.exists?(d2), "File2 was not purged") end end