diff --git a/lib/puppet/application.rb b/lib/puppet/application.rb index ad92aa11f..b009902bf 100644 --- a/lib/puppet/application.rb +++ b/lib/puppet/application.rb @@ -1,415 +1,414 @@ require 'optparse' require 'puppet/util/plugins' require 'puppet/error' # This class handles all the aspects of a Puppet application/executable # * setting up options # * setting up logs # * choosing what to run # * representing execution status # # === Usage # An application is a subclass of Puppet::Application. # # For legacy compatibility, # Puppet::Application[:example].run # is equivalent to # Puppet::Application::Example.new.run # # # class Puppet::Application::Example << Puppet::Application # # def preinit # # perform some pre initialization # @all = false # end # # # run_command is called to actually run the specified command # def run_command # send Puppet::Util::CommandLine.new.args.shift # end # # # option uses metaprogramming to create a method # # and also tells the option parser how to invoke that method # option("--arg ARGUMENT") do |v| # @args << v # end # # option("--debug", "-d") do |v| # @debug = v # end # # option("--all", "-a:) do |v| # @all = v # end # # def handle_unknown(opt,arg) # # last chance to manage an option # ... # # let's say to the framework we finally handle this option # true # end # # def read # # read action # end # # def write # # writeaction # end # # end # # === Preinit # The preinit block is the first code to be called in your application, before option parsing, # setup or command execution. # # === Options # Puppet::Application uses +OptionParser+ to manage the application options. # Options are defined with the +option+ method to which are passed various # arguments, including the long option, the short option, a description... # Refer to +OptionParser+ documentation for the exact format. # * If the option method is given a block, this one will be called whenever # the option is encountered in the command-line argument. # * If the option method has no block, a default functionnality will be used, that # stores the argument (or true/false if the option doesn't require an argument) in # the global (to the application) options array. # * If a given option was not defined by a the +option+ method, but it exists as a Puppet settings: # * if +unknown+ was used with a block, it will be called with the option name and argument # * if +unknown+ wasn't used, then the option/argument is handed to Puppet.settings.handlearg for # a default behavior # # --help is managed directly by the Puppet::Application class, but can be overriden. # # === Setup # Applications can use the setup block to perform any initialization. # The defaul +setup+ behaviour is to: read Puppet configuration and manage log level and destination # # === What and how to run # If the +dispatch+ block is defined it is called. This block should return the name of the registered command # to be run. # If it doesn't exist, it defaults to execute the +main+ command if defined. # # === Execution state # The class attributes/methods of Puppet::Application serve as a global place to set and query the execution # status of the application: stopping, restarting, etc. The setting of the application status does not directly # aftect its running status; it's assumed that the various components within the application will consult these # settings appropriately and affect their own processing accordingly. Control operations (signal handlers and # the like) should set the status appropriately to indicate to the overall system that it's the process of # stopping or restarting (or just running as usual). # # So, if something in your application needs to stop the process, for some reason, you might consider: # # def stop_me! # # indicate that we're stopping # Puppet::Application.stop! # # ...do stuff... # end # # And, if you have some component that involves a long-running process, you might want to consider: # # def my_long_process(giant_list_to_munge) # giant_list_to_munge.collect do |member| # # bail if we're stopping # return if Puppet::Application.stop_requested? # process_member(member) # end # end module Puppet class Application require 'puppet/util' include Puppet::Util DOCPATTERN = ::File.expand_path(::File.dirname(__FILE__) + "/util/command_line/*" ) class << self include Puppet::Util attr_accessor :run_status def clear! self.run_status = nil end def stop! self.run_status = :stop_requested end def restart! self.run_status = :restart_requested end # Indicates that Puppet::Application.restart! has been invoked and components should # do what is necessary to facilitate a restart. def restart_requested? :restart_requested == run_status end # Indicates that Puppet::Application.stop! has been invoked and components should do what is necessary # for a clean stop. def stop_requested? :stop_requested == run_status end # Indicates that one of stop! or start! was invoked on Puppet::Application, and some kind of process # shutdown/short-circuit may be necessary. def interrupted? [:restart_requested, :stop_requested].include? run_status end # Indicates that Puppet::Application believes that it's in usual running run_mode (no stop/restart request # currently active). def clear? run_status.nil? end # Only executes the given block if the run status of Puppet::Application is clear (no restarts, stops, # etc. requested). # Upon block execution, checks the run status again; if a restart has been requested during the block's # execution, then controlled_run will send a new HUP signal to the current process. # Thus, long-running background processes can potentially finish their work before a restart. def controlled_run(&block) return unless clear? result = block.call Process.kill(:HUP, $PID) if restart_requested? result end SHOULD_PARSE_CONFIG_DEPRECATION_MSG = "is no longer supported; config file parsing " + "is now controlled by the puppet engine, rather than by individual applications. This " + "method will be removed in a future version of puppet." def should_parse_config Puppet.deprecation_warning("should_parse_config " + SHOULD_PARSE_CONFIG_DEPRECATION_MSG) end def should_not_parse_config Puppet.deprecation_warning("should_not_parse_config " + SHOULD_PARSE_CONFIG_DEPRECATION_MSG) end def should_parse_config? Puppet.deprecation_warning("should_parse_config? " + SHOULD_PARSE_CONFIG_DEPRECATION_MSG) true end # used to declare code that handle an option def option(*options, &block) long = options.find { |opt| opt =~ /^--/ }.gsub(/^--(?:\[no-\])?([^ =]+).*$/, '\1' ).gsub('-','_') fname = symbolize("handle_#{long}") if (block_given?) define_method(fname, &block) else define_method(fname) do |value| self.options["#{long}".to_sym] = value end end self.option_parser_commands << [options, fname] end def banner(banner = nil) @banner ||= banner end def option_parser_commands @option_parser_commands ||= ( superclass.respond_to?(:option_parser_commands) ? superclass.option_parser_commands.dup : [] ) @option_parser_commands end def find(name) klass = name.to_s.capitalize begin require ::File.join('puppet', 'application', name.to_s.downcase) rescue LoadError => e puts "Unable to find application '#{name}'. #{e}" Kernel::exit(1) end self.const_get(klass) end def [](name) find(name).new end # # I think that it would be nice to look into changing this into two methods (getter/setter); however, # it sounds like this is a desirable feature of our ruby DSL. --cprice 2012-03-06 # # Sets or gets the run_mode name. Sets the run_mode name if a mode_name is # passed. Otherwise, gets the run_mode or a default run_mode # def run_mode( mode_name = nil) return @run_mode if @run_mode and not mode_name require 'puppet/util/run_mode' @run_mode = Puppet::Util::RunMode[ mode_name || :user ] end end attr_reader :options, :command_line # Every app responds to --version option("--version", "-V") do |arg| puts "#{Puppet.version}" exit end # Every app responds to --help option("--help", "-h") do |v| puts help exit end def app_defaults() { - :name => name, :run_mode => self.class.run_mode.name, :confdir => self.class.run_mode.conf_dir, :vardir => self.class.run_mode.var_dir, :rundir => self.class.run_mode.run_dir, :logdir => self.class.run_mode.log_dir, } end def initialize_app_defaults() Puppet.settings.initialize_app_defaults(app_defaults) end # override to execute code before running anything else def preinit end def initialize(command_line = nil) require 'puppet/util/command_line' @command_line = command_line || Puppet::Util::CommandLine.new @options = {} end # This is the main application entry point def run # I don't really like the names of these lifecycle phases. It would be nice to change them to some more meaningful # names, and make deprecated aliases. Also, Daniel suggests that we can probably get rid of this "plugin_hook" # pattern, but we need to check with PE and the community first. --cprice 2012-03-16 # exit_on_fail("get application-specific default settings") do plugin_hook('initialize_app_defaults') { initialize_app_defaults } end require 'puppet' require 'puppet/util/instrumentation' Puppet::Util::Instrumentation.init exit_on_fail("initialize") { plugin_hook('preinit') { preinit } } exit_on_fail("parse application options") { plugin_hook('parse_options') { parse_options } } exit_on_fail("prepare for execution") { plugin_hook('setup') { setup } } exit_on_fail("configure routes from #{Puppet[:route_file]}") { configure_indirector_routes } exit_on_fail("run") { plugin_hook('run_command') { run_command } } end def main raise NotImplementedError, "No valid command or main" end def run_command main end def setup setup_logs end def setup_logs if options[:debug] or options[:verbose] Puppet::Util::Log.newdestination(:console) if options[:debug] Puppet::Util::Log.level = :debug else Puppet::Util::Log.level = :info end end Puppet::Util::Log.setup_default unless options[:setdest] end def configure_indirector_routes route_file = Puppet[:route_file] if ::File.exists?(route_file) routes = YAML.load_file(route_file) application_routes = routes[name.to_s] Puppet::Indirector.configure_routes(application_routes) if application_routes end end def parse_options # Create an option parser option_parser = OptionParser.new(self.class.banner) # He're we're building up all of the options that the application may need to handle. The main # puppet settings defined in "defaults.rb" have already been parsed once (in command_line.rb) by # the time we get here; however, our app may wish to handle some of them specially, so we need to # make the parser aware of them again. We might be able to make this a bit more efficient by # re-using the parser object that gets built up in command_line.rb. --cprice 2012-03-16 # Add all global options to it. Puppet.settings.optparse_addargs([]).each do |option| option_parser.on(*option) do |arg| handlearg(option[0], arg) end end # Add options that are local to this application, which were # created using the "option()" metaprogramming method. If there # are any conflicts, this application's options will be favored. self.class.option_parser_commands.each do |options, fname| option_parser.on(*options) do |value| # Call the method that "option()" created. self.send(fname, value) end end # Scan command line. We just hand any exceptions to our upper levels, # rather than printing help and exiting, so that we can meaningfully # respond with context-sensitive help if we want to. --daniel 2011-04-12 option_parser.parse!(self.command_line.args) end def handlearg(opt, val) opt, val = Puppet::Util::CommandLine.clean_opt(opt, val) send(:handle_unknown, opt, val) if respond_to?(:handle_unknown) end # this is used for testing def self.exit(code) exit(code) end def name self.class.to_s.sub(/.*::/,"").downcase.to_sym end def help "No help available for puppet #{name}" end def plugin_hook(step,&block) Puppet::Plugins.send("before_application_#{step}",:application_object => self) x = yield Puppet::Plugins.send("after_application_#{step}",:application_object => self, :return_value => x) x end private :plugin_hook end end diff --git a/lib/puppet/application/queue.rb b/lib/puppet/application/queue.rb index 22bdfe005..e0304def4 100644 --- a/lib/puppet/application/queue.rb +++ b/lib/puppet/application/queue.rb @@ -1,158 +1,162 @@ require 'puppet/application' require 'puppet/util' class Puppet::Application::Queue < Puppet::Application attr_accessor :daemon + + def app_defaults() + super.merge( :pidfile => "$rundir/queue.pid" ) + end def preinit require 'puppet/daemon' @daemon = Puppet::Daemon.new @daemon.argv = ARGV.dup # Do an initial trap, so that cancels don't get a stack trace. # This exits with exit code 1 Signal.trap(:INT) do $stderr.puts "Caught SIGINT; shutting down" exit(1) end # This is a normal shutdown, so code 0 Signal.trap(:TERM) do $stderr.puts "Caught SIGTERM; shutting down" exit(0) end { :verbose => false, :debug => false }.each do |opt,val| options[opt] = val end end option("--debug","-d") option("--verbose","-v") def help <<-HELP puppet-queue(8) -- Queuing daemon for asynchronous storeconfigs ======== SYNOPSIS -------- Retrieves serialized storeconfigs records from a queue and processes them in order. USAGE ----- puppet queue [-d|--debug] [-v|--verbose] DESCRIPTION ----------- This application runs as a daemon and processes storeconfigs data, retrieving the data from a stomp server message queue and writing it to a database. For more information, including instructions for properly setting up your puppet master and message queue, see the documentation on setting up asynchronous storeconfigs at: http://projects.puppetlabs.com/projects/1/wiki/Using_Stored_Configuration OPTIONS ------- Note that any configuration parameter that's valid in the configuration file is also a valid long argument. For example, 'server' is a valid configuration parameter, so you can specify '--server ' as an argument. See the configuration file documentation at http://docs.puppetlabs.com/references/stable/configuration.html for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet queue with '--genconfig'. * --debug: Enable full debugging. * --help: Print this help message * --verbose: Turn on verbose reporting. * --version: Print the puppet version number and exit. EXAMPLE ------- $ puppet queue AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end option("--logdest DEST", "-l DEST") do |arg| begin Puppet::Util::Log.newdestination(arg) options[:setdest] = true rescue => detail Puppet.log_exception(detail) end end def main require 'puppet/indirector/catalog/queue' # provides Puppet::Indirector::Queue.subscribe Puppet.notice "Starting puppetqd #{Puppet.version}" Puppet::Resource::Catalog::Queue.subscribe do |catalog| # Once you have a Puppet::Resource::Catalog instance, passing it to save should suffice # to put it through to the database via its active_record indirector (which is determined # by the terminus_class = :active_record setting above) Puppet::Util.benchmark(:notice, "Processing queued catalog for #{catalog.name}") do begin Puppet::Resource::Catalog.indirection.save(catalog) rescue => detail Puppet.log_exception(detail, "Could not save queued catalog for #{catalog.name}: #{detail}") end end end Thread.list.each { |thread| thread.join } end def setup unless Puppet.features.stomp? raise ArgumentError, "Could not load the 'stomp' library, which must be present for queueing to work. You must install the required library." end setup_logs exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? require 'puppet/resource/catalog' Puppet::Resource::Catalog.indirection.terminus_class = :store_configs daemon.daemonize if Puppet[:daemonize] # We want to make sure that we don't have a cache # class set up, because if storeconfigs is enabled, # we'll get a loop of continually caching the catalog # for storage again. Puppet::Resource::Catalog.indirection.cache_class = nil end end diff --git a/lib/puppet/daemon.rb b/lib/puppet/daemon.rb index fc60f2ed9..15358f081 100755 --- a/lib/puppet/daemon.rb +++ b/lib/puppet/daemon.rb @@ -1,218 +1,218 @@ require 'puppet' require 'puppet/util/pidlock' require 'puppet/application' # A module that handles operations common to all daemons. This is included # into the Server and Client base classes. class Puppet::Daemon attr_accessor :agent, :server, :argv def daemonname - Puppet[:name] + Puppet.run_mode.name end # Put the daemon into the background. def daemonize if pid = fork Process.detach(pid) exit(0) end create_pidfile # Get rid of console logging Puppet::Util::Log.close(:console) Process.setsid Dir.chdir("/") end # Close stdin/stdout/stderr so that we can finish our transition into 'daemon' mode. # @return nil def self.close_streams() Puppet.debug("Closing streams for daemon mode") begin $stdin.reopen "/dev/null" $stdout.reopen "/dev/null", "a" $stderr.reopen $stdout Puppet::Util::Log.reopen Puppet.debug("Finished closing streams for daemon mode") rescue => detail - Puppet.err "Could not start #{Puppet[:name]}: #{detail}" + Puppet.err "Could not start #{Puppet.run_mode.name}: #{detail}" Puppet::Util::replace_file("/tmp/daemonout", 0644) do |f| - f.puts "Could not start #{Puppet[:name]}: #{detail}" + f.puts "Could not start #{Puppet.run_mode.name}: #{detail}" end exit(12) end end # Convenience signature for calling Puppet::Daemon.close_streams def close_streams() Puppet::Daemon.close_streams end # Create a pidfile for our daemon, so we can be stopped and others # don't try to start. def create_pidfile - Puppet::Util.synchronize_on(Puppet[:name],Sync::EX) do + Puppet::Util.synchronize_on(Puppet.run_mode.name,Sync::EX) do raise "Could not create PID file: #{pidfile}" unless Puppet::Util::Pidlock.new(pidfile).lock end end # Provide the path to our pidfile. def pidfile Puppet[:pidfile] end def reexec raise Puppet::DevError, "Cannot reexec unless ARGV arguments are set" unless argv command = $0 + " " + argv.join(" ") Puppet.notice "Restarting with '#{command}'" stop(:exit => false) exec(command) end def reload return unless agent if agent.running? Puppet.notice "Not triggering already-running agent" return end agent.run end # Remove the pid file for our daemon. def remove_pidfile - Puppet::Util.synchronize_on(Puppet[:name],Sync::EX) do + Puppet::Util.synchronize_on(Puppet.run_mode.name,Sync::EX) do Puppet::Util::Pidlock.new(pidfile).unlock end end def restart Puppet::Application.restart! reexec unless agent and agent.running? end def reopen_logs Puppet::Util::Log.reopen end # Trap a couple of the main signals. This should probably be handled # in a way that anyone else can register callbacks for traps, but, eh. def set_signal_traps signals = {:INT => :stop, :TERM => :stop } # extended signals not supported under windows signals.update({:HUP => :restart, :USR1 => :reload, :USR2 => :reopen_logs }) unless Puppet.features.microsoft_windows? signals.each do |signal, method| Signal.trap(signal) do Puppet.notice "Caught #{signal}; calling #{method}" send(method) end end end # Stop everything def stop(args = {:exit => true}) Puppet::Application.stop! server.stop if server remove_pidfile Puppet::Util::Log.close_all exit if args[:exit] end def start set_signal_traps create_pidfile raise Puppet::DevError, "Daemons must have an agent, server, or both" unless agent or server # Start the listening server, if required. server.start if server # now that the server has started, we've waited just about as long as possible to close # our streams and become a "real" daemon process. This is in hopes of allowing # errors to have the console available as a fallback for logging for as long as # possible. close_streams if Puppet[:daemonize] # Finally, loop forever running events - or, at least, until we exit. run_event_loop end def run_event_loop # Now, we loop waiting for either the configuration file to change, or the # next agent run to be due. Fun times. # # We want to trigger the reparse if 15 seconds passed since the previous # wakeup, and the agent run if Puppet[:runinterval] seconds have passed # since the previous wakeup. # # We always want to run the agent on startup, so it was always before now. # Because 0 means "continuously run", `to_i` does the right thing when the # input is strange or badly formed by returning 0. Integer will raise, # which we don't want, and we want to protect against -1 or below. next_agent_run = 0 agent_run_interval = [Puppet[:runinterval].to_i, 0].max # We may not want to reparse; that can be disable. Fun times. next_reparse = 0 reparse_interval = Puppet[:filetimeout].to_i loop do now = Time.now.to_i # We set a default wakeup of "one hour from now", which will # recheck everything at a minimum every hour. Just in case something in # the math messes up or something; it should be inexpensive enough to # wake once an hour, then go back to sleep after doing nothing, if # someone only wants listen mode. next_event = now + 60 * 60 # Handle reparsing of configuration files, if desired and required. # `reparse` will just check if the action is required, and would be # better named `reparse_if_changed` instead. if reparse_interval > 0 and now >= next_reparse Puppet.settings.reparse # The time to the next reparse might have changed, so recalculate # now. That way we react dynamically to reconfiguration. reparse_interval = Puppet[:filetimeout].to_i # Set up the next reparse check based on the new reparse_interval. if reparse_interval > 0 next_reparse = now + reparse_interval next_event > next_reparse and next_event = next_reparse end # We should also recalculate the agent run interval, and adjust the # next time it is scheduled to run, just in case. In the event that # we made no change the result will be a zero second adjustment. new_run_interval = [Puppet[:runinterval].to_i, 0].max next_agent_run += agent_run_interval - new_run_interval agent_run_interval = new_run_interval end # Handle triggering another agent run. This will block the next check # for configuration reparsing, which is a desired and deliberate # behaviour. You should not change that. --daniel 2012-02-21 if agent and now >= next_agent_run agent.run # Set up the next agent run time next_agent_run = now + agent_run_interval next_event > next_agent_run and next_event = next_agent_run end # Finally, an interruptable able sleep until the next scheduled event. how_long = next_event - now how_long > 0 and select([], [], [], how_long) end end end diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index b5798299a..0f8770029 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -1,1484 +1,1484 @@ # The majority of Puppet's configuration settings are set in this file. module Puppet ############################################################################################ # NOTE: For information about the available values for the ":type" property of settings, # see the docs for Settings.define_settings ############################################################################################ define_settings(:main, :confdir => { :default => nil, :type => :directory, :desc => "The main Puppet configuration directory. The default for this setting is calculated based on the user. If the process\n" + "is running as root or the user that Puppet is supposed to run as, it defaults to a system directory, but if it's running as any other user,\n" + "it defaults to being in the user's home directory.", }, :vardir => { :default => nil, :type => :directory, :desc => "Where Puppet stores dynamic and growing data. The default for this setting is calculated specially, like `confdir`_.", }, ### NOTE: this setting is usually being set to a symbol value. We don't officially have a ### setting type for that yet, but we might want to consider creating one. :name => { :default => nil, :desc => "The name of the application, if we are running as one. The\n" + "default is essentially $0 without the path or `.rb`.", }, ## This setting needs to go away. As a first step, we could just make it a first-class property of the Settings ## class, instead of treating it as a normal setting. There are places where the Settings class tries to use ## the value of run_mode to help in resolving other values, and that is no good for nobody. It would cause ## infinite recursion and stack overflows without some chicanery... so, it needs to be cleaned up. ## ## As a longer term goal I think we should be looking into getting rid of run_mode altogether, but that is going ## to be a larger undertaking, as it is being branched on in a lot of places in the current code. ## ## --cprice 2012-03-16 :run_mode => { :default => nil, :desc => "The effective 'run mode' of the application: master, agent, or user.", } ) define_settings(:main, :logdir => { :default => nil, :type => :directory, :mode => 0750, :owner => "service", :group => "service", :desc => "The directory in which to store log files", } ) define_settings(:main, :trace => { :default => false, :type => :boolean, :desc => "Whether to print stack traces on some errors", }, :autoflush => { :default => true, :type => :boolean, :desc => "Whether log files should always flush to disk.", :hook => proc { |value| Log.autoflush = value } }, :syslogfacility => { :default => "daemon", :desc => "What syslog facility to use when logging to\n" + "syslog. Syslog has a fixed list of valid facilities, and you must\n" + "choose one of those; you cannot just make one up." }, :statedir => { :default => "$vardir/state", :type => :directory, :mode => 01755, :desc => "The directory where Puppet state is stored. Generally, this directory can be removed without causing harm (although it might result in spurious service restarts)." }, :rundir => { :default => nil, :type => :directory, :mode => 01777, :desc => "Where Puppet PID files are kept." }, :genconfig => { :default => false, :type => :boolean, :desc => "Whether to just print a configuration to stdout and exit. Only makes\n" + "sense when used interactively. Takes into account arguments specified\n" + "on the CLI.", }, :genmanifest => { :default => false, :type => :boolean, :desc => "Whether to just print a manifest to stdout and exit. Only makes\n" + "sense when used interactively. Takes into account arguments specified\n" + "on the CLI.", }, :configprint => { :default => "", :desc => "Print the value of a specific configuration setting. If the name of a\n" + "setting is provided for this, then the value is printed and puppet\n" + "exits. Comma-separate multiple values. For a list of all values,\n" + "specify 'all'.", }, :color => { :default => (Puppet.features.microsoft_windows? ? "false" : "ansi"), :type => :string, :desc => "Whether to use colors when logging to the console. Valid values are\n" + "`ansi` (equivalent to `true`), `html`, and `false`, which produces no color.\n" + "Defaults to false on Windows, as its console does not support ansi colors.", }, :mkusers => { :default => false, :type => :boolean, :desc => "Whether to create the necessary user and group that puppet agent will run as.", }, :manage_internal_file_permissions => { :default => true, :type => :boolean, :desc => "Whether Puppet should manage the owner, group, and mode of files it uses internally", }, :onetime => { :default => false, :type => :boolean, :desc => "Run the configuration once, rather than as a long-running\n" + "daemon. This is useful for interactively running puppetd.", :short => 'o', }, :path => { :default => "none", :desc => "The shell search path. Defaults to whatever is inherited\n" + "from the parent process.", :call_on_define => true, # Call our hook with the default value, so we always get the libdir set. :hook => proc do |value| ENV["PATH"] = "" if ENV["PATH"].nil? ENV["PATH"] = value unless value == "none" paths = ENV["PATH"].split(File::PATH_SEPARATOR) %w{/usr/sbin /sbin}.each do |path| ENV["PATH"] += File::PATH_SEPARATOR + path unless paths.include?(path) end value end }, :libdir => { :type => :directory, :default => "$vardir/lib", :desc => "An extra search path for Puppet. This is only useful\n" + "for those files that Puppet will load on demand, and is only\n" + "guaranteed to work for those cases. In fact, the autoload\n" + "mechanism is responsible for making sure this directory\n" + "is in Ruby's search path\n", #:call_on_define => true, # Call our hook with the default value, so we always get the libdir set. :hook => proc do |value| $LOAD_PATH.delete(@oldlibdir) if defined?(@oldlibdir) and $LOAD_PATH.include?(@oldlibdir) @oldlibdir = value $LOAD_PATH << value end }, :ignoreimport => { :default => false, :type => :boolean, :desc => "If true, allows the parser to continue without requiring\n" + "all files referenced with `import` statements to exist. This setting was primarily\n" + "designed for use with commit hooks for parse-checking.", }, :authconfig => { :default => "$confdir/namespaceauth.conf", :desc => "The configuration file that defines the rights to the different\n" + "namespaces and methods. This can be used as a coarse-grained\n" + "authorization system for both `puppet agent` and `puppet master`.", }, :environment => { :default => "production", :desc => "The environment Puppet is running in. For clients\n" + "(e.g., `puppet agent`) this determines the environment itself, which\n" + "is used to find modules and much more. For servers (i.e., `puppet master`)\n" + "this provides the default environment for nodes we know nothing about." }, :diff_args => { :default => "-u", :desc => "Which arguments to pass to the diff command when printing differences between\n" + "files. The command to use can be chosen with the `diff` setting.", }, :diff => { :default => (Puppet.features.microsoft_windows? ? "" : "diff"), :desc => "Which diff command to use when printing differences between files. This setting\n" + "has no default value on Windows, as standard `diff` is not available, but Puppet can use many\n" + "third-party diff tools.", }, :show_diff => { :type => :boolean, :default => false, :desc => "Whether to log and report a contextual diff when files are being replaced. This causes\n" + "partial file contents to pass through Puppet's normal logging and reporting system, so this setting\n" + "should be used with caution if you are sending Puppet's reports to an insecure destination.\n" + "This feature currently requires the `diff/lcs` Ruby library.", }, :daemonize => { :type => :boolean, :default => (Puppet.features.microsoft_windows? ? false : true), :desc => "Whether to send the process into the background. This defaults to true on POSIX systems, and to false on Windows (where Puppet currently cannot daemonize).", :short => "D", :hook => proc do |value| if value and Puppet.features.microsoft_windows? raise "Cannot daemonize on Windows" end end }, :maximum_uid => { :default => 4294967290, :desc => "The maximum allowed UID. Some platforms use negative UIDs\n" + "but then ship with tools that do not know how to handle signed ints, so the UIDs show up as\n" + "huge numbers that can then not be fed back into the system. This is a hackish way to fail in a\n" + "slightly more useful way when that happens.", }, :route_file => { :default => "$confdir/routes.yaml", :desc => "The YAML file containing indirector route configuration.", }, :node_terminus => { :default => "plain", :desc => "Where to find information about nodes.", }, :catalog_terminus => { :default => "compiler", :desc => "Where to get node catalogs. This is useful to change if, for instance, you'd like to pre-compile catalogs and store them in memcached or some other easily-accessed store.", }, :facts_terminus => { :default => 'facter', :desc => "The node facts terminus.", :hook => proc do |value| require 'puppet/node/facts' # Cache to YAML if we're uploading facts away if %w[rest inventory_service].include? value.to_s Puppet::Node::Facts.indirection.cache_class = :yaml end end }, :inventory_terminus => { :default => "$facts_terminus", :desc => "Should usually be the same as the facts terminus", }, :httplog => { :default => "$logdir/http.log", :type => :file, :owner => "root", :mode => 0640, :desc => "Where the puppet agent web server logs.", }, :http_proxy_host => { :default => "none", :desc => "The HTTP proxy host to use for outgoing connections. Note: You may need to use a FQDN for the server hostname when using a proxy.", }, :http_proxy_port => { :default => 3128, :desc => "The HTTP proxy port to use for outgoing connections", }, :filetimeout => { :default => 15, :desc => "The minimum time to wait (in seconds) between checking for updates in configuration files. This timeout determines how quickly Puppet checks whether a file (such as manifests or templates) has changed on disk.", }, :queue_type => { :default => "stomp", :desc => "Which type of queue to use for asynchronous processing.", }, :queue_type => { :default => "stomp", :desc => "Which type of queue to use for asynchronous processing.", }, :queue_source => { :default => "stomp://localhost:61613/", :desc => "Which type of queue to use for asynchronous processing. If your stomp server requires authentication, you can include it in the URI as long as your stomp client library is at least 1.1.1", }, :async_storeconfigs => { :default => false, :type => :boolean, :desc => "Whether to use a queueing system to provide asynchronous database integration. Requires that `puppetqd` be running and that 'PSON' support for ruby be installed.", :hook => proc do |value| if value # This reconfigures the terminii for Node, Facts, and Catalog Puppet.settings[:storeconfigs] = true # But then we modify the configuration Puppet::Resource::Catalog.indirection.cache_class = :queue else raise "Cannot disable asynchronous storeconfigs in a running process" end end }, :thin_storeconfigs => { :default => false, :type => :boolean, :desc => "Boolean; whether storeconfigs store in the database only the facts and exported resources. If true, then storeconfigs performance will be higher and still allow exported/collected resources, but other usage external to Puppet might not work", :hook => proc do |value| Puppet.settings[:storeconfigs] = true if value end }, :config_version => { :default => "", :desc => "How to determine the configuration version. By default, it will be the time that the configuration is parsed, but you can provide a shell script to override how the version is determined. The output of this script will be added to every log message in the reports, allowing you to correlate changes on your hosts to the source version on the server.", }, :zlib => { :default => true, :type => :boolean, :desc => "Boolean; whether to use the zlib library", }, :prerun_command => { :default => "", :desc => "A command to run before every agent run. If this command returns a non-zero return code, the entire Puppet run will fail.", }, :postrun_command => { :default => "", :desc => "A command to run after every agent run. If this command returns a non-zero return code, the entire Puppet run will be considered to have failed, even though it might have performed work during the normal run.", }, :freeze_main => { :default => false, :type => :boolean, :desc => "Freezes the 'main' class, disallowing any code to be added to it. This\n" + "essentially means that you can't have any code outside of a node, class, or definition other\n" + "than in the site manifest.", } ) Puppet.define_settings(:module_tool, :module_repository => { :default => 'http://forge.puppetlabs.com', :desc => "The module repository", }, :module_working_dir => { :default => '$vardir/puppet-module', :desc => "The directory into which module tool data is stored", } ) hostname = Facter["hostname"].value domain = Facter["domain"].value if domain and domain != "" fqdn = [hostname, domain].join(".") else fqdn = hostname end Puppet.define_settings( :main, # We have to downcase the fqdn, because the current ssl stuff (as oppsed to in master) doesn't have good facilities for # manipulating naming. :certname => { :default => fqdn.downcase, :desc => "The name to use when handling certificates. Defaults to the fully qualified domain name.", :call_on_define => true, # Call our hook with the default value, so we're always downcased :hook => proc { |value| raise(ArgumentError, "Certificate names must be lower case; see #1168") unless value == value.downcase }}, :certdnsnames => { :default => '', :hook => proc do |value| unless value.nil? or value == '' then Puppet.warning < < { :default => '', :desc => < { :default => "$ssldir/certs", :type => :directory, :owner => "service", :desc => "The certificate directory." }, :ssldir => { :default => "$confdir/ssl", :type => :directory, :mode => 0771, :owner => "service", :desc => "Where SSL certificates are kept." }, :publickeydir => { :default => "$ssldir/public_keys", :type => :directory, :owner => "service", :desc => "The public key directory." }, :requestdir => { :default => "$ssldir/certificate_requests", :type => :directory, :type => :directory, :owner => "service", :desc => "Where host certificate requests are stored." }, :privatekeydir => { :default => "$ssldir/private_keys", :type => :directory, :mode => 0750, :owner => "service", :desc => "The private key directory." }, :privatedir => { :default => "$ssldir/private", :type => :directory, :mode => 0750, :owner => "service", :desc => "Where the client stores private certificate information." }, :passfile => { :default => "$privatedir/password", :type => :file, :mode => 0640, :owner => "service", :desc => "Where puppet agent stores the password for its private key. Generally unused." }, :hostcsr => { :default => "$ssldir/csr_$certname.pem", :type => :file, :mode => 0644, :owner => "service", :desc => "Where individual hosts store and look for their certificate requests." }, :hostcert => { :default => "$certdir/$certname.pem", :type => :file, :mode => 0644, :owner => "service", :desc => "Where individual hosts store and look for their certificates." }, :hostprivkey => { :default => "$privatekeydir/$certname.pem", :type => :file, :mode => 0600, :owner => "service", :desc => "Where individual hosts store and look for their private key." }, :hostpubkey => { :default => "$publickeydir/$certname.pem", :type => :file, :mode => 0644, :owner => "service", :desc => "Where individual hosts store and look for their public key." }, :localcacert => { :default => "$certdir/ca.pem", :type => :file, :mode => 0644, :owner => "service", :desc => "Where each client stores the CA certificate." }, :hostcrl => { :default => "$ssldir/crl.pem", :type => :file, :mode => 0644, :owner => "service", :desc => "Where the host's certificate revocation list can be found. This is distinct from the certificate authority's CRL." }, :certificate_revocation => { :default => true, :type => :boolean, :desc => "Whether certificate revocation should be supported by downloading a Certificate Revocation List (CRL) to all clients. If enabled, CA chaining will almost definitely not work.", } ) define_settings( :ca, :ca_name => { :default => "Puppet CA: $certname", :desc => "The name to use the Certificate Authority certificate.", }, :cadir => { :default => "$ssldir/ca", :type => :directory, :owner => "service", :group => "service", :mode => 0770, :desc => "The root directory for the certificate authority." }, :cacert => { :default => "$cadir/ca_crt.pem", :type => :file, :owner => "service", :group => "service", :mode => 0660, :desc => "The CA certificate." }, :cakey => { :default => "$cadir/ca_key.pem", :type => :file, :owner => "service", :group => "service", :mode => 0660, :desc => "The CA private key." }, :capub => { :default => "$cadir/ca_pub.pem", :type => :file, :owner => "service", :group => "service", :desc => "The CA public key." }, :cacrl => { :default => "$cadir/ca_crl.pem", :type => :file, :owner => "service", :group => "service", :mode => 0664, :desc => "The certificate revocation list (CRL) for the CA. Will be used if present but otherwise ignored.", :hook => proc do |value| if value == 'false' Puppet.deprecation_warning "Setting the :cacrl to 'false' is deprecated; Puppet will just ignore the crl if yours is missing" end end }, :caprivatedir => { :default => "$cadir/private", :type => :directory, :owner => "service", :group => "service", :mode => 0770, :desc => "Where the CA stores private certificate information." }, :csrdir => { :default => "$cadir/requests", :type => :directory, :owner => "service", :group => "service", :desc => "Where the CA stores certificate requests" }, :signeddir => { :default => "$cadir/signed", :type => :directory, :owner => "service", :group => "service", :mode => 0770, :desc => "Where the CA stores signed certificates." }, :capass => { :default => "$caprivatedir/ca.pass", :type => :file, :owner => "service", :group => "service", :mode => 0660, :desc => "Where the CA stores the password for the private key" }, :serial => { :default => "$cadir/serial", :type => :file, :owner => "service", :group => "service", :mode => 0644, :desc => "Where the serial number for certificates is stored." }, :autosign => { :default => "$confdir/autosign.conf", :type => :file, :mode => 0644, :desc => "Whether to enable autosign. Valid values are true (which autosigns any key request, and is a very bad idea), false (which never autosigns any key request), and the path to a file, which uses that configuration file to determine which keys to sign."}, :allow_duplicate_certs => { :default => false, :type => :boolean, :desc => "Whether to allow a new certificate request to overwrite an existing certificate.", }, :ca_days => { :default => "", :desc => "How long a certificate should be valid, in days. This setting is deprecated; use `ca_ttl` instead", }, :ca_ttl => { :default => "5y", :desc => "The default TTL for new certificates; valid values must be an integer, optionally followed by one of the units 'y' (years of 365 days), 'd' (days), 'h' (hours), or 's' (seconds). The unit defaults to seconds. If this setting is set, ca_days is ignored. Examples are '3600' (one hour) and '1825d', which is the same as '5y' (5 years) ", }, :ca_md => { :default => "md5", :desc => "The type of hash used in certificates.", }, :req_bits => { :default => 4096, :desc => "The bit length of the certificates.", }, :keylength => { :default => 4096, :desc => "The bit length of keys.", }, :cert_inventory => { :default => "$cadir/inventory.txt", :type => :file, :mode => 0644, :owner => "service", :group => "service", :desc => "A Complete listing of all certificates" } ) # Define the config default. define_settings(:application, :config_file_name => { :type => :string, :default => Puppet::Util::Settings.default_config_file_name, :desc => "The name of the puppet config file.", }, :config => { :type => :file, :default => "$confdir/${config_file_name}", :desc => "The configuration file for the current puppet application", }, :pidfile => { :type => :file, - :default => "$rundir/$name.pid", + :default => "$rundir/#{Puppet.run_mode.name}.pid", :desc => "The pid file", }, :bindaddress => { :default => "", :desc => "The address a listening server should bind to. Mongrel servers default to 127.0.0.1 and WEBrick defaults to 0.0.0.0.", }, :servertype => { :default => "webrick", :desc => "The type of server to use. Currently supported options are webrick and mongrel. If you use mongrel, you will need a proxy in front of the process or processes, since Mongrel cannot speak SSL.", :call_on_define => true, # Call our hook with the default value, so we always get the correct bind address set. :hook => proc { |value| value == "webrick" ? Puppet.settings[:bindaddress] = "0.0.0.0" : Puppet.settings[:bindaddress] = "127.0.0.1" if Puppet.settings[:bindaddress] == "" } } ) define_settings(:master, :user => { :default => "puppet", :desc => "The user puppet master should run as.", }, :group => { :default => "puppet", :desc => "The group puppet master should run as.", }, :manifestdir => { :default => "$confdir/manifests", :type => :directory, :desc => "Where puppet master looks for its manifests.", }, :manifest => { :default => "$manifestdir/site.pp", :type => :file, :desc => "The entry-point manifest for puppet master.", }, :code => { :default => "", :desc => "Code to parse directly. This is essentially only used by `puppet`, and should only be set if you're writing your own Puppet executable", }, :masterlog => { :default => "$logdir/puppetmaster.log", :type => :file, :owner => "service", :group => "service", :mode => 0660, :desc => "Where puppet master logs. This is generally not used, since syslog is the default log destination." }, :masterhttplog => { :default => "$logdir/masterhttp.log", :type => :file, :owner => "service", :group => "service", :mode => 0660, :create => true, :desc => "Where the puppet master web server logs." }, :masterport => { :default => 8140, :desc => "Which port puppet master listens on.", }, :node_name => { :default => "cert", :desc => "How the puppet master determines the client's identity and sets the 'hostname', 'fqdn' and 'domain' facts for use in the manifest, in particular for determining which 'node' statement applies to the client. Possible values are 'cert' (use the subject's CN in the client's certificate) and 'facter' (use the hostname that the client reported in its facts)", }, :bucketdir => { :default => "$vardir/bucket", :type => :directory, :mode => 0750, :owner => "service", :group => "service", :desc => "Where FileBucket files are stored." }, :rest_authconfig => { :default => "$confdir/auth.conf", :type => :file, :desc => "The configuration file that defines the rights to the different rest indirections. This can be used as a fine-grained authorization system for `puppet master`.", }, :ca => { :default => true, :type => :boolean, :desc => "Whether the master should function as a certificate authority.", }, :modulepath => { :default => "$confdir/modules#{File::PATH_SEPARATOR}/usr/share/puppet/modules", :type => :path, :desc => "The search path for modules, as a list of directories separated by the system path separator character. " + "(The POSIX path separator is ':', and the Windows path separator is ';'.)", }, :ssl_client_header => { :default => "HTTP_X_CLIENT_DN", :desc => "The header containing an authenticated client's SSL DN. Only used with Mongrel. This header must be set by the proxy to the authenticated client's SSL DN (e.g., `/CN=puppet.puppetlabs.com`). See http://projects.puppetlabs.com/projects/puppet/wiki/Using_Mongrel for more information.", }, :ssl_client_verify_header => { :default => "HTTP_X_CLIENT_VERIFY", :desc => "The header containing the status message of the client verification. Only used with Mongrel. This header must be set by the proxy to 'SUCCESS' if the client successfully authenticated, and anything else otherwise. See http://projects.puppetlabs.com/projects/puppet/wiki/Using_Mongrel for more information.", }, # To make sure this directory is created before we try to use it on the server, we need # it to be in the server section (#1138). :yamldir => { :default => "$vardir/yaml", :type => :directory, :owner => "service", :group => "service", :mode => "750", :desc => "The directory in which YAML data is stored, usually in a subdirectory."}, :server_datadir => { :default => "$vardir/server_data", :type => :directory, :owner => "service", :group => "service", :mode => "750", :desc => "The directory in which serialized data is stored, usually in a subdirectory."}, :reports => { :default => "store", :desc => "The list of reports to generate. All reports are looked for in `puppet/reports/name.rb`, and multiple report names should be comma-separated (whitespace is okay).", }, :reportdir => { :default => "$vardir/reports", :type => :directory, :mode => 0750, :owner => "service", :group => "service", :desc => "The directory in which to store reports received from the client. Each client gets a separate subdirectory."}, :reporturl => { :default => "http://localhost:3000/reports/upload", :desc => "The URL used by the http reports processor to send reports", }, :fileserverconfig => { :default => "$confdir/fileserver.conf", :type => :file, :desc => "Where the fileserver configuration is stored.", }, :strict_hostname_checking => { :default => false, :desc => "Whether to only search for the complete hostname as it is in the certificate when searching for node information in the catalogs.", } ) define_settings(:metrics, :rrddir => { :type => :directory, :default => "$vardir/rrd", :mode => 0750, :owner => "service", :group => "service", :desc => "The directory where RRD database files are stored. Directories for each reporting host will be created under this directory." }, :rrdinterval => { :default => "$runinterval", :desc => "How often RRD should expect data. This should match how often the hosts report back to the server.", } ) define_settings(:device, :devicedir => { :default => "$vardir/devices", :type => :directory, :mode => "750", :desc => "The root directory of devices' $vardir", }, :deviceconfig => { :default => "$confdir/device.conf", :desc => "Path to the device config file for puppet device", } ) define_settings(:agent, :node_name_value => { :default => "$certname", :desc => "The explicit value used for the node name for all requests the agent makes to the master. WARNING: This setting is mutually exclusive with node_name_fact. Changing this setting also requires changes to the default auth.conf configuration on the Puppet Master. Please see http://links.puppetlabs.com/node_name_value for more information." }, :node_name_fact => { :default => "", :desc => "The fact name used to determine the node name used for all requests the agent makes to the master. WARNING: This setting is mutually exclusive with node_name_value. Changing this setting also requires changes to the default auth.conf configuration on the Puppet Master. Please see http://links.puppetlabs.com/node_name_fact for more information.", :hook => proc do |value| if !value.empty? and Puppet[:node_name_value] != Puppet[:certname] raise "Cannot specify both the node_name_value and node_name_fact settings" end end }, :localconfig => { :default => "$statedir/localconfig", :type => :file, :owner => "root", :mode => 0660, :desc => "Where puppet agent caches the local configuration. An extension indicating the cache format is added automatically."}, :statefile => { :default => "$statedir/state.yaml", :type => :file, :mode => 0660, :desc => "Where puppet agent and puppet master store state associated with the running configuration. In the case of puppet master, this file reflects the state discovered through interacting with clients." }, :clientyamldir => { :default => "$vardir/client_yaml", :type => :directory, :mode => "750", :desc => "The directory in which client-side YAML data is stored." }, :client_datadir => { :default => "$vardir/client_data", :type => :directory, :mode => "750", :desc => "The directory in which serialized data is stored on the client." }, :classfile => { :default => "$statedir/classes.txt", :type => :file, :owner => "root", :mode => 0644, :desc => "The file in which puppet agent stores a list of the classes associated with the retrieved configuration. Can be loaded in the separate `puppet` executable using the `--loadclasses` option."}, :resourcefile => { :default => "$statedir/resources.txt", :type => :file, :owner => "root", :mode => 0644, :desc => "The file in which puppet agent stores a list of the resources associated with the retrieved configuration." }, :puppetdlog => { :default => "$logdir/puppetd.log", :type => :file, :owner => "root", :mode => 0640, :desc => "The log file for puppet agent. This is generally not used." }, :server => { :default => "puppet", :desc => "The server to which the puppet agent should connect" }, :use_srv_records => { :default => true, :type => :boolean, :desc => "Whether the server will search for SRV records in DNS for the current domain.", }, :srv_domain => { :default => "#{domain}", :desc => "The domain which will be queried to find the SRV records of servers to use.", }, :ignoreschedules => { :default => false, :type => :boolean, :desc => "Boolean; whether puppet agent should ignore schedules. This is useful for initial puppet agent runs.", }, :puppetport => { :default => 8139, :desc => "Which port puppet agent listens on.", }, :noop => { :default => false, :type => :boolean, :desc => "Whether puppet agent should be run in noop mode.", }, :runinterval => { :default => 1800, # 30 minutes :desc => "How often puppet agent applies the client configuration; in seconds. Note that a runinterval of 0 means \"run continuously\" rather than \"never run.\" If you want puppet agent to never run, you should start it with the `--no-client` option.", }, :listen => { :default => false, :type => :boolean, :desc => "Whether puppet agent should listen for connections. If this is true, then puppet agent will accept incoming REST API requests, subject to the default ACLs and the ACLs set in the `rest_authconfig` file. Puppet agent can respond usefully to requests on the `run`, `facts`, `certificate`, and `resource` endpoints.", }, :ca_server => { :default => "$server", :desc => "The server to use for certificate authority requests. It's a separate server because it cannot and does not need to horizontally scale.", }, :ca_port => { :default => "$masterport", :desc => "The port to use for the certificate authority.", }, :catalog_format => { :default => "", :desc => "(Deprecated for 'preferred_serialization_format') What format to use to dump the catalog. Only supports 'marshal' and 'yaml'. Only matters on the client, since it asks the server for a specific format.", :hook => proc { |value| if value Puppet.deprecation_warning "Setting 'catalog_format' is deprecated; use 'preferred_serialization_format' instead." Puppet.settings[:preferred_serialization_format] = value end } }, :preferred_serialization_format => { :default => "pson", :desc => "The preferred means of serializing ruby instances for passing over the wire. This won't guarantee that all instances will be serialized using this method, since not all classes can be guaranteed to support this format, but it will be used for all classes that support it.", }, :puppetdlockfile => { :default => "$statedir/puppetdlock", :type => :file, :desc => "A lock file to temporarily stop puppet agent from doing anything.", }, :usecacheonfailure => { :default => true, :type => :boolean, :desc => "Whether to use the cached configuration when the remote configuration will not compile. This option is useful for testing new configurations, where you want to fix the broken configuration rather than reverting to a known-good one.", }, :use_cached_catalog => { :default => false, :type => :boolean, :desc => "Whether to only use the cached catalog rather than compiling a new catalog on every run. Puppet can be run with this enabled by default and then selectively disabled when a recompile is desired.", }, :ignorecache => { :default => false, :type => :boolean, :desc => "Ignore cache and always recompile the configuration. This is useful for testing new configurations, where the local cache may in fact be stale even if the timestamps are up to date - if the facts change or if the server changes.", }, :downcasefacts => { :default => false, :type => :boolean, :desc => "Whether facts should be made all lowercase when sent to the server.", }, :dynamicfacts => { :default => "memorysize,memoryfree,swapsize,swapfree", :desc => "Facts that are dynamic; these facts will be ignored when deciding whether changed facts should result in a recompile. Multiple facts should be comma-separated.", }, :splaylimit => { :default => "$runinterval", :desc => "The maximum time to delay before runs. Defaults to being the same as the run interval.", }, :splay => { :default => false, :type => :boolean, :desc => "Whether to sleep for a pseudo-random (but consistent) amount of time before a run.", }, :clientbucketdir => { :default => "$vardir/clientbucket", :type => :directory, :mode => 0750, :desc => "Where FileBucket files are stored locally." }, :configtimeout => { :default => 120, :desc => "How long the client should wait for the configuration to be retrieved before considering it a failure. This can help reduce flapping if too many clients contact the server at one time.", }, :reportserver => { :default => "$server", :call_on_define => false, :desc => "(Deprecated for 'report_server') The server to which to send transaction reports.", :hook => proc do |value| Puppet.settings[:report_server] = value if value end }, :report_server => { :default => "$server", :desc => "The server to send transaction reports to.", }, :report_port => { :default => "$masterport", :desc => "The port to communicate with the report_server.", }, :inventory_server => { :default => "$server", :desc => "The server to send facts to.", }, :inventory_port => { :default => "$masterport", :desc => "The port to communicate with the inventory_server.", }, :report => { :default => true, :type => :boolean, :desc => "Whether to send reports after every transaction.", }, :lastrunfile => { :default => "$statedir/last_run_summary.yaml", :type => :file, :mode => 0644, :desc => "Where puppet agent stores the last run report summary in yaml format." }, :lastrunreport => { :default => "$statedir/last_run_report.yaml", :type => :file, :mode => 0644, :desc => "Where puppet agent stores the last run report in yaml format." }, :graph => { :default => false, :type => :boolean, :desc => "Whether to create dot graph files for the different configuration graphs. These dot files can be interpreted by tools like OmniGraffle or dot (which is part of ImageMagick).", }, :graphdir => { :default => "$statedir/graphs", :type => :directory, :desc => "Where to store dot-outputted graphs.", }, :http_compression => { :default => false, :type => :boolean, :desc => "Allow http compression in REST communication with the master. This setting might improve performance for agent -> master communications over slow WANs. Your puppet master needs to support compression (usually by activating some settings in a reverse-proxy in front of the puppet master, which rules out webrick). It is harmless to activate this settings if your master doesn't support compression, but if it supports it, this setting might reduce performance on high-speed LANs.", }, :waitforcert => { :default => 120, # 2 minutes :desc => "The time interval, specified in seconds, 'puppet agent' should connect to the server and ask it to sign a certificate request. This is useful for the initial setup of a puppet client. You can turn off waiting for certificates by specifying a time of 0.", } ) define_settings(:inspect, :archive_files => { :type => :boolean, :default => false, :desc => "During an inspect run, whether to archive files whose contents are audited to a file bucket.", }, :archive_file_server => { :default => "$server", :desc => "During an inspect run, the file bucket server to archive files to if archive_files is set.", } ) # Plugin information. define_settings( :main, :plugindest => { :type => :directory, :default => "$libdir", :desc => "Where Puppet should store plugins that it pulls down from the central server.", }, :pluginsource => { :default => "puppet://$server/plugins", :desc => "From where to retrieve plugins. The standard Puppet `file` type is used for retrieval, so anything that is a valid file source can be used here.", }, :pluginsync => { :default => true, :type => :boolean, :desc => "Whether plugins should be synced with the central server.", }, :pluginsignore => { :default => ".svn CVS .git", :desc => "What files to ignore when pulling down plugins.", } ) # Central fact information. define_settings( :main, :factpath => { :type => :path, :default => "$vardir/lib/facter#{File::PATH_SEPARATOR}$vardir/facts", :desc => "Where Puppet should look for facts. Multiple directories should be separated by the system path separator character. (The POSIX path separator is ':', and the Windows path separator is ';'.)", #:call_on_define => true, # Call our hook with the default value, so we always get the value added to facter. :hook => proc { |value| Facter.search(value) if Facter.respond_to?(:search) }} ) define_settings( :tagmail, :tagmap => { :default => "$confdir/tagmail.conf", :desc => "The mapping between reporting tags and email addresses.", }, :sendmail => { :default => which('sendmail') || '', :desc => "Where to find the sendmail binary with which to send email.", }, :reportfrom => { :default => "report@" + [Facter["hostname"].value,Facter["domain"].value].join("."), :desc => "The 'from' email address for the reports.", }, :smtpserver => { :default => "none", :desc => "The server through which to send email reports.", } ) define_settings( :rails, :dblocation => { :default => "$statedir/clientconfigs.sqlite3", :type => :file, :mode => 0660, :owner => "service", :group => "service", :desc => "The database cache for client configurations. Used for querying within the language." }, :dbadapter => { :default => "sqlite3", :desc => "The type of database to use.", }, :dbmigrate => { :default => false, :type => :boolean, :desc => "Whether to automatically migrate the database.", }, :dbname => { :default => "puppet", :desc => "The name of the database to use.", }, :dbserver => { :default => "localhost", :desc => "The database server for caching. Only used when networked databases are used.", }, :dbport => { :default => "", :desc => "The database password for caching. Only used when networked databases are used.", }, :dbuser => { :default => "puppet", :desc => "The database user for caching. Only used when networked databases are used.", }, :dbpassword => { :default => "puppet", :desc => "The database password for caching. Only used when networked databases are used.", }, :dbconnections => { :default => '', :desc => "The number of database connections for networked databases. Will be ignored unless the value is a positive integer.", }, :dbsocket => { :default => "", :desc => "The database socket location. Only used when networked databases are used. Will be ignored if the value is an empty string.", }, :railslog => { :default => "$logdir/rails.log", :type => :file, :mode => 0600, :owner => "service", :group => "service", :desc => "Where Rails-specific logs are sent" }, :rails_loglevel => { :default => "info", :desc => "The log level for Rails connections. The value must be a valid log level within Rails. Production environments normally use `info` and other environments normally use `debug`.", } ) define_settings( :couchdb, :couchdb_url => { :default => "http://127.0.0.1:5984/puppet", :desc => "The url where the puppet couchdb database will be created", } ) define_settings( :transaction, :tags => { :default => "", :desc => "Tags to use to find resources. If this is set, then only resources tagged with the specified tags will be applied. Values must be comma-separated.", }, :evaltrace => { :default => false, :type => :boolean, :desc => "Whether each resource should log when it is being evaluated. This allows you to interactively see exactly what is being done.", }, :summarize => { :default => false, :type => :boolean, :desc => "Whether to print a transaction summary.", } ) define_settings( :main, :external_nodes => { :default => "none", :desc => "An external command that can produce node information. The command's output must be a YAML dump of a hash, and that hash must have a `classes` key and/or a `parameters` key, where `classes` is an array or hash and `parameters` is a hash. For unknown nodes, the command should exit with a non-zero exit code. This command makes it straightforward to store your node mapping information in other data sources like databases.", } ) define_settings( :ldap, :ldapnodes => { :default => false, :type => :boolean, :desc => "Whether to search for node configurations in LDAP. See http://projects.puppetlabs.com/projects/puppet/wiki/LDAP_Nodes for more information.", }, :ldapssl => { :default => false, :type => :boolean, :desc => "Whether SSL should be used when searching for nodes. Defaults to false because SSL usually requires certificates to be set up on the client side.", }, :ldaptls => { :default => false, :type => :boolean, :desc => "Whether TLS should be used when searching for nodes. Defaults to false because TLS usually requires certificates to be set up on the client side.", }, :ldapserver => { :default => "ldap", :desc => "The LDAP server. Only used if `ldapnodes` is enabled.", }, :ldapport => { :default => 389, :desc => "The LDAP port. Only used if `ldapnodes` is enabled.", }, :ldapstring => { :default => "(&(objectclass=puppetClient)(cn=%s))", :desc => "The search string used to find an LDAP node.", }, :ldapclassattrs => { :default => "puppetclass", :desc => "The LDAP attributes to use to define Puppet classes. Values should be comma-separated.", }, :ldapstackedattrs => { :default => "puppetvar", :desc => "The LDAP attributes that should be stacked to arrays by adding the values in all hierarchy elements of the tree. Values should be comma-separated.", }, :ldapattrs => { :default => "all", :desc => "The LDAP attributes to include when querying LDAP for nodes. All returned attributes are set as variables in the top-level scope. Multiple values should be comma-separated. The value 'all' returns all attributes.", }, :ldapparentattr => { :default => "parentnode", :desc => "The attribute to use to define the parent node.", }, :ldapuser => { :default => "", :desc => "The user to use to connect to LDAP. Must be specified as a full DN.", }, :ldappassword => { :default => "", :desc => "The password to use to connect to LDAP.", }, :ldapbase => { :default => "", :desc => "The search base for LDAP searches. It's impossible to provide a meaningful default here, although the LDAP libraries might have one already set. Generally, it should be the 'ou=Hosts' branch under your main directory.", } ) define_settings(:master, :storeconfigs => { :default => false, :type => :boolean, :desc => "Whether to store each client's configuration, including catalogs, facts, and related data. This also enables the import and export of resources in the Puppet language - a mechanism for exchange resources between nodes. By default this uses ActiveRecord and an SQL database to store and query the data; this, in turn, will depend on Rails being available. You can adjust the backend using the storeconfigs_backend setting.", # Call our hook with the default value, so we always get the libdir set. #:call_on_define => true, :hook => proc do |value| require 'puppet/node' require 'puppet/node/facts' if value Puppet.settings[:async_storeconfigs] or Puppet::Resource::Catalog.indirection.cache_class = :store_configs Puppet::Node::Facts.indirection.cache_class = :store_configs Puppet::Node.indirection.cache_class = :store_configs Puppet::Resource.indirection.terminus_class = :store_configs end end }, :storeconfigs_backend => { :default => "active_record", :desc => "Configure the backend terminus used for StoreConfigs. By default, this uses the ActiveRecord store, which directly talks to the database from within the Puppet Master process." } ) # This doesn't actually work right now. define_settings( :parser, :lexical => { :default => false, :type => :boolean, :desc => "Whether to use lexical scoping (vs. dynamic).", }, :templatedir => { :default => "$vardir/templates", :type => :directory, :desc => "Where Puppet looks for template files. Can be a list of colon-separated directories.", } ) define_settings( :puppetdoc, :document_all => { :default => false, :type => :boolean, :desc => "Document all resources", } ) end diff --git a/lib/puppet/network/server.rb b/lib/puppet/network/server.rb index f2ed829e2..275eaf726 100644 --- a/lib/puppet/network/server.rb +++ b/lib/puppet/network/server.rb @@ -1,137 +1,137 @@ require 'puppet/network/http' require 'puppet/util/pidlock' class Puppet::Network::Server attr_reader :server_type, :address, :port # TODO: does anything actually call this? It seems like it's a duplicate of the code in Puppet::Daemon, but that # it's not actually called anywhere. # Put the daemon into the background. def daemonize if pid = fork Process.detach(pid) exit(0) end # Get rid of console logging Puppet::Util::Log.close(:console) Process.setsid Dir.chdir("/") end def close_streams() Puppet::Daemon.close_streams() end # Create a pidfile for our daemon, so we can be stopped and others # don't try to start. def create_pidfile - Puppet::Util.synchronize_on(Puppet[:name],Sync::EX) do + Puppet::Util.synchronize_on(Puppet.run_mode.name,Sync::EX) do raise "Could not create PID file: #{pidfile}" unless Puppet::Util::Pidlock.new(pidfile).lock end end # Remove the pid file for our daemon. def remove_pidfile - Puppet::Util.synchronize_on(Puppet[:name],Sync::EX) do + Puppet::Util.synchronize_on(Puppet.run_mode.name,Sync::EX) do Puppet::Util::Pidlock.new(pidfile).unlock end end # Provide the path to our pidfile. def pidfile Puppet[:pidfile] end def initialize(args = {}) valid_args = [:handlers, :port] bad_args = args.keys.find_all { |p| ! valid_args.include?(p) }.collect { |p| p.to_s }.join(",") raise ArgumentError, "Invalid argument(s) #{bad_args}" unless bad_args == "" @server_type = Puppet[:servertype] or raise "No servertype configuration found." # e.g., WEBrick, Mongrel, etc. http_server_class || raise(ArgumentError, "Could not determine HTTP Server class for server type [#{@server_type}]") @port = args[:port] || Puppet[:masterport] || raise(ArgumentError, "Must specify :port or configure Puppet :masterport") @address = determine_bind_address @listening = false @routes = {} self.register(args[:handlers]) if args[:handlers] # Make sure we have all of the directories we need to function. #Puppet.settings.use(:main, :ssl, Puppet[:name]) Puppet.settings.use(:main, :ssl, :application) end # Register handlers for REST networking, based on the Indirector. def register(*indirections) raise ArgumentError, "Indirection names are required." if indirections.empty? indirections.flatten.each do |name| Puppet::Indirector::Indirection.model(name) || raise(ArgumentError, "Cannot locate indirection '#{name}'.") @routes[name.to_sym] = true end end # Unregister Indirector handlers. def unregister(*indirections) raise "Cannot unregister indirections while server is listening." if listening? indirections = @routes.keys if indirections.empty? indirections.flatten.each do |i| raise(ArgumentError, "Indirection [#{i}] is unknown.") unless @routes[i.to_sym] end indirections.flatten.each do |i| @routes.delete(i.to_sym) end end def listening? @listening end def listen raise "Cannot listen -- already listening." if listening? @listening = true http_server.listen(:address => address, :port => port, :handlers => @routes.keys) end def unlisten raise "Cannot unlisten -- not currently listening." unless listening? http_server.unlisten @listening = false end def http_server_class http_server_class_by_type(@server_type) end def start create_pidfile close_streams if Puppet[:daemonize] listen end def stop unlisten remove_pidfile end private def http_server @http_server ||= http_server_class.new end def http_server_class_by_type(kind) Puppet::Network::HTTP.server_class_by_type(kind) end def determine_bind_address tmp = Puppet[:bindaddress] return tmp if tmp != "" server_type.to_s == "webrick" ? "0.0.0.0" : "127.0.0.1" end end diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index e0d49fbbe..cf401c97a 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -1,490 +1,490 @@ # the class that actually walks our resource/property tree, collects the changes, # and performs them require 'puppet' require 'puppet/util/tagging' require 'puppet/application' require 'digest/sha1' class Puppet::Transaction require 'puppet/transaction/event' require 'puppet/transaction/event_manager' require 'puppet/transaction/resource_harness' require 'puppet/resource/status' attr_accessor :component, :catalog, :ignoreschedules, :for_network_device attr_accessor :configurator # The report, once generated. attr_reader :report # Routes and stores any events and subscriptions. attr_reader :event_manager # Handles most of the actual interacting with resources attr_reader :resource_harness include Puppet::Util include Puppet::Util::Tagging # Wraps application run state check to flag need to interrupt processing def stop_processing? Puppet::Application.stop_requested? end # Add some additional times for reporting def add_times(hash) hash.each do |name, num| report.add_times(name, num) end end # Are there any failed resources in this transaction? def any_failed? report.resource_statuses.values.detect { |status| status.failed? } end # Apply all changes for a resource def apply(resource, ancestor = nil) status = resource_harness.evaluate(resource) add_resource_status(status) event_manager.queue_events(ancestor || resource, status.events) unless status.failed? rescue => detail resource.err "Could not evaluate: #{detail}" end # Find all of the changed resources. def changed? report.resource_statuses.values.find_all { |status| status.changed }.collect { |status| catalog.resource(status.resource) } end # Find all of the applied resources (including failed attempts). def applied_resources report.resource_statuses.values.collect { |status| catalog.resource(status.resource) } end # Copy an important relationships from the parent to the newly-generated # child resource. def add_conditional_directed_dependency(parent, child, label=nil) relationship_graph.add_vertex(child) edge = parent.depthfirst? ? [child, parent] : [parent, child] if relationship_graph.edge?(*edge.reverse) parent.debug "Skipping automatic relationship to #{child}" else relationship_graph.add_edge(edge[0],edge[1],label) end end # Evaluate a single resource. def eval_resource(resource, ancestor = nil) if skip?(resource) resource_status(resource).skipped = true else resource_status(resource).scheduled = true apply(resource, ancestor) end # Check to see if there are any events queued for this resource event_manager.process_events(resource) end # This method does all the actual work of running a transaction. It # collects all of the changes, executes them, and responds to any # necessary events. def evaluate add_dynamically_generated_resources Puppet.info "Applying configuration version '#{catalog.version}'" if catalog.version relationship_graph.traverse do |resource| if resource.is_a?(Puppet::Type::Component) Puppet.warning "Somehow left a component in the relationship graph" else resource.info "Starting to evaluate the resource" if Puppet[:evaltrace] and @catalog.host_config? seconds = thinmark { eval_resource(resource) } resource.info "Evaluated in %0.2f seconds" % seconds if Puppet[:evaltrace] and @catalog.host_config? end end Puppet.debug "Finishing transaction #{object_id}" end def events event_manager.events end def failed?(resource) s = resource_status(resource) and s.failed? end # Does this resource have any failed dependencies? def failed_dependencies?(resource) # First make sure there are no failed dependencies. To do this, # we check for failures in any of the vertexes above us. It's not # enough to check the immediate dependencies, which is why we use # a tree from the reversed graph. found_failed = false # When we introduced the :whit into the graph, to reduce the combinatorial # explosion of edges, we also ended up reporting failures for containers # like class and stage. This is undesirable; while just skipping the # output isn't perfect, it is RC-safe. --daniel 2011-06-07 suppress_report = (resource.class == Puppet::Type.type(:whit)) relationship_graph.dependencies(resource).each do |dep| next unless failed?(dep) found_failed = true # See above. --daniel 2011-06-06 unless suppress_report then resource.notice "Dependency #{dep} has failures: #{resource_status(dep).failed}" end end found_failed end def eval_generate(resource) return false unless resource.respond_to?(:eval_generate) raise Puppet::DevError,"Depthfirst resources are not supported by eval_generate" if resource.depthfirst? begin made = resource.eval_generate.uniq return false if made.empty? made = made.inject({}) {|a,v| a.merge(v.name => v) } rescue => detail resource.log_exception(detail, "Failed to generate additional resources using 'eval_generate: #{detail}") return false end made.values.each do |res| begin res.tag(*resource.tags) @catalog.add_resource(res) res.finish rescue Puppet::Resource::Catalog::DuplicateResourceError res.info "Duplicate generated resource; skipping" end end sentinel = Puppet::Type.type(:whit).new(:name => "completed_#{resource.title}", :catalog => resource.catalog) # The completed whit is now the thing that represents the resource is done relationship_graph.adjacent(resource,:direction => :out,:type => :edges).each { |e| # But children run as part of the resource, not after it next if made[e.target.name] add_conditional_directed_dependency(sentinel, e.target, e.label) relationship_graph.remove_edge! e } default_label = Puppet::Resource::Catalog::Default_label made.values.each do |res| # Depend on the nearest ancestor we generated, falling back to the # resource if we have none parent_name = res.ancestors.find { |a| made[a] and made[a] != res } parent = made[parent_name] || resource add_conditional_directed_dependency(parent, res) # This resource isn't 'completed' until each child has run add_conditional_directed_dependency(res, sentinel, default_label) end # This edge allows the resource's events to propagate, though it isn't # strictly necessary for ordering purposes add_conditional_directed_dependency(resource, sentinel, default_label) true end # A general method for recursively generating new resources from a # resource. def generate_additional_resources(resource) return unless resource.respond_to?(:generate) begin made = resource.generate rescue => detail resource.log_exception(detail, "Failed to generate additional resources using 'generate': #{detail}") end return unless made made = [made] unless made.is_a?(Array) made.uniq.each do |res| begin res.tag(*resource.tags) @catalog.add_resource(res) res.finish add_conditional_directed_dependency(resource, res) generate_additional_resources(res) rescue Puppet::Resource::Catalog::DuplicateResourceError res.info "Duplicate generated resource; skipping" end end end def add_dynamically_generated_resources @catalog.vertices.each { |resource| generate_additional_resources(resource) } end # Should we ignore tags? def ignore_tags? - ! (@catalog.host_config? or Puppet[:name] == "puppet") + ! @catalog.host_config? end # this should only be called by a Puppet::Type::Component resource now # and it should only receive an array def initialize(catalog, report = nil) @catalog = catalog @report = report || Puppet::Transaction::Report.new("apply", catalog.version, catalog.environment) @event_manager = Puppet::Transaction::EventManager.new(self) @resource_harness = Puppet::Transaction::ResourceHarness.new(self) @prefetched_providers = Hash.new { |h,k| h[k] = {} } end def resources_by_provider(type_name, provider_name) unless @resources_by_provider @resources_by_provider = Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = {} } } @catalog.vertices.each do |resource| if resource.class.attrclass(:provider) prov = resource.provider && resource.provider.class.name @resources_by_provider[resource.type][prov][resource.name] = resource end end end @resources_by_provider[type_name][provider_name] || {} end def prefetch_if_necessary(resource) provider_class = resource.provider.class return unless provider_class.respond_to?(:prefetch) and !prefetched_providers[resource.type][provider_class.name] resources = resources_by_provider(resource.type, provider_class.name) if provider_class == resource.class.defaultprovider providerless_resources = resources_by_provider(resource.type, nil) providerless_resources.values.each {|res| res.provider = provider_class.name} resources.merge! providerless_resources end prefetch(provider_class, resources) end attr_reader :prefetched_providers # Prefetch any providers that support it, yo. We don't support prefetching # types, just providers. def prefetch(provider_class, resources) type_name = provider_class.resource_type.name return if @prefetched_providers[type_name][provider_class.name] Puppet.debug "Prefetching #{provider_class.name} resources for #{type_name}" begin provider_class.prefetch(resources) rescue => detail Puppet.log_exception(detail, "Could not prefetch #{type_name} provider '#{provider_class.name}': #{detail}") end @prefetched_providers[type_name][provider_class.name] = true end # We want to monitor changes in the relationship graph of our # catalog but this is complicated by the fact that the catalog # both is_a graph and has_a graph, by the fact that changes to # the structure of the object can have adverse serialization # effects, by threading issues, by order-of-initialization issues, # etc. # # Since the proper lifetime/scope of the monitoring is a transaction # and the transaction is already commiting a mild law-of-demeter # transgression, we cut the Gordian knot here by simply wrapping the # transaction's view of the resource graph to capture and maintain # the information we need. Nothing outside the transaction needs # this information, and nothing outside the transaction can see it # except via the Transaction#relationship_graph class Relationship_graph_wrapper require 'puppet/rb_tree_map' attr_reader :real_graph,:transaction,:ready,:generated,:done,:blockers,:unguessable_deterministic_key def initialize(real_graph,transaction) @real_graph = real_graph @transaction = transaction @ready = Puppet::RbTreeMap.new @generated = {} @done = {} @blockers = {} @unguessable_deterministic_key = Hash.new { |h,k| h[k] = Digest::SHA1.hexdigest("NaCl, MgSO4 (salts) and then #{k.ref}") } @providerless_types = [] end def method_missing(*args,&block) real_graph.send(*args,&block) end def add_vertex(v) real_graph.add_vertex(v) end def add_edge(f,t,label=nil) key = unguessable_deterministic_key[t] ready.delete(key) real_graph.add_edge(f,t,label) end # Enqueue the initial set of resources, those with no dependencies. def enqueue_roots vertices.each do |v| blockers[v] = direct_dependencies_of(v).length enqueue(v) if blockers[v] == 0 end end # Decrement the blocker count for the resource by 1. If the number of # blockers is unknown, count them and THEN decrement by 1. def unblock(resource) blockers[resource] ||= direct_dependencies_of(resource).select { |r2| !done[r2] }.length if blockers[resource] > 0 blockers[resource] -= 1 else resource.warning "appears to have a negative number of dependencies" end blockers[resource] <= 0 end def enqueue(*resources) resources.each do |resource| key = unguessable_deterministic_key[resource] ready[key] = resource end end def finish(resource) direct_dependents_of(resource).each do |v| enqueue(v) if unblock(v) end done[resource] = true end def next_resource ready.delete_min end def traverse(&block) real_graph.report_cycles_in_graph enqueue_roots deferred_resources = [] while (resource = next_resource) && !transaction.stop_processing? if resource.suitable? made_progress = true transaction.prefetch_if_necessary(resource) # If we generated resources, we don't know what they are now # blocking, so we opt to recompute it, rather than try to track every # change that would affect the number. blockers.clear if transaction.eval_generate(resource) yield resource finish(resource) else deferred_resources << resource end if ready.empty? and deferred_resources.any? if made_progress enqueue(*deferred_resources) else fail_unsuitable_resources(deferred_resources) end made_progress = false deferred_resources = [] end end # Just once per type. No need to punish the user. @providerless_types.uniq.each do |type| Puppet.err "Could not find a suitable provider for #{type}" end end def fail_unsuitable_resources(resources) resources.each do |resource| # We don't automatically assign unsuitable providers, so if there # is one, it must have been selected by the user. if resource.provider resource.err "Provider #{resource.provider.class.name} is not functional on this host" else @providerless_types << resource.type end transaction.resource_status(resource).failed = true finish(resource) end end end def relationship_graph @relationship_graph ||= Relationship_graph_wrapper.new(catalog.relationship_graph,self) end def add_resource_status(status) report.add_resource_status status end def resource_status(resource) report.resource_statuses[resource.to_s] || add_resource_status(Puppet::Resource::Status.new(resource)) end # Is the resource currently scheduled? def scheduled?(resource) self.ignoreschedules or resource_harness.scheduled?(resource_status(resource), resource) end # Should this resource be skipped? def skip?(resource) if missing_tags?(resource) resource.debug "Not tagged with #{tags.join(", ")}" elsif ! scheduled?(resource) resource.debug "Not scheduled" elsif failed_dependencies?(resource) # When we introduced the :whit into the graph, to reduce the combinatorial # explosion of edges, we also ended up reporting failures for containers # like class and stage. This is undesirable; while just skipping the # output isn't perfect, it is RC-safe. --daniel 2011-06-07 unless resource.class == Puppet::Type.type(:whit) then resource.warning "Skipping because of failed dependencies" end elsif resource.virtual? resource.debug "Skipping because virtual" elsif resource.appliable_to_device? ^ for_network_device resource.debug "Skipping #{resource.appliable_to_device? ? 'device' : 'host'} resources because running on a #{for_network_device ? 'device' : 'host'}" else return false end true end # The tags we should be checking. def tags self.tags = Puppet[:tags] unless defined?(@tags) super end def handle_qualified_tags( qualified ) # The default behavior of Puppet::Util::Tagging is # to split qualified tags into parts. That would cause # qualified tags to match too broadly here. return end # Is this resource tagged appropriately? def missing_tags?(resource) return false if ignore_tags? return false if tags.empty? not resource.tagged?(*tags) end end require 'puppet/transaction/report' diff --git a/lib/puppet/util/log/destinations.rb b/lib/puppet/util/log/destinations.rb index 777cbf501..4dc1da4a7 100644 --- a/lib/puppet/util/log/destinations.rb +++ b/lib/puppet/util/log/destinations.rb @@ -1,271 +1,270 @@ Puppet::Util::Log.newdesttype :syslog do def self.suitable?(obj) Puppet.features.syslog? end def close Syslog.close end def initialize Syslog.close if Syslog.opened? - name = Puppet[:name] - name = "puppet-#{name}" unless name =~ /puppet/ + name = "puppet-#{Puppet.run_mode.name}" options = Syslog::LOG_PID | Syslog::LOG_NDELAY # XXX This should really be configurable. str = Puppet[:syslogfacility] begin facility = Syslog.const_get("LOG_#{str.upcase}") rescue NameError raise Puppet::Error, "Invalid syslog facility #{str}" end @syslog = Syslog.open(name, options, facility) end def handle(msg) # XXX Syslog currently has a bug that makes it so you # cannot log a message with a '%' in it. So, we get rid # of them. if msg.source == "Puppet" msg.to_s.split("\n").each do |line| @syslog.send(msg.level, line.gsub("%", '%%')) end else msg.to_s.split("\n").each do |line| @syslog.send(msg.level, "(%s) %s" % [msg.source.to_s.gsub("%", ""), line.gsub("%", '%%') ] ) end end end end Puppet::Util::Log.newdesttype :file do require 'fileutils' def self.match?(obj) Puppet::Util.absolute_path?(obj) end def close if defined?(@file) @file.close @file = nil end end def flush @file.flush if defined?(@file) end attr_accessor :autoflush def initialize(path) @name = path # first make sure the directory exists # We can't just use 'Config.use' here, because they've # specified a "special" destination. unless FileTest.exist?(File.dirname(path)) FileUtils.mkdir_p(File.dirname(path), :mode => 0755) Puppet.info "Creating log directory #{File.dirname(path)}" end # create the log file, if it doesn't already exist file = File.open(path, File::WRONLY|File::CREAT|File::APPEND) @file = file @autoflush = Puppet[:autoflush] end def handle(msg) @file.puts("#{msg.time} #{msg.source} (#{msg.level}): #{msg}") @file.flush if @autoflush end end Puppet::Util::Log.newdesttype :console do require 'puppet/util/colors' include Puppet::Util::Colors def initialize # Flush output immediately. $stdout.sync = true end def handle(msg) if msg.source == "Puppet" puts colorize(msg.level, "#{msg.level}: #{msg}") else puts colorize(msg.level, "#{msg.level}: #{msg.source}: #{msg}") end end end Puppet::Util::Log.newdesttype :telly_prototype_console do require 'puppet/util/colors' include Puppet::Util::Colors def initialize # Flush output immediately. $stderr.sync = true $stdout.sync = true end def handle(msg) error_levels = { :warning => 'Warning', :err => 'Error', :alert => 'Alert', :emerg => 'Emergency', :crit => 'Critical' } str = msg.respond_to?(:multiline) ? msg.multiline : msg.to_s case msg.level when *error_levels.keys $stderr.puts colorize(:hred, "#{error_levels[msg.level]}: #{str}") when :info $stdout.puts "#{colorize(:green, 'Info')}: #{str}" when :debug $stdout.puts "#{colorize(:cyan, 'Debug')}: #{str}" else $stdout.puts str end end end Puppet::Util::Log.newdesttype :host do def initialize(host) Puppet.info "Treating #{host} as a hostname" args = {} if host =~ /:(\d+)/ args[:Port] = $1 args[:Server] = host.sub(/:\d+/, '') else args[:Server] = host end @name = host @driver = Puppet::Network::Client::LogClient.new(args) end def handle(msg) unless msg.is_a?(String) or msg.remote @hostname ||= Facter["hostname"].value unless defined?(@domain) @domain = Facter["domain"].value @hostname += ".#{@domain}" if @domain end if Puppet::Util.absolute_path?(msg.source) msg.source = @hostname + ":#{msg.source}" elsif msg.source == "Puppet" msg.source = @hostname + " #{msg.source}" else msg.source = @hostname + " #{msg.source}" end begin #puts "would have sent #{msg}" #puts "would have sent %s" % # CGI.escape(YAML.dump(msg)) begin tmp = CGI.escape(YAML.dump(msg)) rescue => detail puts "Could not dump: #{detail}" return end # Add the hostname to the source @driver.addlog(tmp) rescue => detail Puppet.log_exception(detail) Puppet::Util::Log.close(self) end end end end # Log to a transaction report. Puppet::Util::Log.newdesttype :report do attr_reader :report match "Puppet::Transaction::Report" def initialize(report) @report = report end def handle(msg) @report << msg end end # Log to an array, just for testing. module Puppet::Test class LogCollector def initialize(logs) @logs = logs end def <<(value) @logs << value end end end Puppet::Util::Log.newdesttype :array do match "Puppet::Test::LogCollector" def initialize(messages) @messages = messages end def handle(msg) @messages << msg end end Puppet::Util::Log.newdesttype :eventlog do def self.suitable?(obj) Puppet.features.eventlog? end def initialize @eventlog = Win32::EventLog.open("Application") end def to_native(level) case level when :debug,:info,:notice [Win32::EventLog::INFO, 0x01] when :warning [Win32::EventLog::WARN, 0x02] when :err,:alert,:emerg,:crit [Win32::EventLog::ERROR, 0x03] end end def handle(msg) native_type, native_id = to_native(msg.level) @eventlog.report_event( :source => "Puppet", :event_type => native_type, :event_id => native_id, :data => (msg.source and msg.source != 'Puppet' ? "#{msg.source}: " : '') + msg.to_s ) end def close if @eventlog @eventlog.close @eventlog = nil end end end diff --git a/lib/puppet/util/settings.rb b/lib/puppet/util/settings.rb index d6b54a32e..5558cf863 100644 --- a/lib/puppet/util/settings.rb +++ b/lib/puppet/util/settings.rb @@ -1,1149 +1,1151 @@ require 'puppet' require 'sync' require 'getoptlong' require 'puppet/util/loadedfile' class Puppet::SettingsError < Puppet::Error end # The class for handling configuration files. class Puppet::Util::Settings include Enumerable require 'puppet/util/settings/string_setting' require 'puppet/util/settings/file_setting' require 'puppet/util/settings/directory_setting' require 'puppet/util/settings/boolean_setting' attr_accessor :files attr_reader :timer - ReadOnly = [:run_mode, :name] + ReadOnly = [:run_mode] # These are the settings that every app is required to specify; there are reasonable defaults defined in application.rb. - REQUIRED_APP_SETTINGS = [:name, :run_mode, :logdir, :confdir, :vardir] + REQUIRED_APP_SETTINGS = [:run_mode, :logdir, :confdir, :vardir] def self.default_global_config_dir() Puppet.features.microsoft_windows? ? File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "etc") : "/etc/puppet" end def self.default_user_config_dir() "~/.puppet" end def self.default_global_var_dir() Puppet.features.microsoft_windows? ? File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "var") : "/var/lib/puppet" end def self.default_user_var_dir() "~/.puppet/var" end def self.default_config_file_name() "puppet.conf" end # Retrieve a config value def [](param) value(param) end # Set a config value. This doesn't set the defaults, it sets the value itself. def []=(param, value) set_value(param, value, :memory) end # Generate the list of valid arguments, in a format that GetoptLong can # understand, and add them to the passed option list. def addargs(options) # Add all of the config parameters as valid options. self.each { |name, setting| setting.getopt_args.each { |args| options << args } } options end # Generate the list of valid arguments, in a format that OptionParser can # understand, and add them to the passed option list. def optparse_addargs(options) # Add all of the config parameters as valid options. self.each { |name, setting| options << setting.optparse_args } options end # Is our parameter a boolean parameter? def boolean?(param) param = param.to_sym !!(@config.include?(param) and @config[param].kind_of? BooleanSetting) end # Remove all set values, potentially skipping cli values. def clear @sync.synchronize do unsafe_clear end end # Remove all set values, potentially skipping cli values. def unsafe_clear(clear_cli = true, clear_application_defaults = false) @values.each do |name, values| next if ((name == :application_defaults) and !clear_application_defaults) next if ((name == :cli) and !clear_cli) @values.delete(name) end # Only clear the 'used' values if we were explicitly asked to clear out # :cli values; otherwise, it may be just a config file reparse, # and we want to retain this cli values. @used = [] if clear_cli + @app_defaults_initialized = false if clear_application_defaults + @cache.clear end private :unsafe_clear # This is mostly just used for testing. def clearused @cache.clear @used = [] end def app_defaults_initialized?() @app_defaults_initialized end def initialize_app_defaults(app_defaults) raise Puppet::DevError, "Attempting to initialize application default settings more than once!" if app_defaults_initialized? REQUIRED_APP_SETTINGS.each do |key| raise Puppet::SettingsError, "missing required app default setting '#{key}'" unless app_defaults.has_key?(key) end app_defaults.each do |key, value| set_value(key, value, :application_defaults) end - @app_defaults_initialized = true end # Do variable interpolation on the value. def convert(value, environment = nil) return nil if value.nil? return value unless value.is_a? String newval = value.gsub(/\$(\w+)|\$\{(\w+)\}/) do |value| varname = $2 || $1 if varname == "environment" and environment environment elsif pval = self.value(varname, environment) pval else raise Puppet::SettingsError, "Could not find value for #{value}" end end newval end # Return a value's description. def description(name) if obj = @config[name.to_sym] obj.desc else nil end end def each @config.each { |name, object| yield name, object } end # Iterate over each section name. def eachsection yielded = [] @config.each do |name, object| section = object.section unless yielded.include? section yield section yielded << section end end end # Return an object by name. def setting(param) param = param.to_sym @config[param] end # Handle a command-line argument. def handlearg(opt, value = nil) @cache.clear if value.is_a?(FalseClass) value = "false" elsif value.is_a?(TrueClass) value = "true" end value &&= munge_value(value) str = opt.sub(/^--/,'') bool = true newstr = str.sub(/^no-/, '') if newstr != str str = newstr bool = false end str = str.intern if @config[str].is_a?(Puppet::Util::Settings::BooleanSetting) if value == "" or value.nil? value = bool end end set_value(str, value, :cli) end def include?(name) name = name.intern if name.is_a? String @config.include?(name) end # check to see if a short name is already defined def shortinclude?(short) short = short.intern if name.is_a? String @shortnames.include?(short) end # Create a new collection of config settings. def initialize @config = {} @shortnames = {} @created = [] @searchpath = nil # Mutex-like thing to protect @values @sync = Sync.new # Keep track of set values. @values = Hash.new { |hash, key| hash[key] = {} } # And keep a per-environment cache @cache = Hash.new { |hash, key| hash[key] = {} } # The list of sections we've used. @used = [] end # NOTE: ACS ahh the util classes. . .sigh # as part of a fix for 1183, I pulled the logic for the following 5 methods out of the executables and puppet.rb # They probably deserve their own class, but I don't want to do that until I can refactor environments # its a little better than where they were # Prints the contents of a config file with the available config settings, or it # prints a single value of a config setting. def print_config_options env = value(:environment) val = value(:configprint) if val == "all" hash = {} each do |name, obj| val = value(name,env) val = val.inspect if val == "" hash[name] = val end hash.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, val| puts "#{name} = #{val}" end else val.split(/\s*,\s*/).sort.each do |v| if include?(v) #if there is only one value, just print it for back compatibility if v == val puts value(val,env) break end puts "#{v} = #{value(v,env)}" else puts "invalid parameter: #{v}" return false end end end true end def generate_config puts to_config true end def generate_manifest puts to_manifest true end def print_configs return print_config_options if value(:configprint) != "" return generate_config if value(:genconfig) generate_manifest if value(:genmanifest) end def print_configs? (value(:configprint) != "" || value(:genconfig) || value(:genmanifest)) && true end # Return a given object's file metadata. def metadata(param) if obj = @config[param.to_sym] and obj.is_a?(FileSetting) return [:owner, :group, :mode].inject({}) do |meta, p| if v = obj.send(p) meta[p] = v end meta end else nil end end # Make a directory with the appropriate user, group, and mode def mkdir(default) obj = get_config_file_default(default) Puppet::Util::SUIDManager.asuser(obj.owner, obj.group) do mode = obj.mode || 0750 Dir.mkdir(obj.value, mode) end end # Figure out the section name for the run_mode. def run_mode @run_mode || :user end # PRIVATE! This only exists because we need a hook to validate the run mode when it's being set, and # it should never, ever, ever, ever be called from outside of this file. def run_mode=(mode) raise Puppet::DevError, "Invalid run mode '#{mode}'" unless [:master, :agent, :user].include?(mode) @run_mode = mode end private :run_mode= # Return all of the parameters associated with a given section. def params(section = nil) if section section = section.intern if section.is_a? String @config.find_all { |name, obj| obj.section == section }.collect { |name, obj| name } else @config.keys end end # Parse the configuration file. Just provides # thread safety. def parse # we are now supporting multiple config files; the "main" config file will be the one located in # /etc/puppet (or overridden $confdir)... but we will also look for a config file in the user's home # directory. This was introduced in an effort to provide maximum backwards compatibility while # de-coupling the process of locating the config file from the "run mode" of the application. files = [main_config_file] files << user_config_file unless Puppet.features.root? @sync.synchronize do unsafe_parse(files) end end def main_config_file # the algorithm here is basically this: # * use the explicit config file location if one has been specified; this can be affected by modifications # to either the "confdir" or "config" settings (most likely via CLI arguments). # * if no explicit config location has been specified, we fall back to the default. # # The easiest way to determine whether an explicit one has been specified is to simply attempt to evaluate # the value of ":config". This will obviously be successful if they've passed an explicit value for :config, # but it will also result in successful interpolation if they've only passed an explicit value for :confdir. # # If they've specified neither, then the interpolation will fail and we'll get an exception. # begin return self[:config] if self[:config] rescue Puppet::SettingsError => err # This means we failed to interpolate, which means that they didn't explicitly specify either :config or # :confdir... so we'll fall out to the default value. end # return the default value. return File.join(self.class.default_global_config_dir, config_file_name) end private :main_config_file def user_config_file return File.join(self.class.default_user_config_dir, config_file_name) end private :user_config_file # This method is here to get around some life-cycle issues. We need to be able to determine the config file name # before the settings / defaults are fully loaded. However, we also need to respect any overrides of this value # that the user may have specified on the command line. # # The easiest way to do this is to attempt to read the setting, and if we catch an error (meaning that it hasn't been # set yet), we'll fall back to the default value. def config_file_name begin return self[:config_file_name] if self[:config_file_name] rescue Puppet::SettingsError => err # This just means that the setting wasn't explicitly set on the command line, so we will ignore it and # fall through to the default name. end return self.class.default_config_file_name end private :config_file_name # Unsafely parse the file -- this isn't thread-safe and causes plenty of problems if used directly. def unsafe_parse(files) raise Puppet::DevError unless files.length > 0 # build up a single data structure that contains the values from all of the parsed files. data = {} files.each do |file| next unless FileTest.exist?(file) begin file_data = parse_file(file) # This is a little kludgy; basically we are merging a hash of hashes. We can't use "merge" at the # outermost level or we risking losing data from the hash we're merging into. file_data.keys.each do |key| if data.has_key?(key) data[key].merge!(file_data[key]) else data[key] = file_data[key] end end rescue => detail Puppet.log_exception(detail, "Could not parse #{file}: #{detail}") return end end # If we get here and don't have any data, we just return and don't muck with the current state of the world. return if data.empty? # If we get here then we have some data, so we need to clear out any previous settings that may have come from # config files. unsafe_clear(false, false) # And now we can repopulate with the values from our last parsing of the config files. metas = {} data.each do |area, values| metas[area] = values.delete(:_meta) values.each do |key,value| set_value(key, value, area, :dont_trigger_handles => true, :ignore_bad_settings => true ) end end # Determine our environment, if we have one. if @config[:environment] env = self.value(:environment).to_sym else env = "none" end # Call any hooks we should be calling. settings_with_hooks.each do |setting| each_source(env) do |source| if value = @values[source][setting.name] # We still have to use value to retrieve the value, since # we want the fully interpolated value, not $vardir/lib or whatever. # This results in extra work, but so few of the settings # will have associated hooks that it ends up being less work this # way overall. setting.handle(self.value(setting.name, env)) break end end end # We have to do it in the reverse of the search path, # because multiple sections could set the same value # and I'm too lazy to only set the metadata once. searchpath.reverse.each do |source| source = run_mode if source == :run_mode source = @name if (@name && source == :name) if meta = metas[source] set_metadata(meta) end end end private :unsafe_parse # Create a new setting. The value is passed in because it's used to determine # what kind of setting we're creating, but the value itself might be either # a default or a value, so we can't actually assign it. # # See #define_settings for documentation on the legal values for the ":type" option. def newsetting(hash) klass = nil hash[:section] = hash[:section].to_sym if hash[:section] if type = hash[:type] unless klass = { :string => StringSetting, :file => FileSetting, :directory => DirectorySetting, :path => StringSetting, :boolean => BooleanSetting, } [type] raise ArgumentError, "Invalid setting type '#{type}'" end hash.delete(:type) else # The only implicit typing we still do for settings is to fall back to "String" type if they didn't explicitly # specify a type. Personally I'd like to get rid of this too, and make the "type" option mandatory... but # there was a little resistance to taking things quite that far for now. --cprice 2012-03-19 klass = StringSetting end hash[:settings] = self setting = klass.new(hash) setting end # This has to be private, because it doesn't add the settings to @config private :newsetting # Iterate across all of the objects in a given section. def persection(section) section = section.to_sym self.each { |name, obj| if obj.section == section yield obj end } end # Reparse our config file, if necessary. def reparse if files if filename = any_files_changed? Puppet.notice "Config file #{filename} changed; triggering re-parse of all config files." parse reuse end end end def files return @files if @files @files = [] [main_config_file, user_config_file].each do |path| if FileTest.exist?(path) @files << Puppet::Util::LoadedFile.new(path) end end @files end private :files # Checks to see if any of the config files have been modified # @return the filename of the first file that is found to have changed, or nil if no files have changed def any_files_changed? files.each do |file| return file.file if file.changed? end nil end private :any_files_changed? def reuse return unless defined?(@used) @sync.synchronize do # yay, thread-safe new = @used @used = [] self.use(*new) end end # The order in which to search for values. def searchpath(environment = nil) if environment [:cli, :memory, environment, :run_mode, :main, :application_defaults] else [:cli, :memory, :run_mode, :main, :application_defaults] end end # Get a list of objects per section def sectionlist sectionlist = [] self.each { |name, obj| section = obj.section || "puppet" sections[section] ||= [] sectionlist << section unless sectionlist.include?(section) sections[section] << obj } return sectionlist, sections end def service_user_available? return @service_user_available if defined?(@service_user_available) return @service_user_available = false unless user_name = self[:user] user = Puppet::Type.type(:user).new :name => self[:user], :audit => :ensure @service_user_available = user.exists? end def legacy_to_mode(type, param) require 'puppet/util/command_line/legacy_command_line' if Puppet::Util::CommandLine::LegacyCommandLine::LEGACY_APPS.has_key?(type) new_type = Puppet::Util::CommandLine::LegacyCommandLine::LEGACY_APPS[type].run_mode Puppet.deprecation_warning "You have configuration parameter $#{param} specified in [#{type}], which is a deprecated section. I'm assuming you meant [#{new_type}]" return new_type end type end # Allow later inspection to determine if the setting was set on the # command line, or through some other code path. Used for the # `dns_alt_names` option during cert generate. --daniel 2011-10-18 def set_by_cli?(param) param = param.to_sym !@values[:cli][param].nil? end def set_value(param, value, type, options = {}) param = param.to_sym unless setting = @config[param] if options[:ignore_bad_settings] return else raise ArgumentError, "Attempt to assign a value to unknown configuration parameter #{param.inspect}" end end value = setting.munge(value) if setting.respond_to?(:munge) setting.handle(value) if setting.respond_to?(:handle) and not options[:dont_trigger_handles] if ReadOnly.include? param and type != :application_defaults raise ArgumentError, "You're attempting to set configuration parameter $#{param}, which is read-only." end type = legacy_to_mode(type, param) @sync.synchronize do # yay, thread-safe @values[type][param] = value @cache.clear clearused # Clear the list of environments, because they cache, at least, the module path. # We *could* preferentially just clear them if the modulepath is changed, # but we don't really know if, say, the vardir is changed and the modulepath # is defined relative to it. We need the defined?(stuff) because of loading # order issues. Puppet::Node::Environment.clear if defined?(Puppet::Node) and defined?(Puppet::Node::Environment) end # This is a hack. The run_mode should probably not be a "normal" setting, because the places # it is used tend to create lifecycle issues and cause other weird problems. In some places # we need for it to have a default value, in other places it may be preferable to be able to # determine that it has not yet been set. There used to be a global variable that some # code paths would access; as a first step towards cleaning it up, I've gotten rid of the global # variable and am instead using an instance variable in this class, but that means that if # someone modifies the value of the setting at a later point during execution, then the # instance variable needs to be updated as well... so that's what we're doing here. # # This code should be removed if we get a chance to remove run_mode from the defined settings. # --cprice 2012-03-19 self.run_mode = value if param == :run_mode value end # Deprecated; use #define_settings instead def setdefaults(section, defs) Puppet.deprecation_warning("'setdefaults' is deprecated and will be removed; please call 'define_settings' instead") define_settings(section, defs) end # Define a group of settings. # # @param [Symbol] section a symbol to use for grouping multiple settings together into a conceptual unit. This value # (and the conceptual separation) is not used very often; the main place where it will have a potential impact # is when code calls Settings#use method. See docs on that method for further details, but basically that method # just attempts to do any preparation that may be necessary before code attempts to leverage the value of a particular # setting. This has the most impact for file/directory settings, where #use will attempt to "ensure" those # files / directories. # @param [Hash[Hash]] defs the settings to be defined. This argument is a hash of hashes; each key should be a symbol, # which is basically the name of the setting that you are defining. The value should be another hash that specifies # the parameters for the particular setting. Legal values include: # [:default] => required; this is a string value that will be used as a default value for a setting if no other # value is specified (via cli, config file, etc.) This string may include "variables", demarcated with $ or ${}, # which will be interpolated with values of other settings. # [:desc] => required; a description of the setting, used in documentation / help generation # [:type] => not required, but highly encouraged! This specifies the data type that the setting represents. If # you do not specify it, it will default to "string". Legal values include: # :string - A generic string setting # :boolean - A boolean setting; values are expected to be "true" or "false" # :file - A (single) file path; puppet may attempt to create this file depending on how the settings are used. This type # also supports additional options such as "mode", "owner", "group" # :directory - A (single) directory path; puppet may attempt to create this file depending on how the settings are used. This type # also supports additional options such as "mode", "owner", "group" # :path - This is intended to be used for settings whose value can contain multiple directory paths, respresented # as strings separated by the system path separator (e.g. system path, module path, etc.). # [:mode] => an (optional) octal value to be used as the permissions/mode for :file and :directory settings # [:owner] => optional owner username/uid for :file and :directory settings # [:group] => optional group name/gid for :file and :directory settings # def define_settings(section, defs) section = section.to_sym call = [] defs.each { |name, hash| raise ArgumentError, "setting definition for '#{name}' is not a hash!" unless hash.is_a? Hash name = name.to_sym hash[:name] = name hash[:section] = section raise ArgumentError, "Parameter #{name} is already defined" if @config.include?(name) tryconfig = newsetting(hash) if short = tryconfig.short if other = @shortnames[short] raise ArgumentError, "Parameter #{other.name} is already using short name '#{short}'" end @shortnames[short] = tryconfig end @config[name] = tryconfig # Collect the settings that need to have their hooks called immediately. # We have to collect them so that we can be sure we're fully initialized before # the hook is called. call << tryconfig if tryconfig.call_on_define } call.each { |setting| setting.handle(self.value(setting.name)) } end # Convert the settings we manage into a catalog full of resources that model those settings. def to_catalog(*sections) sections = nil if sections.empty? catalog = Puppet::Resource::Catalog.new("Settings") @config.keys.find_all { |key| @config[key].is_a?(FileSetting) }.each do |key| file = @config[key] next unless (sections.nil? or sections.include?(file.section)) next unless resource = file.to_resource next if catalog.resource(resource.ref) Puppet.debug("Using settings: adding file resource '#{key}': '#{resource.inspect}'") catalog.add_resource(resource) end add_user_resources(catalog, sections) catalog end # Convert our list of config settings into a configuration file. def to_config - str = %{The configuration file for #{Puppet[:name]}. Note that this file + str = %{The configuration file for #{Puppet.run_mode.name}. Note that this file is likely to have unused configuration parameters in it; any parameter that's valid anywhere in Puppet can be in any config file, even if it's not used. Every section can specify three special parameters: owner, group, and mode. These parameters affect the required permissions of any files specified after their specification. Puppet will sometimes use these parameters to check its own configured state, so they can be used to make Puppet a bit more self-managing. Generated on #{Time.now}. }.gsub(/^/, "# ") # Add a section heading that matches our name. if @config.include?(:run_mode) str += "[#{self[:run_mode]}]\n" end eachsection do |section| persection(section) do |obj| str += obj.to_config + "\n" unless ReadOnly.include? obj.name or obj.name == :genconfig end end return str end # Convert to a parseable manifest def to_manifest catalog = to_catalog catalog.resource_refs.collect do |ref| catalog.resource(ref).to_manifest end.join("\n\n") end # Create the necessary objects to use a section. This is idempotent; # you can 'use' a section as many times as you want. def use(*sections) sections = sections.collect { |s| s.to_sym } @sync.synchronize do # yay, thread-safe sections = sections.reject { |s| @used.include?(s) } return if sections.empty? begin catalog = to_catalog(*sections).to_ral rescue => detail Puppet.log_and_raise(detail, "Could not create resources for managing Puppet's files and directories in sections #{sections.inspect}: #{detail}") end catalog.host_config = false catalog.apply do |transaction| if transaction.any_failed? report = transaction.report failures = report.logs.find_all { |log| log.level == :err } raise "Got #{failures.length} failure(s) while initializing: #{failures.collect { |l| l.to_s }.join("; ")}" end end sections.each { |s| @used << s } @used.uniq! end end def valid?(param) param = param.to_sym @config.has_key?(param) end def uninterpolated_value(param, environment = nil) param = param.to_sym environment &&= environment.to_sym # See if we can find it within our searchable list of values val = find_value(environment, param) # If we didn't get a value, use the default val = @config[param].default if val.nil? val end def find_value(environment, param) each_source(environment) do |source| # Look for the value. We have to test the hash for whether # it exists, because the value might be false. @sync.synchronize do return @values[source][param] if @values[source].include?(param) end end return nil end private :find_value # Find the correct value using our search path. Optionally accept an environment # in which to search before the other configuration sections. - def value(param, environment = nil) + def value(param, environment = nil, bypass_interpolation = false) param = param.to_sym environment &&= environment.to_sym # Short circuit to nil for undefined parameters. return nil unless @config.include?(param) # Yay, recursion. #self.reparse unless [:config, :filetimeout].include?(param) # Check the cache first. It needs to be a per-environment # cache so that we don't spread values from one env # to another. if cached = @cache[environment||"none"][param] return cached end val = uninterpolated_value(param, environment) + return val if bypass_interpolation if param == :code # if we interpolate code, all hell breaks loose. return val end # Convert it if necessary begin val = convert(val, environment) rescue Puppet::SettingsError => err raise Puppet::SettingsError.new("Error converting value for param '#{param}': #{err}") end # And cache it @cache[environment||"none"][param] = val val end # Open a file with the appropriate user, group, and mode def write(default, *args, &bloc) obj = get_config_file_default(default) writesub(default, value(obj.name), *args, &bloc) end # Open a non-default file under a default dir with the appropriate user, # group, and mode def writesub(default, file, *args, &bloc) obj = get_config_file_default(default) chown = nil if Puppet.features.root? chown = [obj.owner, obj.group] else chown = [nil, nil] end Puppet::Util::SUIDManager.asuser(*chown) do mode = obj.mode ? obj.mode.to_i : 0640 args << "w" if args.empty? args << mode # Update the umask to make non-executable files Puppet::Util.withumask(File.umask ^ 0111) do File.open(file, *args) do |file| yield file end end end end def readwritelock(default, *args, &bloc) file = value(get_config_file_default(default).name) tmpfile = file + ".tmp" sync = Sync.new raise Puppet::DevError, "Cannot create #{file}; directory #{File.dirname(file)} does not exist" unless FileTest.directory?(File.dirname(tmpfile)) sync.synchronize(Sync::EX) do File.open(file, ::File::CREAT|::File::RDWR, 0600) do |rf| rf.lock_exclusive do if File.exist?(tmpfile) raise Puppet::Error, ".tmp file already exists for #{file}; Aborting locked write. Check the .tmp file and delete if appropriate" end # If there's a failure, remove our tmpfile begin writesub(default, tmpfile, *args, &bloc) rescue File.unlink(tmpfile) if FileTest.exist?(tmpfile) raise end begin File.rename(tmpfile, file) rescue => detail Puppet.err "Could not rename #{file} to #{tmpfile}: #{detail}" File.unlink(tmpfile) if FileTest.exist?(tmpfile) end end end end end private def get_config_file_default(default) obj = nil unless obj = @config[default] raise ArgumentError, "Unknown default #{default}" end raise ArgumentError, "Default #{default} is not a file" unless obj.is_a? FileSetting obj end def add_user_resources(catalog, sections) return unless Puppet.features.root? return if Puppet.features.microsoft_windows? return unless self[:mkusers] @config.each do |name, setting| next unless setting.respond_to?(:owner) next unless sections.nil? or sections.include?(setting.section) if user = setting.owner and user != "root" and catalog.resource(:user, user).nil? resource = Puppet::Resource.new(:user, user, :parameters => {:ensure => :present}) resource[:gid] = self[:group] if self[:group] catalog.add_resource resource end if group = setting.group and ! %w{root wheel}.include?(group) and catalog.resource(:group, group).nil? catalog.add_resource Puppet::Resource.new(:group, group, :parameters => {:ensure => :present}) end end end # Yield each search source in turn. def each_source(environment) searchpath(environment).each do |source| # Modify the source as necessary. source = self.run_mode if source == :run_mode yield source end end # Return all settings that have associated hooks; this is so # we can call them after parsing the configuration file. def settings_with_hooks @config.values.find_all { |setting| setting.respond_to?(:handle) } end # Extract extra setting information for files. def extract_fileinfo(string) result = {} value = string.sub(/\{\s*([^}]+)\s*\}/) do params = $1 params.split(/\s*,\s*/).each do |str| if str =~ /^\s*(\w+)\s*=\s*([\w\d]+)\s*$/ param, value = $1.intern, $2 result[param] = value raise ArgumentError, "Invalid file option '#{param}'" unless [:owner, :mode, :group].include?(param) if param == :mode and value !~ /^\d+$/ raise ArgumentError, "File modes must be numbers" end else raise ArgumentError, "Could not parse '#{string}'" end end '' end result[:value] = value.sub(/\s*$/, '') result end # Convert arguments into booleans, integers, or whatever. def munge_value(value) # Handle different data types correctly return case value when /^false$/i; false when /^true$/i; true when /^\d+$/i; Integer(value) when true; true when false; false else value.gsub(/^["']|["']$/,'').sub(/\s+$/, '') end end # This method just turns a file in to a hash of hashes. def parse_file(file) text = read_file(file) result = Hash.new { |names, name| names[name] = {} } count = 0 # Default to 'main' for the section. section = :main result[section][:_meta] = {} text.split(/\n/).each do |line| count += 1 case line when /^\s*\[(\w+)\]\s*$/ section = $1.intern # Section names #disallow application_defaults in config file if section == :application_defaults raise Puppet::Error.new("Illegal section 'application_defaults' in config file", file, line) end # Add a meta section result[section][:_meta] ||= {} when /^\s*#/; next # Skip comments when /^\s*$/; next # Skip blanks when /^\s*(\w+)\s*=\s*(.*?)\s*$/ # settings var = $1.intern # We don't want to munge modes, because they're specified in octal, so we'll # just leave them as a String, since Puppet handles that case correctly. if var == :mode value = $2 else value = munge_value($2) end # Check to see if this is a file argument and it has extra options begin if value.is_a?(String) and options = extract_fileinfo(value) value = options[:value] options.delete(:value) result[section][:_meta][var] = options end result[section][var] = value rescue Puppet::Error => detail detail.file = file detail.line = line raise end else error = Puppet::Error.new("Could not match line #{line}") error.file = file error.line = line raise error end end result end # Read the file in. def read_file(file) begin return File.read(file) rescue Errno::ENOENT raise ArgumentError, "No such file #{file}" rescue Errno::EACCES raise ArgumentError, "Permission denied to file #{file}" end end # Set file metadata. def set_metadata(meta) meta.each do |var, values| values.each do |param, value| @config[var].send(param.to_s + "=", value) end end end # Private method for internal test use only; allows to do a comprehensive clear of all settings between tests. # # @return nil def clear_everything_for_tests() @sync.synchronize do unsafe_clear(true, true) @app_defaults_initialized = false end end private :clear_everything_for_tests end diff --git a/lib/puppet/util/settings/string_setting.rb b/lib/puppet/util/settings/string_setting.rb index 740595236..22428d5c1 100644 --- a/lib/puppet/util/settings/string_setting.rb +++ b/lib/puppet/util/settings/string_setting.rb @@ -1,107 +1,112 @@ # The base element type. class Puppet::Util::Settings::StringSetting attr_accessor :name, :section, :default, :call_on_define attr_reader :desc, :short def desc=(value) @desc = value.gsub(/^\s*/, '') end #added as a proper method, only to generate a deprecation warning #and return value from def setbycli Puppet.deprecation_warning "Puppet.settings.setting(#{name}).setbycli is deprecated. Use Puppet.settings.set_by_cli?(#{name}) instead." @settings.set_by_cli?(name) end def setbycli=(value) Puppet.deprecation_warning "Puppet.settings.setting(#{name}).setbycli= is deprecated. You should not manually set that values were specified on the command line." @settings.set_value(name, @settings[name], :cli) if value raise ArgumentError, "Cannot unset setbycli" unless value end # get the arguments in getopt format def getopt_args if short [["--#{name}", "-#{short}", GetoptLong::REQUIRED_ARGUMENT]] else [["--#{name}", GetoptLong::REQUIRED_ARGUMENT]] end end # get the arguments in OptionParser format def optparse_args if short ["--#{name}", "-#{short}", desc, :REQUIRED] else ["--#{name}", desc, :REQUIRED] end end def hook=(block) meta_def :handle, &block end # Create the new element. Pretty much just sets the name. def initialize(args = {}) unless @settings = args.delete(:settings) raise ArgumentError.new("You must refer to a settings object") end args.each do |param, value| method = param.to_s + "=" raise ArgumentError, "#{self.class} (setting '#{args[:name]}') does not accept #{param}" unless self.respond_to? method self.send(method, value) end raise ArgumentError, "You must provide a description for the #{self.name} config option" unless self.desc end def iscreated @iscreated = true end def iscreated? @iscreated end def set? !!(!@value.nil?) end # short name for the celement def short=(value) raise ArgumentError, "Short names can only be one character." if value.to_s.length != 1 @short = value.to_s end + + def default(check_application_defaults_first = false) + return @default unless check_application_defaults_first + return @settings.value(name, :application_defaults, true) || @default + end # Convert the object to a config statement. def to_config str = @desc.gsub(/^/, "# ") + "\n" # Add in a statement about the default. - str += "# The default value is '#{@default}'.\n" if @default + str += "# The default value is '#{default(true)}'.\n" if default(true) # If the value has not been overridden, then print it out commented # and unconverted, so it's clear that that's the default and how it # works. value = @settings.value(self.name) if value != @default line = "#{@name} = #{value}" else line = "# #{@name} = #{@default}" end str += line + "\n" str.gsub(/^/, " ") end # Retrieves the value, or if it's not set, retrieves the default. def value @settings.value(self.name) end end diff --git a/spec/unit/daemon_spec.rb b/spec/unit/daemon_spec.rb index e6b700fb5..207db3df3 100755 --- a/spec/unit/daemon_spec.rb +++ b/spec/unit/daemon_spec.rb @@ -1,268 +1,268 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/daemon' require 'puppet/agent' def without_warnings flag = $VERBOSE $VERBOSE = nil yield $VERBOSE = flag end class TestClient def lockfile_path "/dev/null" end end describe Puppet::Daemon, :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files before do @agent = Puppet::Agent.new(TestClient.new) @daemon = Puppet::Daemon.new @daemon.stubs(:close_streams).returns nil end it "should be able to manage an agent" do @daemon.should respond_to(:agent) end it "should be able to manage a network server" do @daemon.should respond_to(:server) end it "should reopen the Log logs when told to reopen logs" do Puppet::Util::Log.expects(:reopen) @daemon.reopen_logs end describe "when setting signal traps" do signals = {:INT => :stop, :TERM => :stop } signals.update({:HUP => :restart, :USR1 => :reload, :USR2 => :reopen_logs}) unless Puppet.features.microsoft_windows? signals.each do |signal, method| it "should log and call #{method} when it receives #{signal}" do Signal.expects(:trap).with(signal).yields Puppet.expects(:notice) @daemon.expects(method) @daemon.set_signal_traps end end end describe "when starting" do before do @daemon.stubs(:create_pidfile) @daemon.stubs(:set_signal_traps) @daemon.stubs(:run_event_loop) end it "should fail if it has neither agent nor server" do lambda { @daemon.start }.should raise_error(Puppet::DevError) end it "should create its pidfile" do @daemon.agent = @agent @daemon.expects(:create_pidfile) @daemon.start end it "should start its server if one is configured" do server = mock 'server' server.expects(:start) @daemon.stubs(:server).returns server @daemon.start end end describe "when stopping" do before do @daemon.stubs(:remove_pidfile) Puppet::Util::Log.stubs(:close_all) # to make the global safe to mock, set it to a subclass of itself, # then restore it in an after pass without_warnings { Puppet::Application = Class.new(Puppet::Application) } end after do # restore from the superclass so we lose the stub garbage without_warnings { Puppet::Application = Puppet::Application.superclass } end it "should stop its server if one is configured" do server = mock 'server' server.expects(:stop) @daemon.stubs(:server).returns server expect { @daemon.stop }.to exit_with 0 end it 'should request a stop from Puppet::Application' do Puppet::Application.expects(:stop!) expect { @daemon.stop }.to exit_with 0 end it "should remove its pidfile" do @daemon.expects(:remove_pidfile) expect { @daemon.stop }.to exit_with 0 end it "should close all logs" do Puppet::Util::Log.expects(:close_all) expect { @daemon.stop }.to exit_with 0 end it "should exit unless called with ':exit => false'" do expect { @daemon.stop }.to exit_with 0 end it "should not exit if called with ':exit => false'" do @daemon.stop :exit => false end end describe "when creating its pidfile" do it "should use an exclusive mutex" do - Puppet.settings.expects(:value).with(:name).returns "me" + Puppet.run_mode.expects(:name).returns "me" Puppet::Util.expects(:synchronize_on).with("me",Sync::EX) @daemon.create_pidfile end it "should lock the pidfile using the Pidlock class" do pidfile = mock 'pidfile' - Puppet.settings.stubs(:value).with(:name).returns "eh" + Puppet.run_mode.expects(:name).returns "eh" Puppet.settings.expects(:value).with(:pidfile).returns make_absolute("/my/file") Puppet::Util::Pidlock.expects(:new).with(make_absolute("/my/file")).returns pidfile pidfile.expects(:lock).returns true @daemon.create_pidfile end it "should fail if it cannot lock" do pidfile = mock 'pidfile' - Puppet.settings.stubs(:value).with(:name).returns "eh" + Puppet.run_mode.expects(:name).returns "eh" Puppet.settings.stubs(:value).with(:pidfile).returns make_absolute("/my/file") Puppet::Util::Pidlock.expects(:new).with(make_absolute("/my/file")).returns pidfile pidfile.expects(:lock).returns false lambda { @daemon.create_pidfile }.should raise_error end end describe "when removing its pidfile" do it "should use an exclusive mutex" do - Puppet.settings.expects(:value).with(:name).returns "me" + Puppet.run_mode.expects(:name).returns "me" Puppet::Util.expects(:synchronize_on).with("me",Sync::EX) @daemon.remove_pidfile end it "should do nothing if the pidfile is not present" do pidfile = mock 'pidfile', :unlock => false Puppet[:pidfile] = make_absolute("/my/file") Puppet::Util::Pidlock.expects(:new).with(make_absolute("/my/file")).returns pidfile @daemon.remove_pidfile end it "should unlock the pidfile using the Pidlock class" do pidfile = mock 'pidfile', :unlock => true Puppet[:pidfile] = make_absolute("/my/file") Puppet::Util::Pidlock.expects(:new).with(make_absolute("/my/file")).returns pidfile @daemon.remove_pidfile end end describe "when reloading" do it "should do nothing if no agent is configured" do @daemon.reload end it "should do nothing if the agent is running" do @agent.expects(:running?).returns true @daemon.agent = @agent @daemon.reload end it "should run the agent if one is available and it is not running" do @agent.expects(:running?).returns false @agent.expects :run @daemon.agent = @agent @daemon.reload end end describe "when restarting" do before do without_warnings { Puppet::Application = Class.new(Puppet::Application) } end after do without_warnings { Puppet::Application = Puppet::Application.superclass } end it 'should set Puppet::Application.restart!' do Puppet::Application.expects(:restart!) @daemon.stubs(:reexec) @daemon.restart end it "should reexec itself if no agent is available" do @daemon.expects(:reexec) @daemon.restart end it "should reexec itself if the agent is not running" do @agent.expects(:running?).returns false @daemon.agent = @agent @daemon.expects(:reexec) @daemon.restart end end describe "when reexecing it self" do before do @daemon.stubs(:exec) @daemon.stubs(:stop) end it "should fail if no argv values are available" do @daemon.expects(:argv).returns nil lambda { @daemon.reexec }.should raise_error(Puppet::DevError) end it "should shut down without exiting" do @daemon.argv = %w{foo} @daemon.expects(:stop).with(:exit => false) @daemon.reexec end it "should call 'exec' with the original executable and arguments" do @daemon.argv = %w{foo} @daemon.expects(:exec).with($0 + " foo") @daemon.reexec end end end diff --git a/spec/unit/network/server_spec.rb b/spec/unit/network/server_spec.rb index 20c34da2c..00fca4288 100755 --- a/spec/unit/network/server_spec.rb +++ b/spec/unit/network/server_spec.rb @@ -1,411 +1,411 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/network/server' describe Puppet::Network::Server do before do @mock_http_server_class = mock('http server class') Puppet.settings.stubs(:use) - Puppet.settings.stubs(:value).with(:name).returns("me") + Puppet.run_mode.stubs(:name).returns "me" Puppet.settings.stubs(:value).with(:servertype).returns(:suparserver) Puppet.settings.stubs(:value).with(:bindaddress).returns("") Puppet.settings.stubs(:value).with(:masterport).returns(8140) Puppet.settings.stubs(:value).with(:daemonize).returns(true) Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class) Puppet.settings.stubs(:value).with(:servertype).returns(:suparserver) @server = Puppet::Network::Server.new(:port => 31337) @server.stubs(:close_streams).returns(nil) end describe "when initializing" do before do Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') Puppet.settings.stubs(:value).with(:bindaddress).returns("") Puppet.settings.stubs(:value).with(:masterport).returns('') end it 'should fail if an unknown option is provided' do lambda { Puppet::Network::Server.new(:foo => 31337) }.should raise_error(ArgumentError) end it "should allow specifying a listening port" do Puppet.settings.stubs(:value).with(:bindaddress).returns('') @server = Puppet::Network::Server.new(:port => 31337) @server.port.should == 31337 end it "should use the :bindaddress setting to determine the default listening address" do Puppet.settings.stubs(:value).with(:masterport).returns('') Puppet.settings.expects(:value).with(:bindaddress).returns("10.0.0.1") @server = Puppet::Network::Server.new @server.address.should == "10.0.0.1" end it "should set the bind address to '127.0.0.1' if the default address is an empty string and the server type is mongrel" do Puppet.settings.stubs(:value).with(:servertype).returns("mongrel") Puppet.settings.expects(:value).with(:bindaddress).returns("") @server = Puppet::Network::Server.new @server.address.should == '127.0.0.1' end it "should set the bind address to '0.0.0.0' if the default address is an empty string and the server type is webrick" do Puppet.settings.stubs(:value).with(:servertype).returns("webrick") Puppet.settings.expects(:value).with(:bindaddress).returns("") @server = Puppet::Network::Server.new @server.address.should == '0.0.0.0' end it "should use the Puppet configurator to find a default listening port" do Puppet.settings.stubs(:value).with(:bindaddress).returns('') Puppet.settings.expects(:value).with(:masterport).returns(6667) @server = Puppet::Network::Server.new @server.port.should == 6667 end it "should fail to initialize if no listening port can be found" do Puppet.settings.stubs(:value).with(:bindaddress).returns("127.0.0.1") Puppet.settings.stubs(:value).with(:masterport).returns(nil) lambda { Puppet::Network::Server.new }.should raise_error(ArgumentError) end it "should use the Puppet configurator to determine which HTTP server will be used to provide access to clients" do Puppet.settings.expects(:value).with(:servertype).returns(:suparserver) @server = Puppet::Network::Server.new(:port => 31337) @server.server_type.should == :suparserver end it "should fail to initialize if there is no HTTP server known to the Puppet configurator" do Puppet.settings.expects(:value).with(:servertype).returns(nil) lambda { Puppet::Network::Server.new(:port => 31337) }.should raise_error end it "should ask the Puppet::Network::HTTP class to fetch the proper HTTP server class" do Puppet::Network::HTTP.expects(:server_class_by_type).with(:suparserver).returns(@mock_http_server_class) @server = Puppet::Network::Server.new(:port => 31337) end it "should fail if the HTTP server class is unknown" do Puppet::Network::HTTP.stubs(:server_class_by_type).returns(nil) lambda { Puppet::Network::Server.new(:port => 31337) }.should raise_error(ArgumentError) end it "should allow registering REST handlers" do @server = Puppet::Network::Server.new(:port => 31337, :handlers => [ :foo, :bar, :baz]) lambda { @server.unregister(:foo, :bar, :baz) }.should_not raise_error end it "should not be listening after initialization" do Puppet::Network::Server.new(:port => 31337).should_not be_listening end it "should use the :main setting section" do Puppet.settings.expects(:use).with { |*args| args.include?(:main) } @server = Puppet::Network::Server.new(:port => 31337) end it "should use the :application setting section" do #Puppet.settings.expects(:value).with(:name).returns "me" Puppet.settings.expects(:use).with { |*args| args.include?(:application) } @server = Puppet::Network::Server.new(:port => 31337) end end # We don't test the method, because it's too much of a Unix-y pain. it "should be able to daemonize" do @server.should respond_to(:daemonize) end describe "when being started" do before do @server.stubs(:listen) @server.stubs(:create_pidfile) end it "should listen" do @server.expects(:listen) @server.start end it "should create its PID file" do @server.expects(:create_pidfile) @server.start end end describe "when being stopped" do before do @server.stubs(:unlisten) @server.stubs(:remove_pidfile) end it "should unlisten" do @server.expects(:unlisten) @server.stop end it "should remove its PID file" do @server.expects(:remove_pidfile) @server.stop end end describe "when creating its pidfile" do it "should use an exclusive mutex" do - Puppet.settings.expects(:value).with(:name).returns "me" + Puppet.run_mode.expects(:name).returns "me" Puppet::Util.expects(:synchronize_on).with("me",Sync::EX) @server.create_pidfile end it "should lock the pidfile using the Pidlock class" do pidfile = mock 'pidfile' - Puppet.settings.stubs(:value).with(:name).returns "eh" + Puppet.run_mode.expects(:name).returns "eh" Puppet.settings.expects(:value).with(:pidfile).returns "/my/file" Puppet::Util::Pidlock.expects(:new).with("/my/file").returns pidfile pidfile.expects(:lock).returns true @server.create_pidfile end it "should fail if it cannot lock" do pidfile = mock 'pidfile' - Puppet.settings.stubs(:value).with(:name).returns "eh" + Puppet.run_mode.stubs(:name).returns "eh" Puppet.settings.stubs(:value).with(:pidfile).returns "/my/file" Puppet::Util::Pidlock.expects(:new).with("/my/file").returns pidfile pidfile.expects(:lock).returns false lambda { @server.create_pidfile }.should raise_error end end describe "when removing its pidfile" do it "should use an exclusive mutex" do - Puppet.settings.expects(:value).with(:name).returns "me" + Puppet.run_mode.expects(:name).returns "me" Puppet::Util.expects(:synchronize_on).with("me",Sync::EX) @server.remove_pidfile end it "should do nothing if the pidfile is not present" do pidfile = mock 'pidfile', :unlock => false Puppet::Util::Pidlock.expects(:new).with("/my/file").returns pidfile Puppet.settings.stubs(:value).with(:pidfile).returns "/my/file" @server.remove_pidfile end it "should unlock the pidfile using the Pidlock class" do pidfile = mock 'pidfile', :unlock => true Puppet::Util::Pidlock.expects(:new).with("/my/file").returns pidfile Puppet.settings.stubs(:value).with(:pidfile).returns "/my/file" @server.remove_pidfile end end describe "when managing indirection registrations" do before do Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') end it "should allow registering an indirection for client access by specifying its indirection name" do lambda { @server.register(:foo) }.should_not raise_error end it "should require that the indirection be valid" do Puppet::Indirector::Indirection.expects(:model).with(:foo).returns nil lambda { @server.register(:foo) }.should raise_error(ArgumentError) end it "should require at least one indirection name when registering indirections for client access" do lambda { @server.register }.should raise_error(ArgumentError) end it "should allow for numerous indirections to be registered at once for client access" do lambda { @server.register(:foo, :bar, :baz) }.should_not raise_error end it "should allow the use of indirection names to specify which indirections are to be no longer accessible to clients" do @server.register(:foo) lambda { @server.unregister(:foo) }.should_not raise_error end it "should leave other indirections accessible to clients when turning off indirections" do @server.register(:foo, :bar) @server.unregister(:foo) lambda { @server.unregister(:bar)}.should_not raise_error end it "should allow specifying numerous indirections which are to be no longer accessible to clients" do @server.register(:foo, :bar) lambda { @server.unregister(:foo, :bar) }.should_not raise_error end it "should not turn off any indirections if given unknown indirection names to turn off" do @server.register(:foo, :bar) lambda { @server.unregister(:foo, :bar, :baz) }.should raise_error(ArgumentError) lambda { @server.unregister(:foo, :bar) }.should_not raise_error end it "should not allow turning off unknown indirection names" do @server.register(:foo, :bar) lambda { @server.unregister(:baz) }.should raise_error(ArgumentError) end it "should disable client access immediately when turning off indirections" do @server.register(:foo, :bar) @server.unregister(:foo) lambda { @server.unregister(:foo) }.should raise_error(ArgumentError) end it "should allow turning off all indirections at once" do @server.register(:foo, :bar) @server.unregister [ :foo, :bar, :baz].each do |indirection| lambda { @server.unregister(indirection) }.should raise_error(ArgumentError) end end end it "should provide a means of determining whether it is listening" do @server.should respond_to(:listening?) end it "should provide a means of determining which HTTP server will be used to provide access to clients" do @server.server_type.should == :suparserver end it "should provide a means of determining the listening address" do @server.address.should == "127.0.0.1" end it "should provide a means of determining the listening port" do @server.port.should == 31337 end it "should allow for multiple configurations, each handling different indirections" do Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') @server2 = Puppet::Network::Server.new(:port => 31337) @server.register(:foo, :bar) @server2.register(:foo, :xyzzy) @server.unregister(:foo, :bar) @server2.unregister(:foo, :xyzzy) lambda { @server.unregister(:xyzzy) }.should raise_error(ArgumentError) lambda { @server2.unregister(:bar) }.should raise_error(ArgumentError) end describe "when listening is off" do before do @mock_http_server = mock('http server') @mock_http_server.stubs(:listen) @server.stubs(:http_server).returns(@mock_http_server) end it "should indicate that it is not listening" do @server.should_not be_listening end it "should not allow listening to be turned off" do lambda { @server.unlisten }.should raise_error(RuntimeError) end it "should allow listening to be turned on" do lambda { @server.listen }.should_not raise_error end end describe "when listening is on" do before do @mock_http_server = mock('http server') @mock_http_server.stubs(:listen) @mock_http_server.stubs(:unlisten) @server.stubs(:http_server).returns(@mock_http_server) @server.listen end it "should indicate that it is listening" do @server.should be_listening end it "should not allow listening to be turned on" do lambda { @server.listen }.should raise_error(RuntimeError) end it "should allow listening to be turned off" do lambda { @server.unlisten }.should_not raise_error end end describe "when listening is being turned on" do before do Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') @server = Puppet::Network::Server.new(:port => 31337, :handlers => [:node]) @mock_http_server = mock('http server') @mock_http_server.stubs(:listen) end it "should fetch an instance of an HTTP server" do @server.stubs(:http_server_class).returns(@mock_http_server_class) @mock_http_server_class.expects(:new).returns(@mock_http_server) @server.listen end it "should cause the HTTP server to listen" do @server.stubs(:http_server).returns(@mock_http_server) @mock_http_server.expects(:listen) @server.listen end it "should pass the listening address to the HTTP server" do @server.stubs(:http_server).returns(@mock_http_server) @mock_http_server.expects(:listen).with do |args| args[:address] == '127.0.0.1' end @server.listen end it "should pass the listening port to the HTTP server" do @server.stubs(:http_server).returns(@mock_http_server) @mock_http_server.expects(:listen).with do |args| args[:port] == 31337 end @server.listen end it "should pass a list of REST handlers to the HTTP server" do @server.stubs(:http_server).returns(@mock_http_server) @mock_http_server.expects(:listen).with do |args| args[:handlers] == [ :node ] end @server.listen end end describe "when listening is being turned off" do before do @mock_http_server = mock('http server') @mock_http_server.stubs(:listen) @server.stubs(:http_server).returns(@mock_http_server) @server.listen end it "should cause the HTTP server to stop listening" do @mock_http_server.expects(:unlisten) @server.unlisten end it "should not allow for indirections to be turned off" do Puppet::Indirector::Indirection.stubs(:model).returns mock('indirection') @server.register(:foo) lambda { @server.unregister(:foo) }.should raise_error(RuntimeError) end end end diff --git a/spec/unit/util/settings/string_setting_spec.rb b/spec/unit/util/settings/string_setting_spec.rb new file mode 100644 index 000000000..f0b314f5a --- /dev/null +++ b/spec/unit/util/settings/string_setting_spec.rb @@ -0,0 +1,75 @@ +#!/usr/bin/env rspec +require 'spec_helper' + +require 'puppet/util/settings' +require 'puppet/util/settings/string_setting' + +describe Puppet::Util::Settings::StringSetting do + StringSetting = Puppet::Util::Settings::StringSetting + + before(:each) do + @test_setting_name = :test_setting + @test_setting_default = "my_crazy_default/$var" + @application_setting = "application/$var" + @application_defaults = { } + Puppet::Util::Settings::REQUIRED_APP_SETTINGS.each do |key| + @application_defaults[key] = "foo" + end + @application_defaults[:run_mode] = :user + @settings = Puppet::Util::Settings.new + @application_defaults.each { |k,v| @settings.define_settings :main, k => {:default=>"", :desc => "blah"} } + @settings.define_settings :main, :var => { :default => "interpolate!", + :type => :string, + :desc => "my var desc" }, + @test_setting_name => { :default => @test_setting_default, + :type => :string, + :desc => "my test desc" } + @test_setting = @settings.setting(@test_setting_name) + end + + describe "#default" do + describe "with no arguments" do + it "should return the setting default" do + @test_setting.default.should == @test_setting_default + end + + it "should be uninterpolated" do + @test_setting.default.should_not =~ /interpolate/ + end + end + + describe "checking application defaults first" do + describe "if application defaults set" do + before(:each) do + @settings.initialize_app_defaults @application_defaults.merge @test_setting_name => @application_setting + end + + it "should return the application-set default" do + @test_setting.default(true).should == @application_setting + end + + it "should be uninterpolated" do + @test_setting.default(true).should_not =~ /interpolate/ + end + + end + + describe "if application defaults not set" do + it "should return the regular default" do + @test_setting.default(true).should == @test_setting_default + end + + it "should be uninterpolated" do + @test_setting.default(true).should_not =~ /interpolate/ + end + end + end + end + + describe "#value" do + it "should be interpolated" do + @test_setting.value.should =~ /interpolate/ + end + end +end +