diff --git a/lib/puppet/agent.rb b/lib/puppet/agent.rb index 7d6a6e996..c34535200 100644 --- a/lib/puppet/agent.rb +++ b/lib/puppet/agent.rb @@ -1,102 +1,101 @@ require 'sync' -require 'puppet/external/event-loop' require 'puppet/application' # A general class for triggering a run of another # class. class Puppet::Agent require 'puppet/agent/locker' include Puppet::Agent::Locker require 'puppet/agent/disabler' include Puppet::Agent::Disabler attr_reader :client_class, :client, :splayed # Just so we can specify that we are "the" instance. def initialize(client_class) @splayed = false @client_class = client_class end def lockfile_path client_class.lockfile_path end def needing_restart? Puppet::Application.restart_requested? end # Perform a run with our client. def run(*args) if running? Puppet.notice "Run of #{client_class} already in progress; skipping" return end if disabled? Puppet.notice "Skipping run of #{client_class}; administratively disabled: #{disable_message}" return end result = nil block_run = Puppet::Application.controlled_run do splay with_client do |client| begin sync.synchronize { lock { result = client.run(*args) } } rescue SystemExit,NoMemoryError raise rescue Exception => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not run #{client_class}: #{detail}" end end true end Puppet.notice "Shutdown/restart in progress (#{Puppet::Application.run_status.inspect}); skipping run" unless block_run result end def stopping? Puppet::Application.stop_requested? end # Have we splayed already? def splayed? splayed end # Sleep when splay is enabled; else just return. def splay return unless Puppet[:splay] return if splayed? time = rand(Integer(Puppet[:splaylimit]) + 1) Puppet.info "Sleeping for #{time} seconds (splay is enabled)" sleep(time) @splayed = true end def sync @sync ||= Sync.new end private # Create and yield a client instance, keeping a reference # to it during the yield. def with_client begin @client = client_class.new rescue SystemExit,NoMemoryError raise rescue Exception => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not create instance of #{client_class}: #{detail}" return end yield @client ensure @client = nil end end diff --git a/lib/puppet/daemon.rb b/lib/puppet/daemon.rb index 5a0d4d5c3..d43da6fd5 100755 --- a/lib/puppet/daemon.rb +++ b/lib/puppet/daemon.rb @@ -1,196 +1,195 @@ require 'puppet' require 'puppet/util/pidlock' -require 'puppet/external/event-loop' 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] 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("/") begin $stdin.reopen "/dev/null" $stdout.reopen "/dev/null", "a" $stderr.reopen $stdout Puppet::Util::Log.reopen rescue => detail Puppet.err "Could not start #{Puppet[:name]}: #{detail}" Puppet::Util::secure_open("/tmp/daemonout", "w") { |f| f.puts "Could not start #{Puppet[:name]}: #{detail}" } exit(12) end end # Create a pidfile for our daemon, so we can be stopped and others # don't try to start. def create_pidfile Puppet::Util.synchronize_on(Puppet[:name],Sync::EX) do raise "Could not create PID file: #{pidfile}" unless Puppet::Util::Pidlock.new(pidfile).lock end end # 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::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 # 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 # 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 next_reparse = now + reparse_interval # 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 next_agent_run = now + agent_run_interval end # Finally, an interruptable able sleep until the next scheduled event. # We also 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 next_event > next_reparse and next_event = next_reparse next_event > next_agent_run and next_event = next_agent_run how_long = next_event - now how_long > 0 and select([], [], [], how_long) end end end diff --git a/lib/puppet/external/event-loop.rb b/lib/puppet/external/event-loop.rb deleted file mode 100644 index 476fb0ba3..000000000 --- a/lib/puppet/external/event-loop.rb +++ /dev/null @@ -1 +0,0 @@ -require "puppet/external/event-loop/event-loop" diff --git a/lib/puppet/external/event-loop/better-definers.rb b/lib/puppet/external/event-loop/better-definers.rb deleted file mode 100644 index ef1d44c53..000000000 --- a/lib/puppet/external/event-loop/better-definers.rb +++ /dev/null @@ -1,367 +0,0 @@ -## better-definers.rb --- better attribute and method definers -# Copyright (C) 2005 Daniel Brockman - -# This program is free software; you can redistribute it -# and/or modify it under the terms of the GNU General Public -# License as published by the Free Software Foundation; -# either version 2 of the License, or (at your option) any -# later version. - -# This file is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty -# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License for more details. - -# You should have received a copy of the GNU General Public -# License along with this program; if not, write to the Free -# Software Foundation, 51 Franklin Street, Fifth Floor, -# Boston, MA 02110-1301, USA. - -class Symbol - def predicate? - to_s.include? "?" end - def imperative? - to_s.include? "!" end - def writer? - to_s.include? "=" end - - def punctuated? - predicate? or imperative? or writer? end - def without_punctuation - to_s.delete("?!=").to_sym end - - def predicate - without_punctuation.to_s + "?" end - def imperative - without_punctuation.to_s + "!" end - def writer - without_punctuation.to_s + "=" end -end - -class Hash - def collect! (&block) - replace Hash[*collect(&block).flatten] - end - - def flatten - to_a.flatten - end -end - -module Kernel - def returning (value) - yield value ; value - end -end - -class Module - def define_hard_aliases (name_pairs) - for new_aliases, existing_name in name_pairs do - new_aliases.kind_of? Array or new_aliases = [new_aliases] - for new_alias in new_aliases do - alias_method(new_alias, existing_name) - end - end - end - - def define_soft_aliases (name_pairs) - for new_aliases, existing_name in name_pairs do - new_aliases.kind_of? Array or new_aliases = [new_aliases] - for new_alias in new_aliases do - class_eval %{def #{new_alias}(*args, &block) - #{existing_name}(*args, &block) end} - end - end - end - - define_soft_aliases \ - :define_hard_alias => :define_hard_aliases, - :define_soft_alias => :define_soft_aliases - - # This method lets you define predicates like :foo?, - # which will be defined to return the value of @foo. - def define_readers (*names) - for name in names.map { |x| x.to_sym } do - if name.punctuated? - # There's no way to define an efficient reader whose - # name is different from the instance variable. - class_eval %{def #{name} ; @#{name.without_punctuation} end} - else - # Use `attr_reader' to define an efficient method. - attr_reader(name) - end - end - end - - def writer_defined? (name) - method_defined?(name.to_sym.writer) - end - - # If you pass a predicate symbol :foo? to this method, it'll first - # define a regular writer method :foo, without a question mark. - # Then it'll define an imperative writer method :foo! as a shorthand - # for setting the property to true. - def define_writers (*names, &body) - for name in names.map { |x| x.to_sym } do - if block_given? - define_method(name.writer, &body) - else - attr_writer(name.without_punctuation) - end - if name.predicate? - class_eval %{def #{name.imperative} - self.#{name.writer} true end} - end - end - end - - define_soft_aliases \ - :define_reader => :define_readers, - :define_writer => :define_writers - - # We don't need a singular alias for `define_accessors', - # because it always defines at least two methods. - - def define_accessors (*names) - define_readers(*names) - define_writers(*names) - end - - def define_opposite_readers (name_pairs) - name_pairs.collect! { |k, v| [k.to_sym, v.to_sym] } - for opposite_name, name in name_pairs do - define_reader(name) unless method_defined?(name) - class_eval %{def #{opposite_name} ; not #{name} end} - end - end - - def define_opposite_writers (name_pairs) - name_pairs.collect! { |k, v| [k.to_sym, v.to_sym] } - for opposite_name, name in name_pairs do - define_writer(name) unless writer_defined?(name) - class_eval %{def #{opposite_name.writer} x - self.#{name.writer} !x end} - class_eval %{def #{opposite_name.imperative} - self.#{name.writer} false end} - end - end - - define_soft_aliases \ - :define_opposite_reader => :define_opposite_readers, - :define_opposite_writer => :define_opposite_writers - - def define_opposite_accessors (name_pairs) - define_opposite_readers name_pairs - define_opposite_writers name_pairs - end - - def define_reader_with_opposite (name_pair, &body) - name, opposite_name = name_pair.flatten.collect { |x| x.to_sym } - define_method(name, &body) - define_opposite_reader(opposite_name => name) - end - - def define_writer_with_opposite (name_pair, &body) - name, opposite_name = name_pair.flatten.collect { |x| x.to_sym } - define_writer(name, &body) - define_opposite_writer(opposite_name => name) - end - - public :define_method - - def define_methods (*names, &body) - names.each { |name| define_method(name, &body) } - end - - def define_private_methods (*names, &body) - define_methods(*names, &body) - names.each { |name| private name } - end - - def define_protected_methods (*names, &body) - define_methods(*names, &body) - names.each { |name| protected name } - end - - def define_private_method (name, &body) - define_method(name, &body) - private name - end - - def define_protected_method (name, &body) - define_method(name, &body) - protected name - end -end - -class ImmutableAttributeError < StandardError - def initialize (attribute=nil, message=nil) - super message - @attribute = attribute - end - - define_accessors :attribute - - def to_s - if @attribute and @message - "cannot change the value of `#@attribute': #@message" - elsif @attribute - "cannot change the value of `#@attribute'" - elsif @message - "cannot change the value of attribute: #@message" - else - "cannot change the value of attribute" - end - end -end - -class Module - # Guard each of the specified attributes by replacing the writer - # method with a proxy that asks the supplied block before proceeding - # with the change. - # - # If it's okay to change the attribute, the block should return - # either nil or the symbol :mutable. If it isn't okay, the block - # should return a string saying why the attribute can't be changed. - # If you don't want to provide a reason, you can have the block - # return just the symbol :immutable. - def guard_writers(*names, &predicate) - for name in names.map { |x| x.to_sym } do - define_hard_alias("__unguarded_#{name.writer}" => name.writer) - define_method(name.writer) do |new_value| - case result = predicate.call - when :mutable, nil - __send__("__unguarded_#{name.writer}", new_value) - when :immutable - raise ImmutableAttributeError.new(name) - else - raise ImmutableAttributeError.new(name, result) - end - end - end - end - - def define_guarded_writers (*names, &block) - define_writers(*names) - guard_writers(*names, &block) - end - - define_soft_alias :guard_writer => :guard_writers - define_soft_alias :define_guarded_writer => :define_guarded_writers -end - -if __FILE__ == $0 - require "test/unit" - - class DefineAccessorsTest < Test::Unit::TestCase - def setup - @X = Class.new - @Y = Class.new @X - @x = @X.new - @y = @Y.new - end - - def test_define_hard_aliases - @X.define_method(:foo) { 123 } - @X.define_method(:baz) { 321 } - @X.define_hard_aliases :bar => :foo, :quux => :baz - assert_equal @x.foo, 123 - assert_equal @x.bar, 123 - assert_equal @y.foo, 123 - assert_equal @y.bar, 123 - assert_equal @x.baz, 321 - assert_equal @x.quux, 321 - assert_equal @y.baz, 321 - assert_equal @y.quux, 321 - @Y.define_method(:foo) { 456 } - assert_equal @y.foo, 456 - assert_equal @y.bar, 123 - @Y.define_method(:quux) { 654 } - assert_equal @y.baz, 321 - assert_equal @y.quux, 654 - end - - def test_define_soft_aliases - @X.define_method(:foo) { 123 } - @X.define_method(:baz) { 321 } - @X.define_soft_aliases :bar => :foo, :quux => :baz - assert_equal @x.foo, 123 - assert_equal @x.bar, 123 - assert_equal @y.foo, 123 - assert_equal @y.bar, 123 - assert_equal @x.baz, 321 - assert_equal @x.quux, 321 - assert_equal @y.baz, 321 - assert_equal @y.quux, 321 - @Y.define_method(:foo) { 456 } - assert_equal @y.foo, @y.bar, 456 - @Y.define_method(:quux) { 654 } - assert_equal @y.baz, 321 - assert_equal @y.quux, 654 - end - - def test_define_readers - @X.define_readers :foo, :bar - assert !@x.respond_to?(:foo=) - assert !@x.respond_to?(:bar=) - @x.instance_eval { @foo = 123 ; @bar = 456 } - assert_equal @x.foo, 123 - assert_equal @x.bar, 456 - @X.define_readers :baz?, :quux? - assert !@x.respond_to?(:baz=) - assert !@x.respond_to?(:quux=) - @x.instance_eval { @baz = false ; @quux = true } - assert !@x.baz? - assert @x.quux? - end - - def test_define_writers - assert !@X.writer_defined?(:foo) - assert !@X.writer_defined?(:bar) - @X.define_writers :foo, :bar - assert @X.writer_defined?(:foo) - assert @X.writer_defined?(:bar) - assert @X.writer_defined?(:foo=) - assert @X.writer_defined?(:bar=) - assert @X.writer_defined?(:foo?) - assert @X.writer_defined?(:bar?) - assert !@x.respond_to?(:foo) - assert !@x.respond_to?(:bar) - @x.foo = 123 - @x.bar = 456 - assert_equal @x.instance_eval { @foo }, 123 - assert_equal @x.instance_eval { @bar }, 456 - @X.define_writers :baz?, :quux? - assert !@x.respond_to?(:baz?) - assert !@x.respond_to?(:quux?) - @x.baz = true - @x.quux = false - assert_equal @x.instance_eval { @baz }, true - assert_equal @x.instance_eval { @quux }, false - end - - def test_define_accessors - @X.define_accessors :foo, :bar - @x.foo = 123 ; @x.bar = 456 - assert_equal @x.foo, 123 - assert_equal @x.bar, 456 - end - - def test_define_opposite_readers - @X.define_opposite_readers :foo? => :bar?, :baz? => :quux? - assert !@x.respond_to?(:foo=) - assert !@x.respond_to?(:bar=) - assert !@x.respond_to?(:baz=) - assert !@x.respond_to?(:quux=) - @x.instance_eval { @bar = true ; @quux = false } - assert !@x.foo? - assert @x.bar? - assert @x.baz? - assert !@x.quux? - end - - def test_define_opposite_writers - @X.define_opposite_writers :foo? => :bar?, :baz => :quux - end - end -end diff --git a/lib/puppet/external/event-loop/event-loop.rb b/lib/puppet/external/event-loop/event-loop.rb deleted file mode 100644 index 3b40f6e71..000000000 --- a/lib/puppet/external/event-loop/event-loop.rb +++ /dev/null @@ -1,355 +0,0 @@ -## event-loop.rb --- high-level IO multiplexer -# Copyright (C) 2005 Daniel Brockman - -# This program is free software; you can redistribute it -# and/or modify it under the terms of the GNU General Public -# License as published by the Free Software Foundation; -# either version 2 of the License, or (at your option) any -# later version. - -# This file is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty -# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License for more details. - -# You should have received a copy of the GNU General Public -# License along with this program; if not, write to the Free -# Software Foundation, 51 Franklin Street, Fifth Floor, -# Boston, MA 02110-1301, USA. - -require "puppet/external/event-loop/better-definers" -require "puppet/external/event-loop/signal-system" - -require "fcntl" - -class EventLoop - include SignalEmitter - - IO_STATES = [:readable, :writable, :exceptional] - - class << self - def default ; @default ||= new end - def default= x ; @default = x end - - def current - Thread.current["event-loop::current"] || default end - def current= x - Thread.current["event-loop::current"] = x end - - def with_current (new) - if current == new - yield - else - begin - old = self.current - self.current = new - yield - ensure - self.current = old - end - end - end - - def method_missing (name, *args, &block) - if current.respond_to? name - current.__send__(name, *args, &block) - else - super - end - end - end - - define_signals :before_sleep, :after_sleep - - def initialize - @running = false - @awake = false - @wakeup_time = nil - @timers = [] - - @io_arrays = [[], [], []] - @ios = Hash.new do |h, k| raise ArgumentError, - "invalid IO event: #{k}", caller(2) end - IO_STATES.each_with_index { |x, i| @ios[x] = @io_arrays[i] } - - @notify_src, @notify_snk = IO.pipe - - # prevent file descriptor leaks - if @notify_src.respond_to?(:fcntl) and defined?(Fcntl) and defined?(Fcntl::F_SETFD) and defined?(Fcntl::FD_CLOEXEC) - @notify_src.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) - @notify_snk.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) - end - - @notify_src.will_block = false - @notify_snk.will_block = false - - # Each time a byte is sent through the notification pipe - # we need to read it, or IO.select will keep returning. - monitor_io(@notify_src, :readable) - @notify_src.extend(Watchable) - @notify_src.on_readable do - begin - @notify_src.sysread(256) - rescue Errno::EAGAIN - # The pipe wasn't readable after all. - end - end - end - - define_opposite_accessors \ - :stopped? => :running?, - :sleeping? => :awake? - - def run - if block_given? - thread = Thread.new { run } - yield ; quit ; thread.join - else - running! - iterate while running? - end - ensure - quit - end - - def iterate (user_timeout=nil) - t1, t2 = user_timeout, max_timeout - timeout = t1 && t2 ? [t1, t2].min : t1 || t2 - select(timeout).zip(IO_STATES) do |ios, state| - ios.each { |x| x.signal(state) } if ios - end - end - - private - - def select (timeout) - @wakeup_time = timeout ? Time.now + timeout : nil - # puts "waiting: #{timeout} seconds" - signal :before_sleep ; sleeping! - IO.select(*@io_arrays + [timeout]) || [] - ensure - awake! ; signal :after_sleep - @timers.each { |x| x.sound_alarm if x.ready? } - end - - public - - def quit ; stopped! ; wake_up ; self end - - def monitoring_io? (io, event) - @ios[event].include? io end - def monitoring_timer? (timer) - @timers.include? timer end - - def monitor_io (io, *events) - for event in events do - @ios[event] << io ; wake_up unless monitoring_io?(io, event) - end - end - - def monitor_timer (timer) - @timers << timer unless monitoring_timer? timer - end - - def check_timer (timer) - wake_up if timer.end_time < @wakeup_time - end - - def ignore_io (io, *events) - events = IO_STATES if events.empty? - for event in events do - wake_up if @ios[event].delete(io) - end - end - - def ignore_timer (timer) - # Don't need to wake up for this. - @timers.delete(timer) - end - - def max_timeout - return nil if @timers.empty? - [@timers.collect { |x| x.time_left }.min, 0].max - end - - def wake_up - @notify_snk.write('.') if sleeping? - end -end - -class Symbol - def io_state? - EventLoop::IO_STATES.include? self - end -end - -module EventLoop::Watchable - include SignalEmitter - - define_signals :readable, :writable, :exceptional - - def monitor_events (*events) - EventLoop.monitor_io(self, *events) end - def ignore_events (*events) - EventLoop.ignore_io(self, *events) end - - define_soft_aliases \ - :monitor_event => :monitor_events, - :ignore_event => :ignore_events - - def close ; super - ignore_events end - def close_read ; super - ignore_event :readable end - def close_write ; super - ignore_event :writable end - - module Automatic - include EventLoop::Watchable - - def add_signal_handler (name, &handler) super - monitor_event(name) if name.io_state? - end - - def remove_signal_handler (name, handler) super - if @signal_handlers[name].empty? - ignore_event(name) if name.io_state? - end - end - end -end - -class IO - def on_readable &block - extend EventLoop::Watchable::Automatic - on_readable(&block) - end - - def on_writable &block - extend EventLoop::Watchable::Automatic - on_writable(&block) - end - - def on_exceptional &block - extend EventLoop::Watchable::Automatic - on_exceptional(&block) - end - - def will_block? - if respond_to?(:fcntl) and defined?(Fcntl) and defined?(Fcntl::F_GETFL) and defined?(Fcntl::O_NONBLOCK) - fcntl(Fcntl::F_GETFL, 0) & Fcntl::O_NONBLOCK == 0 - end - end - - def will_block= (wants_blocking) - if respond_to?(:fcntl) and defined?(Fcntl) and defined?(Fcntl::F_GETFL) and defined?(Fcntl::O_NONBLOCK) - flags = fcntl(Fcntl::F_GETFL, 0) - if wants_blocking - flags &= ~Fcntl::O_NONBLOCK - else - flags |= Fcntl::O_NONBLOCK - end - fcntl(Fcntl::F_SETFL, flags) - end - end -end - -class EventLoop::Timer - include SignalEmitter - - DEFAULT_INTERVAL = 0.0 - DEFAULT_TOLERANCE = 0.001 - - def initialize (options={}, &handler) - @running = false - @start_time = nil - - options = { :interval => options } if options.kind_of? Numeric - - if options[:interval] - @interval = options[:interval].to_f - else - @interval = DEFAULT_INTERVAL - end - - if options[:tolerance] - @tolerance = options[:tolerance].to_f - elsif DEFAULT_TOLERANCE < @interval - @tolerance = DEFAULT_TOLERANCE - else - @tolerance = 0.0 - end - - @event_loop = options[:event_loop] || EventLoop.current - - if block_given? - add_signal_handler(:alarm, &handler) - start unless options[:start?] == false - else - start if options[:start?] - end - end - - define_readers :interval, :tolerance - define_signal :alarm - - def stopped? ; @start_time == nil end - def running? ; @start_time != nil end - - def interval= (new_interval) - old_interval = @interval - @interval = new_interval - @event_loop.check_timer(self) if new_interval < old_interval - end - - def end_time - @start_time + @interval end - def time_left - end_time - Time.now end - def ready? - time_left <= @tolerance end - - def restart - @start_time = Time.now - end - - def sound_alarm - signal :alarm - restart if running? - end - - def start - @start_time = Time.now - @event_loop.monitor_timer(self) - end - - def stop - @start_time = nil - @event_loop.ignore_timer(self) - end -end - -if __FILE__ == $0 - require "test/unit" - - class TimerTest < Test::Unit::TestCase - def setup - @timer = EventLoop::Timer.new(:interval => 0.001) - end - - def test_timer - @timer.on_alarm do - puts "[#{@timer.time_left} seconds left after alarm]" - EventLoop.quit - end - 8.times do - t0 = Time.now - @timer.start ; EventLoop.run - t1 = Time.now - assert(t1 - t0 > @timer.interval - @timer.tolerance) - end - end - end -end - -## event-loop.rb ends here. diff --git a/lib/puppet/external/event-loop/signal-system.rb b/lib/puppet/external/event-loop/signal-system.rb deleted file mode 100644 index d3c924bf8..000000000 --- a/lib/puppet/external/event-loop/signal-system.rb +++ /dev/null @@ -1,218 +0,0 @@ -## signal-system.rb --- simple intra-process signal system -# Copyright (C) 2005 Daniel Brockman - -# This program is free software; you can redistribute it -# and/or modify it under the terms of the GNU General Public -# License as published by the Free Software Foundation; -# either version 2 of the License, or (at your option) any -# later version. - -# This file is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty -# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License for more details. - -# You should have received a copy of the GNU General Public -# License along with this program; if not, write to the Free -# Software Foundation, 51 Franklin Street, Fifth Floor, -# Boston, MA 02110-1301, USA. - -require "puppet/external/event-loop/better-definers" - -module SignalEmitterModule - def self.extended (object) - if object.kind_of? Module and not object < SignalEmitter - if object.respond_to? :fcall - # This is the way to call private methods - # in Ruby 1.9 as of November 16. - object.fcall :include, SignalEmitter - else - object.__send__ :include, SignalEmitter - end - end - end - - def define_signal (name, slot=:before, &body) - # Can't use `define_method' and take a block pre-1.9. - class_eval %{ def on_#{name} &block - add_signal_handler(:#{name}, &block) end } - define_signal_handler(name, :before, &lambda {|*a|}) - define_signal_handler(name, :after, &lambda {|*a|}) - define_signal_handler(name, slot, &body) if block_given? - end - - def define_signals (*names, &body) - names.each { |x| define_signal(x, &body) } - end - - def define_signal_handler (name, slot=:before, &body) - case slot - when :before - define_protected_method "handle_#{name}", &body - when :after - define_protected_method "after_handle_#{name}", &body - else - raise ArgumentError, "invalid slot `#{slot.inspect}'; " + - "should be `:before' or `:after'", caller(1) - end - end -end - -# This is an old name for the same thing. -SignalEmitterClass = SignalEmitterModule - -module SignalEmitter - def self.included (includer) - includer.extend SignalEmitterClass if not includer.kind_of? SignalEmitterClass - end - - def __maybe_initialize_signal_emitter - @signal_handlers ||= Hash.new { |h, k| h[k] = Array.new } - @allow_dynamic_signals ||= false - end - - define_accessors :allow_dynamic_signals? - - def add_signal_handler (name, &handler) - __maybe_initialize_signal_emitter - @signal_handlers[name] << handler - handler - end - - define_soft_aliases [:on, :on_signal] => :add_signal_handler - - def remove_signal_handler (name, handler) - __maybe_initialize_signal_emitter - @signal_handlers[name].delete(handler) - end - - def __signal__ (name, *args, &block) - __maybe_initialize_signal_emitter - respond_to? "on_#{name}" or allow_dynamic_signals? or - fail "undefined signal `#{name}' for #{self}:#{self.class}" - __send__("handle_#{name}", *args, &block) if - respond_to? "handle_#{name}" - @signal_handlers[name].each { |x| x.call(*args, &block) } - __send__("after_handle_#{name}", *args, &block) if - respond_to? "after_handle_#{name}" - end - - define_soft_alias :signal => :__signal__ -end - -# This module is indended to be a convenience mixin to be used by -# classes whose objects need to observe foreign signals. That is, -# if you want to observe some signals coming from an object, *you* -# should mix in this module. -# -# You cannot use this module at two different places of the same -# inheritance chain to observe signals coming from the same object. -# -# XXX: This has not seen much use, and I'd like to provide a -# better solution for the problem in the future. -module SignalObserver - def __maybe_initialize_signal_observer - @observed_signals ||= Hash.new do |signals, object| - signals[object] = Hash.new do |handlers, name| - handlers[name] = Array.new - end - end - end - - def observe_signal (subject, name, &handler) - __maybe_initialize_signal_observer - @observed_signals[subject][name] << handler - subject.add_signal_handler(name, &handler) - end - - def map_signals (source, pairs={}) - pairs.each do |src_name, dst_name| - observe_signal(source, src_name) do |*args| - __signal__(dst_name, *args) - end - end - end - - def absorb_signals (subject, *names) - names.each do |name| - observe_signal(subject, name) do |*args| - __signal__(name, *args) - end - end - end - - define_soft_aliases \ - :map_signal => :map_signals, - :absorb_signal => :absorb_signals - - def ignore_signal (subject, name) - __maybe_initialize_signal_observer - __ignore_signal_1(subject, name) - @observed_signals.delete(subject) if - @observed_signals[subject].empty? - end - - def ignore_signals (subject, *names) - __maybe_initialize_signal_observer - names = @observed_signals[subject] if names.empty? - names.each { |x| __ignore_signal_1(subject, x) } - end - - private - - def __ignore_signal_1(subject, name) - @observed_signals[subject][name].each do |handler| - subject.remove_signal_handler(name, handler) end - @observed_signals[subject].delete(name) - end -end - -if __FILE__ == $0 - require "test/unit" - class SignalEmitterTest < Test::Unit::TestCase - class X - include SignalEmitter - define_signal :foo - end - - def setup - @x = X.new - end - - def test_on_signal - moomin = 0 - @x.on_signal(:foo) { moomin = 1 } - @x.signal :foo - assert moomin == 1 - end - - def test_on_foo - moomin = 0 - @x.on_foo { moomin = 1 } - @x.signal :foo - assert moomin == 1 - end - - def test_multiple_on_signal - moomin = 0 - @x.on_signal(:foo) { moomin += 1 } - @x.on_signal(:foo) { moomin += 2 } - @x.on_signal(:foo) { moomin += 4 } - @x.on_signal(:foo) { moomin += 8 } - @x.signal :foo - assert moomin == 15 - end - - def test_multiple_on_foo - moomin = 0 - @x.on_foo { moomin += 1 } - @x.on_foo { moomin += 2 } - @x.on_foo { moomin += 4 } - @x.on_foo { moomin += 8 } - @x.signal :foo - assert moomin == 15 - end - end -end - -## application-signals.rb ends here. diff --git a/lib/puppet/util/settings.rb b/lib/puppet/util/settings.rb index 7406af8c6..e41bf5fa7 100644 --- a/lib/puppet/util/settings.rb +++ b/lib/puppet/util/settings.rb @@ -1,925 +1,924 @@ require 'puppet' require 'sync' require 'getoptlong' -require 'puppet/external/event-loop' require 'puppet/util/loadedfile' # The class for handling configuration files. class Puppet::Util::Settings include Enumerable require 'puppet/util/settings/setting' require 'puppet/util/settings/file_setting' require 'puppet/util/settings/boolean_setting' attr_accessor :file attr_reader :timer ReadOnly = [:run_mode, :name] # 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(exceptcli = false) @sync.synchronize do unsafe_clear(exceptcli) end end # Remove all set values, potentially skipping cli values. def unsafe_clear(exceptcli = false) @values.each do |name, values| @values.delete(name) unless exceptcli and name == :cli end # Don't clear the 'used' in this case, since it's a config file reparse, # and we want to retain this info. @used = [] unless exceptcli @cache.clear end # This is mostly just used for testing. def clearused @cache.clear @used = [] end # Do variable interpolation on the value. def convert(value, environment = nil) return value unless value 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::DevError, "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 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 Puppet.run_mode.name end # 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 raise "No :config setting defined; cannot parse unknown config file" unless self[:config] @sync.synchronize do unsafe_parse(self[:config]) end end # Unsafely parse the file -- this isn't thread-safe and causes plenty of problems if used directly. def unsafe_parse(file) return unless FileTest.exist?(file) begin data = parse_file(file) rescue => details puts details.backtrace if Puppet[:trace] Puppet.err "Could not parse #{file}: #{details}" return end unsafe_clear(true) 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 # 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. def newsetting(hash) klass = nil hash[:section] = hash[:section].to_sym if hash[:section] if type = hash[:type] unless klass = {:setting => Setting, :file => FileSetting, :boolean => BooleanSetting}[type] raise ArgumentError, "Invalid setting type '#{type}'" end hash.delete(:type) else case hash[:default] when true, false, "true", "false" klass = BooleanSetting when /^\$\w+\//, /^\//, /^\w:\// klass = FileSetting when String, Integer, Float # nothing klass = Setting else raise ArgumentError, "Invalid value '#{hash[:default].inspect}' for #{hash[:name]}" end 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 def file return @file if @file if path = self[:config] and FileTest.exist?(path) @file = Puppet::Util::LoadedFile.new(path) end end # Reparse our config file, if necessary. def reparse if file and file.changed? Puppet.notice "Reparsing #{file.file}" parse reuse end end 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, :mutable_defaults] else [:cli, :memory, :run_mode, :main, :mutable_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) if not defined?(@app_names) require 'puppet/util/command_line' command_line = Puppet::Util::CommandLine.new @app_names = Puppet::Util::CommandLine::LegacyName.inject({}) do |hash, pair| app, legacy = pair command_line.require_application app hash[legacy.to_sym] = Puppet::Application.find(app).run_mode.name hash end end if new_type = @app_names[type] Puppet.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 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 != :mutable_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 # 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 setting.setbycli = true if type == :cli @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 value end # Set a bunch of defaults in a given section. The sections are actually pretty # pointless, but they help break things up a bit, anyway. def setdefaults(section, defs) section = section.to_sym call = [] defs.each { |name, hash| if hash.is_a? Array unless hash.length == 2 raise ArgumentError, "Defaults specified as an array must contain only the default value and the decription" end tmp = hash hash = {} [:default, :desc].zip(tmp).each { |p,v| hash[p] = v } end 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.values.find_all { |value| value.is_a?(FileSetting) }.each do |file| next unless (sections.nil? or sections.include?(file.section)) next unless resource = file.to_resource next if catalog.resource(resource.ref) 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 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 puts detail.backtrace if Puppet[:trace] Puppet.err "Could not create resources for managing Puppet's files and directories in sections #{sections.inspect}: #{detail}" # We need some way to get rid of any resources created during the catalog creation # but not cleaned up. return 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 = catch :foundval do 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 throw :foundval, @values[source][param] if @values[source].include?(param) end end throw :foundval, nil end # If we didn't get a value, use the default val = @config[param].default if val.nil? val end # 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) 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) if param == :code # if we interpolate code, all hell breaks loose. return val end # Convert it if necessary val = convert(val, environment) # 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 # Create the transportable objects for users and groups. 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 { |line| count += 1 case line when /^\s*\[(\w+)\]\s*$/ section = $1.intern # Section names # 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 } 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 end diff --git a/test/other/puppet.rb b/test/other/puppet.rb index 9fb53ddba..871860f08 100755 --- a/test/other/puppet.rb +++ b/test/other/puppet.rb @@ -1,86 +1,85 @@ #!/usr/bin/env ruby require File.expand_path(File.dirname(__FILE__) + '/../lib/puppettest') require 'puppet' require 'puppettest' # Test the different features of the main puppet module class TestPuppetModule < Test::Unit::TestCase include PuppetTest - include SignalObserver def mkfakeclient Class.new(Puppet::Network::Client) do def initialize end def runnow Puppet.info "fake client has run" end end end def mktestclass Class.new do def initialize(file) @file = file end def started? FileTest.exists?(@file) end def start File.open(@file, "w") do |f| f.puts "" end end def shutdown File.unlink(@file) end end end def test_path oldpath = ENV["PATH"] cleanup do ENV["PATH"] = oldpath end newpath = oldpath + ":/something/else" assert_nothing_raised do Puppet[:path] = newpath end assert_equal(newpath, ENV["PATH"]) end def test_libdir oldlibs = $LOAD_PATH.dup cleanup do $LOAD_PATH.each do |dir| $LOAD_PATH.delete(dir) unless oldlibs.include?(dir) end end one = tempfile two = tempfile Dir.mkdir(one) Dir.mkdir(two) # Make sure setting the libdir gets the dir added to $LOAD_PATH assert_nothing_raised do Puppet[:libdir] = one end assert($LOAD_PATH.include?(one), "libdir was not added") # Now change it, make sure it gets added and the old one gets # removed assert_nothing_raised do Puppet[:libdir] = two end assert($LOAD_PATH.include?(two), "libdir was not added") assert(! $LOAD_PATH.include?(one), "old libdir was not removed") end end