summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xbin/puppetd18
-rw-r--r--lib/puppet.rb61
-rw-r--r--lib/puppet/client.rb594
-rw-r--r--lib/puppet/client/ca.rb17
-rw-r--r--lib/puppet/client/dipper.rb78
-rw-r--r--lib/puppet/client/file.rb20
-rw-r--r--lib/puppet/client/log.rb17
-rw-r--r--lib/puppet/client/master.rb244
-rw-r--r--lib/puppet/client/proxy.rb27
-rw-r--r--lib/puppet/client/status.rb7
-rwxr-xr-xlib/puppet/daemon.rb3
-rw-r--r--lib/puppet/event-loop.rb1
-rw-r--r--lib/puppet/event-loop/better-definers.rb367
-rw-r--r--lib/puppet/event-loop/event-loop.rb355
-rw-r--r--lib/puppet/event-loop/signal-system.rb220
-rwxr-xr-xlib/puppet/filetype.rb4
-rw-r--r--lib/puppet/networkclient.rb141
-rw-r--r--lib/puppet/parameter.rb12
-rwxr-xr-xlib/puppet/sslcertificates.rb574
-rw-r--r--lib/puppet/sslcertificates/ca.rb286
-rw-r--r--lib/puppet/sslcertificates/certificate.rb283
-rw-r--r--lib/puppet/storage.rb12
-rw-r--r--lib/puppet/transaction.rb6
-rw-r--r--lib/puppet/type.rb50
-rwxr-xr-xlib/puppet/type/package/rpm.rb7
-rw-r--r--lib/puppet/type/pfile.rb2
-rwxr-xr-xlib/puppet/type/schedule.rb2
-rw-r--r--lib/puppet/type/service.rb1
-rw-r--r--lib/puppet/util.rb18
-rwxr-xr-xtest/executables/puppetd.rb1
-rwxr-xr-xtest/language/snippets.rb7
31 files changed, 2232 insertions, 1203 deletions
diff --git a/bin/puppetd b/bin/puppetd
index 6d2b804b0..c082e2a80 100755
--- a/bin/puppetd
+++ b/bin/puppetd
@@ -259,11 +259,23 @@ end
client.setcerts
trap(:INT) {
- exit(1)
+ client.shutdown
}
-client.run(onetime)
+if onetime
+ begin
+ client.run
+ rescue => detail
+ Puppet.err detail.to_s
+ if Puppet[:debug]
+ puts detail.backtrace
+ end
+ end
+else
+ client.start
-#Puppet.join
+ # Mmm, hackish
+ Puppet.start
+end
# $Id$
diff --git a/lib/puppet.rb b/lib/puppet.rb
index b092dd7e2..2e1a88e66 100644
--- a/lib/puppet.rb
+++ b/lib/puppet.rb
@@ -1,4 +1,5 @@
require 'singleton'
+require 'puppet/event-loop'
require 'puppet/log'
require 'puppet/util'
@@ -85,7 +86,7 @@ PUPPETVERSION = '0.11.2'
:statedir => [:puppetvar, "state"],
:rundir => [:puppetvar, "run"],
- # then the files},
+ # then the files,
:manifestdir => [:puppetconf, "manifests"],
:manifest => [:manifestdir, "site.pp"],
:localconfig => [:puppetconf, "localconfig"],
@@ -109,6 +110,7 @@ PUPPETVERSION = '0.11.2'
:parseonly => false,
:puppetport => 8139,
:masterport => 8140,
+ :runinterval => 60,
}
# If we're running the standalone puppet process as a non-root user,
@@ -181,47 +183,30 @@ PUPPETVERSION = '0.11.2'
end
end
- def self.asuser(user)
- # FIXME this should use our user object, since it already knows how
- # to find users and such
- require 'etc'
-
- begin
- obj = Etc.getpwnam(user)
- rescue ArgumentError
- raise Puppet::Error, "User %s not found"
- end
-
- uid = obj.uid
-
- olduid = nil
- if Process.uid != uid
- olduid = Process.uid
- Process.euid = uid
- end
-
- retval = yield
-
-
- if olduid
- Process.euid = olduid
- end
-
- return retval
+ # Start our event loop. This blocks, waiting for someone, somewhere,
+ # to generate events of some kind.
+ def self.start
+ #Puppet.info "Starting loop"
+ EventLoop.current.run
end
- def self.join
- return unless defined? @threads
- @threads.each { |th| th.join }
- end
-
- def self.newthread
- @threads ||= []
- @threads << Thread.new {
- yield
- }
+ # Create the timer that our different objects (uh, mostly the client)
+ # check.
+ def self.timer
+ unless defined? @timer
+ #Puppet.info "Interval is %s" % Puppet[:runinterval]
+ #@timer = EventLoop::Timer.new(:interval => Puppet[:runinterval])
+ @timer = EventLoop::Timer.new(
+ :interval => Puppet[:runinterval],
+ :tolerance => 1,
+ :start? => true
+ )
+ EventLoop.current.monitor_timer @timer
+ end
+ @timer
end
+ # Store a new default value.
def self.setdefault(param,value)
if value.is_a?(Array)
if value[0].is_a?(Symbol)
diff --git a/lib/puppet/client.rb b/lib/puppet/client.rb
index 27c7cef72..c6061e0ef 100644
--- a/lib/puppet/client.rb
+++ b/lib/puppet/client.rb
@@ -1,155 +1,22 @@
# the available clients
require 'puppet'
-require 'puppet/sslcertificates'
-require 'puppet/type'
-require 'facter'
-require 'openssl'
-require 'puppet/transaction'
-require 'puppet/transportable'
-require 'puppet/metric'
-require 'puppet/daemon'
-require 'puppet/server'
-require 'puppet/base64'
-
-$noclientnetworking = false
-begin
- require 'webrick'
- require 'cgi'
- require 'xmlrpc/client'
- require 'xmlrpc/server'
- require 'yaml'
-rescue LoadError => detail
- $noclientnetworking = detail
- raise Puppet::Error, "You must have the Ruby XMLRPC, CGI, and Webrick libraries installed"
-end
+require 'puppet/networkclient'
module Puppet
- class NetworkClientError < RuntimeError; end
- class ClientError < RuntimeError; end
- #---------------------------------------------------------------
- if $noclientnetworking
- Puppet.err "Could not load client network libs: %s" % $noclientnetworking
- else
- class NetworkClient < XMLRPC::Client
- #include Puppet::Daemon
-
- # add the methods associated with each namespace
- Puppet::Server::Handler.each { |handler|
- interface = handler.interface
- namespace = interface.prefix
-
- interface.methods.each { |ary|
- method = ary[0]
- Puppet.info "Defining %s.%s" % [namespace, method]
- self.send(:define_method,method) { |*args|
- #Puppet.info "Calling %s" % method
- #Puppet.info "peer cert is %s" % @http.peer_cert
- #Puppet.info "cert is %s" % @http.cert
- begin
- call("%s.%s" % [namespace, method.to_s],*args)
- rescue OpenSSL::SSL::SSLError => detail
- #Puppet.err "Could not call %s.%s: Untrusted certificates" %
- # [namespace, method]
- raise NetworkClientError,
- "Certificates were not trusted"
- rescue XMLRPC::FaultException => detail
- #Puppet.err "Could not call %s.%s: %s" %
- # [namespace, method, detail.faultString]
- #raise NetworkClientError,
- # "XMLRPC Error: %s" % detail.faultString
- raise NetworkClientError, detail.faultString
- rescue Errno::ECONNREFUSED => detail
- msg = "Could not connect to %s on port %s" % [@host, @port]
- #Puppet.err msg
- raise NetworkClientError, msg
- rescue SocketError => detail
- Puppet.err "Could not find server %s" % @puppetserver
- exit(12)
- rescue => detail
- Puppet.err "Could not call %s.%s: %s" %
- [namespace, method, detail.inspect]
- #raise NetworkClientError.new(detail.to_s)
- raise
- end
- }
- }
- }
-
- def ca_file=(cafile)
- @http.ca_file = cafile
- store = OpenSSL::X509::Store.new
- cacert = OpenSSL::X509::Certificate.new(
- File.read(cafile)
- )
- store.add_cert(cacert)
- store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
- @http.cert_store = store
- end
-
- def cert=(cert)
- #Puppet.debug "Adding certificate"
- @http.cert = cert
- @http.verify_mode = OpenSSL::SSL::VERIFY_PEER
- end
-
- def key=(key)
- @http.key = key
- end
-
- def initialize(hash)
- hash[:Path] ||= "/RPC2"
- hash[:Server] ||= "localhost"
- hash[:Port] ||= Puppet[:masterport]
-
- @puppetserver = hash[:Server]
-
- super(
- hash[:Server],
- hash[:Path],
- hash[:Port],
- nil, # proxy_host
- nil, # proxy_port
- nil, # user
- nil, # password
- true # use_ssl
- )
-
- if hash[:Certificate]
- self.cert = hash[:Certificate]
- else
- Puppet.err "No certificate; running with reduced functionality."
- end
-
- if hash[:Key]
- self.key = hash[:Key]
- end
-
- if hash[:CAFile]
- self.ca_file = hash[:CAFile]
- end
-
- # from here, i need to add the key, cert, and ca cert
- # and reorgize how i start the client
- end
-
- def local
- false
- end
- end
- end
-
# FIXME this still isn't a good design, because none of the handlers overlap
# so i could just as easily include them all in the main module
# but at least it's better organized for now
class Client
include Puppet
+ include SignalObserver
# FIXME the cert stuff should only come up with networking, so it
# should be in the network client, not the normal client
# but if i do that, it's hard to tell whether the certs have been initialized
include Puppet::Daemon
attr_reader :local, :secureinit
+ attr_accessor :schedule, :lastrun
class << self
attr_reader :drivername
@@ -216,436 +83,69 @@ module Puppet
end
end
- def setcerts
- @driver.cert = @cert
- @driver.key = @key
- @driver.ca_file = @cacertfile
- end
-
- class MasterClient < Puppet::Client
- @drivername = :Master
-
- def self.facts
- facts = {}
- Facter.each { |name,fact|
- facts[name] = fact.downcase
- }
-
- facts
- end
-
- # This method is how the client receives the tree of Transportable
- # objects. For now, just descend into the tree and perform and
- # necessary manipulations.
- def apply
- dostorage()
- unless defined? @objects
- raise Puppet::Error, "Cannot apply; objects not defined"
- end
-
- #Puppet.err :yay
- #p @objects
- #Puppet.err :mark
- #@objects = @objects.to_type
- # this is a gross hack... but i don't see a good way around it
- # set all of the variables to empty
- Puppet::Transaction.init
-
- # For now we just evaluate the top-level object, but eventually
- # there will be schedules and such associated with each object,
- # and probably with the container itself.
- transaction = @objects.evaluate
- #transaction = Puppet::Transaction.new(objects)
- transaction.toplevel = true
- begin
- transaction.evaluate
- rescue Puppet::Error => detail
- Puppet.err "Could not apply complete configuration: %s" %
- detail
- rescue => detail
- Puppet.err "Found a bug: %s" % detail
- if Puppet[:debug]
- puts detail.backtrace
- end
- ensure
- Puppet::Storage.store
- end
- Puppet::Metric.gather
- Puppet::Metric.tally
- if Puppet[:rrdgraph] == true
- Metric.store
- Metric.graph
- end
-
- return transaction
- end
-
- # Cache the config
- def cache(text)
- Puppet.info "Caching configuration at %s" % self.cachefile
- confdir = File.dirname(Puppet[:localconfig])
- unless FileTest.exists?(confdir)
- Puppet.recmkdir(confdir, 0770)
- end
- File.open(self.cachefile + ".tmp", "w", 0660) { |f|
- f.print text
- }
- File.rename(self.cachefile + ".tmp", self.cachefile)
- end
-
- def cachefile
- unless defined? @cachefile
- @cachefile = Puppet[:localconfig] + ".yaml"
- end
- @cachefile
- end
-
- # Initialize and load storage
- def dostorage
- begin
- Puppet::Storage.init
- Puppet::Storage.load
- rescue => detail
- Puppet.err "Corrupt state file %s" % Puppet[:checksumfile]
- begin
- File.unlink(Puppet[:checksumfile])
- retry
- rescue => detail
- raise Puppet::Error.new("Cannot remove %s: %s" %
- [Puppet[statefile], detail])
- end
- end
- end
-
- # Check whether our configuration is up to date
- def fresh?
- unless defined? @configstamp
- return false
- end
-
- # We're willing to give a 2 second drift
- if @driver.freshness - @configstamp < 1
- return true
- else
- return false
- end
- end
-
- # Retrieve the config from a remote server. If this fails, then
- # use the cached copy.
- def getconfig
- if self.fresh?
- Puppet.info "Config is up to date"
- return
- end
- Puppet.debug("getting config")
- dostorage()
-
- facts = self.class.facts
-
- unless facts.length > 0
- raise Puppet::ClientError.new(
- "Could not retrieve any facts"
- )
- end
-
- objects = nil
- if @local
- # If we're local, we don't have to do any of the conversion
- # stuff.
- objects = @driver.getconfig(facts, "yaml")
- @configstamp = Time.now.to_i
-
- if objects == ""
- raise Puppet::Error, "Could not retrieve configuration"
- end
- else
- textobjects = ""
-
- textfacts = CGI.escape(YAML.dump(facts))
-
- # error handling for this is done in the network client
- begin
- textobjects = @driver.getconfig(textfacts, "yaml")
- rescue => detail
- Puppet.err "Could not retrieve configuration: %s" % detail
- end
-
- fromcache = false
- if textobjects == ""
- textobjects = self.retrievecache
- if textobjects == ""
- raise Puppet::Error.new(
- "Cannot connect to server and there is no cached configuration"
- )
- end
- Puppet.notice "Could not get config; using cached copy"
- fromcache = true
- end
-
- begin
- textobjects = CGI.unescape(textobjects)
- @configstamp = Time.now.to_i
- rescue => detail
- raise Puppet::Error, "Could not CGI.unescape configuration"
- end
-
- if @cache and ! fromcache
- self.cache(textobjects)
- end
-
- begin
- objects = YAML.load(textobjects)
- rescue => detail
- raise Puppet::Error,
- "Could not understand configuration: %s" %
- detail.to_s
- end
- end
-
- unless objects.is_a?(Puppet::TransBucket)
- raise NetworkClientError,
- "Invalid returned objects of type %s" % objects.class
- end
-
- if classes = objects.classes
- self.setclasses(classes)
- else
- Puppet.info "No classes to store"
- end
-
- # Clear all existing objects, so we can recreate our stack.
- if defined? @objects
- Puppet::Type.allclear
- end
- @objects = nil
-
- # First create the default scheduling objects
- Puppet.type(:schedule).mkdefaultschedules
-
- # Now convert the objects to real Puppet objects
- @objects = objects.to_type
-
- if @objects.nil?
- raise Puppet::Error, "Configuration could not be processed"
- end
- #@objects = objects
-
- # and perform any necessary final actions before we evaluate.
- Puppet::Type.finalize
-
- return @objects
- end
-
- # Retrieve the cached config
- def retrievecache
- if FileTest.exists?(self.cachefile)
- return File.read(self.cachefile)
- else
- return ""
- end
- end
-
- # The code that actually runs the configuration. For now, just
- # ignore the onetime thing.
- def run(onetime = false)
- #if onetime
- begin
- self.getconfig
- self.apply
- rescue => detail
- Puppet.err detail.to_s
- if Puppet[:debug]
- puts detail.backtrace
- end
- exit(13)
- end
- return
- #end
-
-# Puppet.newthread do
-# begin
-# self.getconfig
-# self.apply
-# rescue => detail
-# Puppet.err detail.to_s
-# if Puppet[:debug]
-# puts detail.backtrace
-# end
-# exit(13)
-# end
-# end
- end
-
- def setclasses(ary)
- begin
- File.open(Puppet[:classfile], "w") { |f|
- f.puts ary.join("\n")
- }
- rescue => detail
- Puppet.err "Could not create class file %s: %s" %
- [Puppet[:classfile], detail]
- end
+ # A wrapper method to run and then store the last run time
+ def runnow
+ begin
+ self.run
+ self.lastrun = Time.now.to_i
+ rescue => detail
+ Puppet.err "Could not run %s: %s" % [self.class, detail]
end
end
- class Dipper < Puppet::Client
- @drivername = :Bucket
-
- def initialize(hash = {})
- if hash.include?(:Path)
- bucket = Puppet::Server::FileBucket.new(
- :Bucket => hash[:Path]
- )
- hash.delete(:Path)
- hash[:Bucket] = bucket
- end
-
- super(hash)
- end
-
- def backup(file)
- unless FileTest.exists?(file)
- raise(BucketError, "File %s does not exist" % file, caller)
- end
- contents = File.open(file) { |of| of.read }
-
- string = Base64.encode64(contents)
- #puts "string is created"
-
- sum = @driver.addfile(string,file)
- #puts "file %s is added" % file
- return sum
- end
-
- def restore(file,sum)
- restore = true
- if FileTest.exists?(file)
- contents = File.open(file) { |of| of.read }
-
- cursum = Digest::MD5.hexdigest(contents)
-
- # if the checksum has changed...
- # this might be extra effort
- if cursum == sum
- restore = false
- end
- end
-
- if restore
- #puts "Restoring %s" % file
- if tmp = @driver.getfile(sum)
- newcontents = Base64.decode64(tmp)
- newsum = Digest::MD5.hexdigest(newcontents)
- changed = nil
- unless FileTest.writable?(file)
- changed = File.stat(file).mode
- File.chmod(changed | 0200, file)
- end
- File.open(file,File::WRONLY|File::TRUNC) { |of|
- of.print(newcontents)
- }
- if changed
- File.chmod(changed, file)
- end
- else
- Puppet.err "Could not find file with checksum %s" % sum
- return nil
- end
- #puts "Done"
- return newsum
- else
- return nil
- end
-
- end
+ def run
+ raise Puppet::DevError, "Client type %s did not override run" %
+ self.class
end
- # unlike the other client classes (again, this design sucks) this class
- # is basically just a proxy class -- it calls its methods on the driver
- # and that's about it
- class ProxyClient < Puppet::Client
- def self.mkmethods
- interface = @handler.interface
- namespace = interface.prefix
-
- interface.methods.each { |ary|
- method = ary[0]
- Puppet.debug "%s: defining %s.%s" % [self, namespace, method]
- self.send(:define_method,method) { |*args|
- begin
- @driver.send(method, *args)
- rescue XMLRPC::FaultException => detail
- #Puppet.err "Could not call %s.%s: %s" %
- # [namespace, method, detail.faultString]
- #raise NetworkClientError,
- # "XMLRPC Error: %s" % detail.faultString
- raise NetworkClientError, detail.faultString
- end
- }
- }
+ def scheduled?
+ if sched = self.schedule
+ return sched.match?(self.lastrun)
+ else
+ return true
end
end
- class FileClient < Puppet::Client::ProxyClient
- @drivername = :FileServer
-
- # set up the appropriate interface methods
- @handler = Puppet::Server::FileServer
-
- self.mkmethods
-
- def initialize(hash = {})
- if hash.include?(:FileServer)
- unless hash[:FileServer].is_a?(Puppet::Server::FileServer)
- raise Puppet::DevError, "Must pass an actual FS object"
- end
- end
-
- super(hash)
- end
+ def setcerts
+ @driver.cert = @cert
+ @driver.key = @key
+ @driver.ca_file = @cacertfile
end
- class CAClient < Puppet::Client::ProxyClient
- @drivername = :CA
-
- # set up the appropriate interface methods
- @handler = Puppet::Server::CA
- self.mkmethods
-
- def initialize(hash = {})
- if hash.include?(:CA)
- hash[:CA] = Puppet::Server::CA.new()
- end
-
- super(hash)
- end
+ def shutdown
+ Puppet::Storage.store
+ exit
end
- class LogClient < Puppet::Client::ProxyClient
- @drivername = :Logger
+ # Start listening for events. We're pretty much just listening for
+ # timer events here.
+ def start
+ # Create our timer
+ timer = EventLoop::Timer.new(
+ :interval => Puppet[:runinterval],
+ :tolerance => 1,
+ :start? => true
+ )
- # set up the appropriate interface methods
- @handler = Puppet::Server::Logger
- self.mkmethods
+ # Stick it in the loop
+ EventLoop.current.monitor_timer timer
- def initialize(hash = {})
- if hash.include?(:Logger)
- hash[:Logger] = Puppet::Server::Logger.new()
+ # And run indefinitely
+ observe_signal timer, :alarm do
+ if self.scheduled?
+ self.runnow
end
-
- super(hash)
end
end
- class StatusClient < Puppet::Client::ProxyClient
- # set up the appropriate interface methods
- @handler = Puppet::Server::ServerStatus
- self.mkmethods
- end
-
+ require 'puppet/client/proxy'
+ require 'puppet/client/ca'
+ require 'puppet/client/dipper'
+ require 'puppet/client/file'
+ require 'puppet/client/log'
+ require 'puppet/client/master'
+ require 'puppet/client/status'
end
-#---------------------------------------------------------------
end
# $Id$
diff --git a/lib/puppet/client/ca.rb b/lib/puppet/client/ca.rb
new file mode 100644
index 000000000..11989230c
--- /dev/null
+++ b/lib/puppet/client/ca.rb
@@ -0,0 +1,17 @@
+class Puppet::Client::CAClient < Puppet::Client::ProxyClient
+ @drivername = :CA
+
+ # set up the appropriate interface methods
+ @handler = Puppet::Server::CA
+ self.mkmethods
+
+ def initialize(hash = {})
+ if hash.include?(:CA)
+ hash[:CA] = Puppet::Server::CA.new()
+ end
+
+ super(hash)
+ end
+end
+
+# $Id$
diff --git a/lib/puppet/client/dipper.rb b/lib/puppet/client/dipper.rb
new file mode 100644
index 000000000..281d0bab7
--- /dev/null
+++ b/lib/puppet/client/dipper.rb
@@ -0,0 +1,78 @@
+module Puppet
+ class Client
+ # The client class for filebuckets.
+ class Dipper < Puppet::Client
+ @drivername = :Bucket
+
+ def initialize(hash = {})
+ if hash.include?(:Path)
+ bucket = Puppet::Server::FileBucket.new(
+ :Bucket => hash[:Path]
+ )
+ hash.delete(:Path)
+ hash[:Bucket] = bucket
+ end
+
+ super(hash)
+ end
+
+ def backup(file)
+ unless FileTest.exists?(file)
+ raise(BucketError, "File %s does not exist" % file, caller)
+ end
+ contents = File.open(file) { |of| of.read }
+
+ string = Base64.encode64(contents)
+ #puts "string is created"
+
+ sum = @driver.addfile(string,file)
+ #puts "file %s is added" % file
+ return sum
+ end
+
+ def restore(file,sum)
+ restore = true
+ if FileTest.exists?(file)
+ contents = File.open(file) { |of| of.read }
+
+ cursum = Digest::MD5.hexdigest(contents)
+
+ # if the checksum has changed...
+ # this might be extra effort
+ if cursum == sum
+ restore = false
+ end
+ end
+
+ if restore
+ #puts "Restoring %s" % file
+ if tmp = @driver.getfile(sum)
+ newcontents = Base64.decode64(tmp)
+ newsum = Digest::MD5.hexdigest(newcontents)
+ changed = nil
+ unless FileTest.writable?(file)
+ changed = File.stat(file).mode
+ File.chmod(changed | 0200, file)
+ end
+ File.open(file,File::WRONLY|File::TRUNC) { |of|
+ of.print(newcontents)
+ }
+ if changed
+ File.chmod(changed, file)
+ end
+ else
+ Puppet.err "Could not find file with checksum %s" % sum
+ return nil
+ end
+ #puts "Done"
+ return newsum
+ else
+ return nil
+ end
+
+ end
+ end
+ end
+end
+
+# $Id$
diff --git a/lib/puppet/client/file.rb b/lib/puppet/client/file.rb
new file mode 100644
index 000000000..116624003
--- /dev/null
+++ b/lib/puppet/client/file.rb
@@ -0,0 +1,20 @@
+class Puppet::Client::FileClient < Puppet::Client::ProxyClient
+ @drivername = :FileServer
+
+ # set up the appropriate interface methods
+ @handler = Puppet::Server::FileServer
+
+ self.mkmethods
+
+ def initialize(hash = {})
+ if hash.include?(:FileServer)
+ unless hash[:FileServer].is_a?(Puppet::Server::FileServer)
+ raise Puppet::DevError, "Must pass an actual FS object"
+ end
+ end
+
+ super(hash)
+ end
+end
+
+# $Id$
diff --git a/lib/puppet/client/log.rb b/lib/puppet/client/log.rb
new file mode 100644
index 000000000..e20c0532c
--- /dev/null
+++ b/lib/puppet/client/log.rb
@@ -0,0 +1,17 @@
+class Puppet::Client::LogClient < Puppet::Client::ProxyClient
+ @drivername = :Logger
+
+ # set up the appropriate interface methods
+ @handler = Puppet::Server::Logger
+ self.mkmethods
+
+ def initialize(hash = {})
+ if hash.include?(:Logger)
+ hash[:Logger] = Puppet::Server::Logger.new()
+ end
+
+ super(hash)
+ end
+end
+
+# $Id$
diff --git a/lib/puppet/client/master.rb b/lib/puppet/client/master.rb
new file mode 100644
index 000000000..f0204c418
--- /dev/null
+++ b/lib/puppet/client/master.rb
@@ -0,0 +1,244 @@
+# The client for interacting with the puppetmaster config server.
+class Puppet::Client::MasterClient < Puppet::Client
+ @drivername = :Master
+
+ def self.facts
+ facts = {}
+ Facter.each { |name,fact|
+ facts[name] = fact.downcase
+ }
+
+ facts
+ end
+
+ # This method is how the client receives the tree of Transportable
+ # objects. For now, just descend into the tree and perform and
+ # necessary manipulations.
+ def apply
+ dostorage()
+ unless defined? @objects
+ raise Puppet::Error, "Cannot apply; objects not defined"
+ end
+
+ #Puppet.err :yay
+ #p @objects
+ #Puppet.err :mark
+ #@objects = @objects.to_type
+ # this is a gross hack... but i don't see a good way around it
+ # set all of the variables to empty
+ Puppet::Transaction.init
+
+ # For now we just evaluate the top-level object, but eventually
+ # there will be schedules and such associated with each object,
+ # and probably with the container itself.
+ transaction = @objects.evaluate
+ #transaction = Puppet::Transaction.new(objects)
+ transaction.toplevel = true
+ begin
+ transaction.evaluate
+ rescue Puppet::Error => detail
+ Puppet.err "Could not apply complete configuration: %s" %
+ detail
+ rescue => detail
+ Puppet.err "Found a bug: %s" % detail
+ if Puppet[:debug]
+ puts detail.backtrace
+ end
+ ensure
+ Puppet::Storage.store
+ end
+ Puppet::Metric.gather
+ Puppet::Metric.tally
+ if Puppet[:rrdgraph] == true
+ Metric.store
+ Metric.graph
+ end
+
+ return transaction
+ end
+
+ # Cache the config
+ def cache(text)
+ Puppet.info "Caching configuration at %s" % self.cachefile
+ confdir = File.dirname(Puppet[:localconfig])
+ unless FileTest.exists?(confdir)
+ Puppet.recmkdir(confdir, 0770)
+ end
+ File.open(self.cachefile + ".tmp", "w", 0660) { |f|
+ f.print text
+ }
+ File.rename(self.cachefile + ".tmp", self.cachefile)
+ end
+
+ def cachefile
+ unless defined? @cachefile
+ @cachefile = Puppet[:localconfig] + ".yaml"
+ end
+ @cachefile
+ end
+
+ # Initialize and load storage
+ def dostorage
+ begin
+ Puppet::Storage.init
+ Puppet::Storage.load
+ rescue => detail
+ Puppet.err "Corrupt state file %s: %s" % [Puppet[:checksumfile], detail]
+ begin
+ File.unlink(Puppet[:checksumfile])
+ retry
+ rescue => detail
+ raise Puppet::Error.new("Cannot remove %s: %s" %
+ [Puppet[statefile], detail])
+ end
+ end
+ end
+
+ # Check whether our configuration is up to date
+ def fresh?
+ unless defined? @configstamp
+ return false
+ end
+
+ # We're willing to give a 2 second drift
+ if @driver.freshness - @configstamp < 1
+ return true
+ else
+ return false
+ end
+ end
+
+ # Retrieve the config from a remote server. If this fails, then
+ # use the cached copy.
+ def getconfig
+ if self.fresh?
+ Puppet.info "Config is up to date"
+ return
+ end
+ Puppet.debug("getting config")
+ dostorage()
+
+ facts = self.class.facts
+
+ unless facts.length > 0
+ raise Puppet::ClientError.new(
+ "Could not retrieve any facts"
+ )
+ end
+
+ objects = nil
+ if @local
+ # If we're local, we don't have to do any of the conversion
+ # stuff.
+ objects = @driver.getconfig(facts, "yaml")
+ @configstamp = Time.now.to_i
+
+ if objects == ""
+ raise Puppet::Error, "Could not retrieve configuration"
+ end
+ else
+ textobjects = ""
+
+ textfacts = CGI.escape(YAML.dump(facts))
+
+ # error handling for this is done in the network client
+ begin
+ textobjects = @driver.getconfig(textfacts, "yaml")
+ rescue => detail
+ Puppet.err "Could not retrieve configuration: %s" % detail
+ end
+
+ fromcache = false
+ if textobjects == ""
+ textobjects = self.retrievecache
+ if textobjects == ""
+ raise Puppet::Error.new(
+ "Cannot connect to server and there is no cached configuration"
+ )
+ end
+ Puppet.notice "Could not get config; using cached copy"
+ fromcache = true
+ end
+
+ begin
+ textobjects = CGI.unescape(textobjects)
+ @configstamp = Time.now.to_i
+ rescue => detail
+ raise Puppet::Error, "Could not CGI.unescape configuration"
+ end
+
+ if @cache and ! fromcache
+ self.cache(textobjects)
+ end
+
+ begin
+ objects = YAML.load(textobjects)
+ rescue => detail
+ raise Puppet::Error,
+ "Could not understand configuration: %s" %
+ detail.to_s
+ end
+ end
+
+ unless objects.is_a?(Puppet::TransBucket)
+ raise NetworkClientError,
+ "Invalid returned objects of type %s" % objects.class
+ end
+
+ if classes = objects.classes
+ self.setclasses(classes)
+ else
+ Puppet.info "No classes to store"
+ end
+
+ # Clear all existing objects, so we can recreate our stack.
+ if defined? @objects
+ Puppet::Type.allclear
+ end
+ @objects = nil
+
+ # First create the default scheduling objects
+ Puppet.type(:schedule).mkdefaultschedules
+
+ # Now convert the objects to real Puppet objects
+ @objects = objects.to_type
+
+ if @objects.nil?
+ raise Puppet::Error, "Configuration could not be processed"
+ end
+ #@objects = objects
+
+ # and perform any necessary final actions before we evaluate.
+ Puppet::Type.finalize
+
+ return @objects
+ end
+
+ # Retrieve the cached config
+ def retrievecache
+ if FileTest.exists?(self.cachefile)
+ return File.read(self.cachefile)
+ else
+ return ""
+ end
+ end
+
+ # The code that actually runs the configuration.
+ def run
+ self.getconfig
+ self.apply
+ end
+
+ def setclasses(ary)
+ begin
+ File.open(Puppet[:classfile], "w") { |f|
+ f.puts ary.join("\n")
+ }
+ rescue => detail
+ Puppet.err "Could not create class file %s: %s" %
+ [Puppet[:classfile], detail]
+ end
+ end
+end
+
+# $Id$
diff --git a/lib/puppet/client/proxy.rb b/lib/puppet/client/proxy.rb
new file mode 100644
index 000000000..2ea0a87f9
--- /dev/null
+++ b/lib/puppet/client/proxy.rb
@@ -0,0 +1,27 @@
+# unlike the other client classes (again, this design sucks) this class
+# is basically just a proxy class -- it calls its methods on the driver
+# and that's about it
+class Puppet::Client::ProxyClient < Puppet::Client
+ def self.mkmethods
+ interface = @handler.interface
+ namespace = interface.prefix
+
+ interface.methods.each { |ary|
+ method = ary[0]
+ Puppet.debug "%s: defining %s.%s" % [self, namespace, method]
+ self.send(:define_method,method) { |*args|
+ begin
+ @driver.send(method, *args)
+ rescue XMLRPC::FaultException => detail
+ #Puppet.err "Could not call %s.%s: %s" %
+ # [namespace, method, detail.faultString]
+ #raise NetworkClientError,
+ # "XMLRPC Error: %s" % detail.faultString
+ raise NetworkClientError, detail.faultString
+ end
+ }
+ }
+ end
+end
+
+# $Id$
diff --git a/lib/puppet/client/status.rb b/lib/puppet/client/status.rb
new file mode 100644
index 000000000..ed1445e04
--- /dev/null
+++ b/lib/puppet/client/status.rb
@@ -0,0 +1,7 @@
+class Puppet::Client::StatusClient < Puppet::Client::ProxyClient
+ # set up the appropriate interface methods
+ @handler = Puppet::Server::ServerStatus
+ self.mkmethods
+end
+
+# $Id$
diff --git a/lib/puppet/daemon.rb b/lib/puppet/daemon.rb
index a3ced6c47..a8ef0a87a 100755
--- a/lib/puppet/daemon.rb
+++ b/lib/puppet/daemon.rb
@@ -153,6 +153,9 @@ module Puppet
begin
cert, cacert = @driver.getcert(@csr.to_pem)
rescue => detail
+ if Puppet[:debug]
+ puts detail.backtrace
+ end
raise Puppet::Error.new("Certificate retrieval failed: %s" %
detail)
end
diff --git a/lib/puppet/event-loop.rb b/lib/puppet/event-loop.rb
new file mode 100644
index 000000000..9d98cf0ee
--- /dev/null
+++ b/lib/puppet/event-loop.rb
@@ -0,0 +1 @@
+require "puppet/event-loop/event-loop"
diff --git a/lib/puppet/event-loop/better-definers.rb b/lib/puppet/event-loop/better-definers.rb
new file mode 100644
index 000000000..0af37da62
--- /dev/null
+++ b/lib/puppet/event-loop/better-definers.rb
@@ -0,0 +1,367 @@
+## 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/event-loop/event-loop.rb b/lib/puppet/event-loop/event-loop.rb
new file mode 100644
index 000000000..5d78844ef
--- /dev/null
+++ b/lib/puppet/event-loop/event-loop.rb
@@ -0,0 +1,355 @@
+## 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/event-loop/better-definers"
+require "puppet/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
+
+ @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
+ unless monitoring_io?(io, event)
+ @ios[event] << io ; wake_up
+ end
+ end
+ end
+
+ def monitor_timer (timer)
+ unless monitoring_timer? timer
+ @timers << timer
+ end
+ 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?
+ require "fcntl"
+ fcntl(Fcntl::F_GETFL, 0) & Fcntl::O_NONBLOCK == 0
+ end
+
+ def will_block= (wants_blocking)
+ require "fcntl"
+ 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
+
+class EventLoop::Timer
+ include SignalEmitter
+
+ DEFAULT_INTERVAL = 0.0
+ DEFAULT_TOLERANCE = 0.001
+
+ def initialize (options={}, &handler)
+ @running = false
+ @start_time = nil
+
+ if options.kind_of? Numeric
+ options = { :interval => options }
+ end
+
+ 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
+ if new_interval < old_interval
+ @event_loop.check_timer(self)
+ end
+ 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/event-loop/signal-system.rb b/lib/puppet/event-loop/signal-system.rb
new file mode 100644
index 000000000..f7fe9b52c
--- /dev/null
+++ b/lib/puppet/event-loop/signal-system.rb
@@ -0,0 +1,220 @@
+## 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/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)
+ if not includer.kind_of? SignalEmitterClass
+ includer.extend SignalEmitterClass
+ end
+ 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
+ return 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/filetype.rb b/lib/puppet/filetype.rb
index dfca58511..c86ed9c07 100755
--- a/lib/puppet/filetype.rb
+++ b/lib/puppet/filetype.rb
@@ -174,7 +174,7 @@ module Puppet
# Remove a specific @path's cron tab.
def remove
- Puppet.asuser(@path) {
+ Puppet::Util.asuser(@path) {
%x{crontab -r 2>/dev/null}
}
end
@@ -182,7 +182,7 @@ module Puppet
# Overwrite a specific @path's cron tab; must be passed the @path name
# and the text with which to create the cron tab.
def write(text)
- Puppet.asuser(@path) {
+ Puppet::Util.asuser(@path) {
IO.popen("crontab", "w") { |p|
p.print text
}
diff --git a/lib/puppet/networkclient.rb b/lib/puppet/networkclient.rb
new file mode 100644
index 000000000..31bc9a9dd
--- /dev/null
+++ b/lib/puppet/networkclient.rb
@@ -0,0 +1,141 @@
+require 'puppet'
+require 'puppet/sslcertificates'
+require 'puppet/type'
+require 'facter'
+require 'openssl'
+require 'puppet/transaction'
+require 'puppet/transportable'
+require 'puppet/metric'
+require 'puppet/daemon'
+require 'puppet/server'
+require 'puppet/base64'
+
+$noclientnetworking = false
+begin
+ require 'webrick'
+ require 'cgi'
+ require 'xmlrpc/client'
+ require 'xmlrpc/server'
+ require 'yaml'
+rescue LoadError => detail
+ $noclientnetworking = detail
+ raise Puppet::Error, "You must have the Ruby XMLRPC, CGI, and Webrick libraries installed"
+end
+
+module Puppet
+ class NetworkClientError < RuntimeError; end
+ class ClientError < RuntimeError; end
+ #---------------------------------------------------------------
+ if $noclientnetworking
+ Puppet.err "Could not load client network libs: %s" % $noclientnetworking
+ else
+ class NetworkClient < XMLRPC::Client
+ #include Puppet::Daemon
+
+ # add the methods associated with each namespace
+ Puppet::Server::Handler.each { |handler|
+ interface = handler.interface
+ namespace = interface.prefix
+
+ interface.methods.each { |ary|
+ method = ary[0]
+ Puppet.info "Defining %s.%s" % [namespace, method]
+ self.send(:define_method,method) { |*args|
+ #Puppet.info "Calling %s" % method
+ #Puppet.info "peer cert is %s" % @http.peer_cert
+ #Puppet.info "cert is %s" % @http.cert
+ begin
+ call("%s.%s" % [namespace, method.to_s],*args)
+ rescue OpenSSL::SSL::SSLError => detail
+ #Puppet.err "Could not call %s.%s: Untrusted certificates" %
+ # [namespace, method]
+ raise NetworkClientError,
+ "Certificates were not trusted"
+ rescue XMLRPC::FaultException => detail
+ #Puppet.err "Could not call %s.%s: %s" %
+ # [namespace, method, detail.faultString]
+ #raise NetworkClientError,
+ # "XMLRPC Error: %s" % detail.faultString
+ raise NetworkClientError, detail.faultString
+ rescue Errno::ECONNREFUSED => detail
+ msg = "Could not connect to %s on port %s" % [@host, @port]
+ #Puppet.err msg
+ raise NetworkClientError, msg
+ rescue SocketError => detail
+ Puppet.err "Could not find server %s" % @puppetserver
+ exit(12)
+ rescue => detail
+ Puppet.err "Could not call %s.%s: %s" %
+ [namespace, method, detail.inspect]
+ #raise NetworkClientError.new(detail.to_s)
+ raise
+ end
+ }
+ }
+ }
+
+ def ca_file=(cafile)
+ @http.ca_file = cafile
+ store = OpenSSL::X509::Store.new
+ cacert = OpenSSL::X509::Certificate.new(
+ File.read(cafile)
+ )
+ store.add_cert(cacert)
+ store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
+ @http.cert_store = store
+ end
+
+ def cert=(cert)
+ #Puppet.debug "Adding certificate"
+ @http.cert = cert
+ @http.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ end
+
+ def key=(key)
+ @http.key = key
+ end
+
+ def initialize(hash)
+ hash[:Path] ||= "/RPC2"
+ hash[:Server] ||= "localhost"
+ hash[:Port] ||= Puppet[:masterport]
+
+ @puppetserver = hash[:Server]
+
+ super(
+ hash[:Server],
+ hash[:Path],
+ hash[:Port],
+ nil, # proxy_host
+ nil, # proxy_port
+ nil, # user
+ nil, # password
+ true # use_ssl
+ )
+
+ if hash[:Certificate]
+ self.cert = hash[:Certificate]
+ else
+ Puppet.err "No certificate; running with reduced functionality."
+ end
+
+ if hash[:Key]
+ self.key = hash[:Key]
+ end
+
+ if hash[:CAFile]
+ self.ca_file = hash[:CAFile]
+ end
+
+ # from here, i need to add the key, cert, and ca cert
+ # and reorgize how i start the client
+ end
+
+ def local
+ false
+ end
+ end
+ end
+end
+
+# $Id$
diff --git a/lib/puppet/parameter.rb b/lib/puppet/parameter.rb
index dfb259909..df6fa8b4d 100644
--- a/lib/puppet/parameter.rb
+++ b/lib/puppet/parameter.rb
@@ -226,10 +226,10 @@ module Puppet
)
end
- # each parameter class must define the name() method, and parameter instances
- # do not change that name
- # this implicitly means that a given object can only have one parameter
- # instance of a given parameter class
+ # each parameter class must define the name() method, and parameter
+ # instances do not change that name this implicitly means that a given
+ # object can only have one parameter instance of a given parameter
+ # class
def name
return self.class.name
end
@@ -325,10 +325,6 @@ module Puppet
end
end
- def name
- self.class.name
- end
-
def to_s
s = "Parameter(%s)" % self.name
end
diff --git a/lib/puppet/sslcertificates.rb b/lib/puppet/sslcertificates.rb
index 6c21f7c49..0c6322bcf 100755
--- a/lib/puppet/sslcertificates.rb
+++ b/lib/puppet/sslcertificates.rb
@@ -8,8 +8,7 @@ rescue LoadError
raise Puppet::Error, "You must have the Ruby openssl library installed"
end
-module Puppet
-module SSLCertificates
+module Puppet::SSLCertificates
def self.mkdir(dir)
# this is all a bunch of stupid hackery
unless FileTest.exists?(dir)
@@ -165,575 +164,8 @@ module SSLCertificates
return hashpath
end
-
-
-
- class CA
- attr_accessor :keyfile, :file, :config, :dir, :cert
-
- @@params = [
- :certdir,
- :publickeydir,
- :privatekeydir,
- :cadir,
- :cakey,
- :cacert,
- :capass,
- :capub,
- :csrdir,
- :signeddir,
- :serial,
- :privatedir,
- :ca_crl_days,
- :ca_days,
- :ca_md,
- :req_bits,
- :keylength,
- :autosign
- ]
-
- @@defaults = {
- :certdir => [:ssldir, "certs"],
- :publickeydir => [:ssldir, "public_keys"],
- :privatekeydir => [:ssldir, "private_keys"],
- :cadir => [:ssldir, "ca"],
- :cacert => [:cadir, "ca_crt.pem"],
- :cakey => [:cadir, "ca_key.pem"],
- :capub => [:cadir, "ca_pub.pem"],
- :csrdir => [:cadir, "requests"],
- :signeddir => [:cadir, "signed"],
- :capass => [:cadir, "ca.pass"],
- :serial => [:cadir, "serial"],
- :privatedir => [:ssldir, "private"],
- :passfile => [:privatedir, "password"],
- :autosign => [:puppetconf, "autosign.conf"],
- :ca_crl_days => 365,
- :ca_days => 1825,
- :ca_md => "md5",
- :req_bits => 2048,
- :keylength => 1024,
- }
-
- @@params.each { |param|
- Puppet.setdefault(param,@@defaults[param])
- }
-
- def certfile
- @config[:cacert]
- end
-
- def host2csrfile(hostname)
- File.join(Puppet[:csrdir], [hostname, "pem"].join("."))
- end
-
- # this stores signed certs in a directory unrelated to
- # normal client certs
- def host2certfile(hostname)
- File.join(Puppet[:signeddir], [hostname, "pem"].join("."))
- end
-
- def thing2name(thing)
- thing.subject.to_a.find { |ary|
- ary[0] == "CN"
- }[1]
- end
-
- def initialize(hash = {})
- self.setconfig(hash)
-
- self.getcert
- unless FileTest.exists?(@config[:serial])
- File.open(@config[:serial], "w") { |f|
- f << "%04X" % 1
- }
- end
-
- if Puppet[:capass] and ! FileTest.exists?(Puppet[:capass])
- self.genpass
- end
- end
-
- def genpass
- pass = ""
- 20.times { pass += (rand(74) + 48).chr }
-
- unless @config[:capass]
- raise "No passfile"
- end
- Puppet::SSLCertificates.mkdir(File.dirname(@config[:capass]))
- File.open(@config[:capass], "w", 0600) { |f| f.print pass }
- return pass
- end
-
- def getcert
- if FileTest.exists?(@config[:cacert])
- @cert = OpenSSL::X509::Certificate.new(
- File.read(@config[:cacert])
- )
- else
- self.mkrootcert
- end
- end
-
- def getclientcsr(host)
- csrfile = host2csrfile(host)
- unless File.exists?(csrfile)
- return nil
- end
-
- return OpenSSL::X509::Request.new(File.read(csrfile))
- end
-
- def getclientcert(host)
- certfile = host2certfile(host)
- unless File.exists?(certfile)
- return [nil, nil]
- end
-
- return [OpenSSL::X509::Certificate.new(File.read(certfile)), @cert]
- end
-
- def list
- return Dir.entries(Puppet[:csrdir]).reject { |file|
- file =~ /^\.+$/
- }.collect { |file|
- file.sub(/\.pem$/, '')
- }
- end
-
- def mkrootcert
- cert = Certificate.new(
- :name => "CAcert",
- :cert => @config[:cacert],
- :encrypt => @config[:passfile],
- :key => @config[:cakey],
- :selfsign => true,
- :length => 1825,
- :type => :ca
- )
- @cert = cert.mkselfsigned
- File.open(@config[:cacert], "w", 0660) { |f|
- f.puts @cert.to_pem
- }
- @key = cert.key
- return cert
- end
-
- def removeclientcsr(host)
- csrfile = host2csrfile(host)
- unless File.exists?(csrfile)
- raise Puppet::Error, "No certificate request for %s" % host
- end
-
- File.unlink(csrfile)
- end
-
- def setconfig(hash)
- @config = {}
- @@params.each { |param|
- if hash.include?(param)
- begin
- @config[param] = hash[param]
- Puppet[param] = hash[param]
- hash.delete(param)
- rescue => detail
- puts detail
- exit
- end
- else
- begin
- @config[param] = Puppet[param]
- rescue => detail
- puts detail
- exit
- end
- end
- }
-
- if hash.include?(:password)
- @config[:password] = hash[:password]
- hash.delete(:password)
- end
-
- if hash.length > 0
- raise ArgumentError, "Unknown parameters %s" % hash.keys.join(",")
- end
-
- [:cadir, :csrdir, :signeddir].each { |dir|
- unless @config[dir]
- raise "%s is undefined" % dir
- end
- unless FileTest.exists?(@config[dir])
- Puppet::SSLCertificates.mkdir(@config[dir])
- end
- }
- end
-
- def sign(csr)
- unless csr.is_a?(OpenSSL::X509::Request)
- raise Puppet::Error,
- "CA#sign only accepts OpenSSL::X509::Request objects, not %s" %
- csr.class
- end
-
- unless csr.verify(csr.public_key)
- raise Puppet::Error, "CSR sign verification failed"
- end
-
- # i should probably check key length...
-
- # read the ca cert in
- cacert = OpenSSL::X509::Certificate.new(
- File.read(@config[:cacert])
- )
-
- cakey = nil
- if @config[:password]
- cakey = OpenSSL::PKey::RSA.new(
- File.read(@config[:cakey]), @config[:password]
- )
- else
- cakey = OpenSSL::PKey::RSA.new(
- File.read(@config[:cakey])
- )
- end
-
- unless cacert.check_private_key(cakey)
- raise Puppet::Error, "CA Certificate is invalid"
- end
-
- serial = File.read(@config[:serial]).chomp.hex
- newcert = SSLCertificates.mkcert(
- :type => :server,
- :name => csr.subject,
- :days => @config[:ca_days],
- :issuer => cacert,
- :serial => serial,
- :publickey => csr.public_key
- )
-
- # increment the serial
- File.open(@config[:serial], "w") { |f|
- f << "%04X" % (serial + 1)
- }
-
- newcert.sign(cakey, OpenSSL::Digest::SHA1.new)
-
- self.storeclientcert(newcert)
-
- return [newcert, cacert]
- end
-
- def storeclientcsr(csr)
- host = thing2name(csr)
-
- csrfile = host2csrfile(host)
- if File.exists?(csrfile)
- raise Puppet::Error, "Certificate request for %s already exists" % host
- end
-
- File.open(csrfile, "w", 0660) { |f|
- f.print csr.to_pem
- }
- end
-
- def storeclientcert(cert)
- host = thing2name(cert)
-
- certfile = host2certfile(host)
- if File.exists?(certfile)
- Puppet.notice "Overwriting signed certificate %s for %s" %
- [certfile, host]
- end
-
- File.open(certfile, "w", 0660) { |f|
- f.print cert.to_pem
- }
- end
-
- end
-
- class Certificate
- attr_accessor :certfile, :keyfile, :name, :dir, :hash, :type
- attr_accessor :key, :cert, :csr, :cacert
-
- @@params2names = {
- :name => "CN",
- :state => "ST",
- :country => "C",
- :email => "emailAddress",
- :org => "O",
- :city => "L",
- :ou => "OU"
- }
-
- def certname
- OpenSSL::X509::Name.new self.subject
- end
-
- def delete
- [@certfile,@keyfile].each { |file|
- if FileTest.exists?(file)
- File.unlink(file)
- end
- }
-
- if defined? @hash and @hash
- if FileTest.symlink?(@hash)
- File.unlink(@hash)
- end
- end
- end
-
- def exists?
- return FileTest.exists?(@certfile)
- end
-
- def getkey
- unless FileTest.exists?(@keyfile)
- self.mkkey()
- end
- if @password
- @key = OpenSSL::PKey::RSA.new(
- File.read(@keyfile),
- @password
- )
- else
- @key = OpenSSL::PKey::RSA.new(
- File.read(@keyfile)
- )
- end
- end
-
- def initialize(hash)
- unless hash.include?(:name)
- raise "You must specify the common name for the certificate"
- end
- @name = hash[:name]
-
- # init a few variables
- @cert = @key = @csr = nil
-
- if hash.include?(:cert)
- @certfile = hash[:cert]
- @dir = File.dirname(@certfile)
- else
- @dir = hash[:dir] || Puppet[:certdir]
- @certfile = File.join(@dir, @name)
- end
-
- @cacertfile ||= File.join(Puppet[:certdir], "ca.pem")
-
- unless FileTest.directory?(@dir)
- Puppet::SSLCertificates.mkdir(@dir)
- end
-
- unless @certfile =~ /\.pem$/
- @certfile += ".pem"
- end
- @keyfile = hash[:key] || File.join(
- Puppet[:privatekeydir], [@name,"pem"].join(".")
- )
- unless FileTest.directory?(@dir)
- Puppet::SSLCertificates.mkdir(@dir)
- end
-
- [@keyfile].each { |file|
- dir = File.dirname(file)
-
- unless FileTest.directory?(dir)
- Puppet::SSLCertificates.mkdir(dir)
- end
- }
-
- @days = hash[:length] || 365
- @selfsign = hash[:selfsign] || false
- @encrypt = hash[:encrypt] || false
- @replace = hash[:replace] || false
- @issuer = hash[:issuer] || nil
-
- if hash.include?(:type)
- case hash[:type]
- when :ca, :client, :server: @type = hash[:type]
- else
- raise "Invalid Cert type %s" % hash[:type]
- end
- else
- @type = :client
- end
-
- @params = {:name => @name}
- [:state, :country, :email, :org, :ou].each { |param|
- if hash.include?(param)
- @params[param] = hash[param]
- end
- }
-
- if @encrypt
- if @encrypt =~ /^\//
- File.open(@encrypt) { |f|
- @password = f.read.chomp
- }
- else
- raise ":encrypt must be a path to a pass phrase file"
- end
- else
- @password = nil
- end
-
- if hash.include?(:selfsign)
- @selfsign = hash[:selfsign]
- else
- @selfsign = false
- end
- end
-
- # this only works for servers, not for users
- def mkcsr
- unless defined? @key and @key
- self.getkey
- end
-
- name = OpenSSL::X509::Name.new self.subject
-
- @csr = OpenSSL::X509::Request.new
- @csr.version = 0
- @csr.subject = name
- @csr.public_key = @key.public_key
- @csr.sign(@key, OpenSSL::Digest::SHA1.new)
-
- #File.open(@csrfile, "w") { |f|
- # f << @csr.to_pem
- #}
-
- unless @csr.verify(@key.public_key)
- raise Puppet::Error, "CSR sign verification failed"
- end
-
- return @csr
- end
-
- def mkkey
- # @key is the file
-
- @key = OpenSSL::PKey::RSA.new(1024)
-# { |p,n|
-# case p
-# when 0; Puppet.info "key info: ." # BN_generate_prime
-# when 1; Puppet.info "key info: +" # BN_generate_prime
-# when 2; Puppet.info "key info: *" # searching good prime,
-# # n = #of try,
-# # but also data from BN_generate_prime
-# when 3; Puppet.info "key info: \n" # found good prime, n==0 - p, n==1 - q,
-# # but also data from BN_generate_prime
-# else; Puppet.info "key info: *" # BN_generate_prime
-# end
-# }
-
- if @password
- #passwdproc = proc { @password }
- keytext = @key.export(
- OpenSSL::Cipher::DES.new(:EDE3, :CBC),
- @password
- )
- File.open(@keyfile, "w", 0400) { |f|
- f << keytext
- }
- else
- File.open(@keyfile, "w", 0400) { |f|
- f << @key.to_pem
- }
- end
-
- #cmd = "#{ossl} genrsa -out #{@key} 1024"
- end
-
- def mkselfsigned
- unless defined? @key and @key
- self.getkey
- end
-
- if defined? @cert and @cert
- raise Puppet::Error, "Cannot replace existing certificate"
- end
-
- args = {
- :name => self.certname,
- :days => @days,
- :issuer => nil,
- :serial => 0x0,
- :publickey => @key.public_key
- }
- if @type
- args[:type] = @type
- else
- args[:type] = :server
- end
- @cert = SSLCertificates.mkcert(args)
-
- @cert.sign(@key, OpenSSL::Digest::SHA1.new) if @selfsign
-
- return @cert
- end
-
- def subject(string = false)
- subj = @@params2names.collect { |param, name|
- if @params.include?(param)
- [name, @params[param]]
- end
- }.reject { |ary| ary.nil? }
-
- if string
- return "/" + subj.collect { |ary|
- "%s=%s" % ary
- }.join("/") + "/"
- else
- return subj
- end
- end
-
- # verify that we can track down the cert chain or whatever
- def verify
- "openssl verify -verbose -CAfile /home/luke/.puppet/ssl/certs/ca.pem -purpose sslserver culain.madstop.com.pem"
- end
-
- def write
- files = {
- @certfile => @cert,
- @keyfile => @key,
- }
- if defined? @cacert
- files[@cacertfile] = @cacert
- end
-
- files.each { |file,thing|
- if defined? thing and thing
- if FileTest.exists?(file)
- next
- end
-
- text = nil
-
- if thing.is_a?(OpenSSL::PKey::RSA) and @password
- text = thing.export(
- OpenSSL::Cipher::DES.new(:EDE3, :CBC),
- @password
- )
- else
- text = thing.to_pem
- end
-
- File.open(file, "w", 0660) { |f| f.print text }
- end
- }
-
- if defined? @cacert
- SSLCertificates.mkhash(Puppet[:certdir], @cacert, @cacertfile)
- end
- end
- end
-end
+ require 'puppet/sslcertificates/certificate'
+ require 'puppet/sslcertificates/ca'
end
-#
# $Id$
diff --git a/lib/puppet/sslcertificates/ca.rb b/lib/puppet/sslcertificates/ca.rb
new file mode 100644
index 000000000..0137e15eb
--- /dev/null
+++ b/lib/puppet/sslcertificates/ca.rb
@@ -0,0 +1,286 @@
+class Puppet::SSLCertificates::CA
+ Certificate = Puppet::SSLCertificates::Certificate
+ attr_accessor :keyfile, :file, :config, :dir, :cert
+
+ @@params = [
+ :certdir,
+ :publickeydir,
+ :privatekeydir,
+ :cadir,
+ :cakey,
+ :cacert,
+ :capass,
+ :capub,
+ :csrdir,
+ :signeddir,
+ :serial,
+ :privatedir,
+ :ca_crl_days,
+ :ca_days,
+ :ca_md,
+ :req_bits,
+ :keylength,
+ :autosign
+ ]
+
+ @@defaults = {
+ :certdir => [:ssldir, "certs"],
+ :publickeydir => [:ssldir, "public_keys"],
+ :privatekeydir => [:ssldir, "private_keys"],
+ :cadir => [:ssldir, "ca"],
+ :cacert => [:cadir, "ca_crt.pem"],
+ :cakey => [:cadir, "ca_key.pem"],
+ :capub => [:cadir, "ca_pub.pem"],
+ :csrdir => [:cadir, "requests"],
+ :signeddir => [:cadir, "signed"],
+ :capass => [:cadir, "ca.pass"],
+ :serial => [:cadir, "serial"],
+ :privatedir => [:ssldir, "private"],
+ :passfile => [:privatedir, "password"],
+ :autosign => [:puppetconf, "autosign.conf"],
+ :ca_crl_days => 365,
+ :ca_days => 1825,
+ :ca_md => "md5",
+ :req_bits => 2048,
+ :keylength => 1024,
+ }
+
+ @@params.each { |param|
+ Puppet.setdefault(param,@@defaults[param])
+ }
+
+ def certfile
+ @config[:cacert]
+ end
+
+ def host2csrfile(hostname)
+ File.join(Puppet[:csrdir], [hostname, "pem"].join("."))
+ end
+
+ # this stores signed certs in a directory unrelated to
+ # normal client certs
+ def host2certfile(hostname)
+ File.join(Puppet[:signeddir], [hostname, "pem"].join("."))
+ end
+
+ def thing2name(thing)
+ thing.subject.to_a.find { |ary|
+ ary[0] == "CN"
+ }[1]
+ end
+
+ def initialize(hash = {})
+ self.setconfig(hash)
+
+ self.getcert
+ unless FileTest.exists?(@config[:serial])
+ File.open(@config[:serial], "w") { |f|
+ f << "%04X" % 1
+ }
+ end
+
+ if Puppet[:capass] and ! FileTest.exists?(Puppet[:capass])
+ self.genpass
+ end
+ end
+
+ def genpass
+ pass = ""
+ 20.times { pass += (rand(74) + 48).chr }
+
+ unless @config[:capass]
+ raise "No passfile"
+ end
+ Puppet::SSLCertificates.mkdir(File.dirname(@config[:capass]))
+ File.open(@config[:capass], "w", 0600) { |f| f.print pass }
+ return pass
+ end
+
+ def getcert
+ if FileTest.exists?(@config[:cacert])
+ @cert = OpenSSL::X509::Certificate.new(
+ File.read(@config[:cacert])
+ )
+ else
+ self.mkrootcert
+ end
+ end
+
+ def getclientcsr(host)
+ csrfile = host2csrfile(host)
+ unless File.exists?(csrfile)
+ return nil
+ end
+
+ return OpenSSL::X509::Request.new(File.read(csrfile))
+ end
+
+ def getclientcert(host)
+ certfile = host2certfile(host)
+ unless File.exists?(certfile)
+ return [nil, nil]
+ end
+
+ return [OpenSSL::X509::Certificate.new(File.read(certfile)), @cert]
+ end
+
+ def list
+ return Dir.entries(Puppet[:csrdir]).reject { |file|
+ file =~ /^\.+$/
+ }.collect { |file|
+ file.sub(/\.pem$/, '')
+ }
+ end
+
+ def mkrootcert
+ cert = Certificate.new(
+ :name => "CAcert",
+ :cert => @config[:cacert],
+ :encrypt => @config[:passfile],
+ :key => @config[:cakey],
+ :selfsign => true,
+ :length => 1825,
+ :type => :ca
+ )
+ @cert = cert.mkselfsigned
+ File.open(@config[:cacert], "w", 0660) { |f|
+ f.puts @cert.to_pem
+ }
+ @key = cert.key
+ return cert
+ end
+
+ def removeclientcsr(host)
+ csrfile = host2csrfile(host)
+ unless File.exists?(csrfile)
+ raise Puppet::Error, "No certificate request for %s" % host
+ end
+
+ File.unlink(csrfile)
+ end
+
+ def setconfig(hash)
+ @config = {}
+ @@params.each { |param|
+ if hash.include?(param)
+ begin
+ @config[param] = hash[param]
+ Puppet[param] = hash[param]
+ hash.delete(param)
+ rescue => detail
+ puts detail
+ exit
+ end
+ else
+ begin
+ @config[param] = Puppet[param]
+ rescue => detail
+ puts detail
+ exit
+ end
+ end
+ }
+
+ if hash.include?(:password)
+ @config[:password] = hash[:password]
+ hash.delete(:password)
+ end
+
+ if hash.length > 0
+ raise ArgumentError, "Unknown parameters %s" % hash.keys.join(",")
+ end
+
+ [:cadir, :csrdir, :signeddir].each { |dir|
+ unless @config[dir]
+ raise "%s is undefined" % dir
+ end
+ unless FileTest.exists?(@config[dir])
+ Puppet::SSLCertificates.mkdir(@config[dir])
+ end
+ }
+ end
+
+ def sign(csr)
+ unless csr.is_a?(OpenSSL::X509::Request)
+ raise Puppet::Error,
+ "CA#sign only accepts OpenSSL::X509::Request objects, not %s" %
+ csr.class
+ end
+
+ unless csr.verify(csr.public_key)
+ raise Puppet::Error, "CSR sign verification failed"
+ end
+
+ # i should probably check key length...
+
+ # read the ca cert in
+ cacert = OpenSSL::X509::Certificate.new(
+ File.read(@config[:cacert])
+ )
+
+ cakey = nil
+ if @config[:password]
+ cakey = OpenSSL::PKey::RSA.new(
+ File.read(@config[:cakey]), @config[:password]
+ )
+ else
+ cakey = OpenSSL::PKey::RSA.new(
+ File.read(@config[:cakey])
+ )
+ end
+
+ unless cacert.check_private_key(cakey)
+ raise Puppet::Error, "CA Certificate is invalid"
+ end
+
+ serial = File.read(@config[:serial]).chomp.hex
+ newcert = Puppet::SSLCertificates.mkcert(
+ :type => :server,
+ :name => csr.subject,
+ :days => @config[:ca_days],
+ :issuer => cacert,
+ :serial => serial,
+ :publickey => csr.public_key
+ )
+
+ # increment the serial
+ File.open(@config[:serial], "w") { |f|
+ f << "%04X" % (serial + 1)
+ }
+
+ newcert.sign(cakey, OpenSSL::Digest::SHA1.new)
+
+ self.storeclientcert(newcert)
+
+ return [newcert, cacert]
+ end
+
+ def storeclientcsr(csr)
+ host = thing2name(csr)
+
+ csrfile = host2csrfile(host)
+ if File.exists?(csrfile)
+ raise Puppet::Error, "Certificate request for %s already exists" % host
+ end
+
+ File.open(csrfile, "w", 0660) { |f|
+ f.print csr.to_pem
+ }
+ end
+
+ def storeclientcert(cert)
+ host = thing2name(cert)
+
+ certfile = host2certfile(host)
+ if File.exists?(certfile)
+ Puppet.notice "Overwriting signed certificate %s for %s" %
+ [certfile, host]
+ end
+
+ File.open(certfile, "w", 0660) { |f|
+ f.print cert.to_pem
+ }
+ end
+end
+
+# $Id$
diff --git a/lib/puppet/sslcertificates/certificate.rb b/lib/puppet/sslcertificates/certificate.rb
new file mode 100644
index 000000000..65ceb44b9
--- /dev/null
+++ b/lib/puppet/sslcertificates/certificate.rb
@@ -0,0 +1,283 @@
+class Puppet::SSLCertificates::Certificate
+ SSLCertificates = Puppet::SSLCertificates
+
+ attr_accessor :certfile, :keyfile, :name, :dir, :hash, :type
+ attr_accessor :key, :cert, :csr, :cacert
+
+ @@params2names = {
+ :name => "CN",
+ :state => "ST",
+ :country => "C",
+ :email => "emailAddress",
+ :org => "O",
+ :city => "L",
+ :ou => "OU"
+ }
+
+ def certname
+ OpenSSL::X509::Name.new self.subject
+ end
+
+ def delete
+ [@certfile,@keyfile].each { |file|
+ if FileTest.exists?(file)
+ File.unlink(file)
+ end
+ }
+
+ if defined? @hash and @hash
+ if FileTest.symlink?(@hash)
+ File.unlink(@hash)
+ end
+ end
+ end
+
+ def exists?
+ return FileTest.exists?(@certfile)
+ end
+
+ def getkey
+ unless FileTest.exists?(@keyfile)
+ self.mkkey()
+ end
+ if @password
+ @key = OpenSSL::PKey::RSA.new(
+ File.read(@keyfile),
+ @password
+ )
+ else
+ @key = OpenSSL::PKey::RSA.new(
+ File.read(@keyfile)
+ )
+ end
+ end
+
+ def initialize(hash)
+ unless hash.include?(:name)
+ raise "You must specify the common name for the certificate"
+ end
+ @name = hash[:name]
+
+ # init a few variables
+ @cert = @key = @csr = nil
+
+ if hash.include?(:cert)
+ @certfile = hash[:cert]
+ @dir = File.dirname(@certfile)
+ else
+ @dir = hash[:dir] || Puppet[:certdir]
+ @certfile = File.join(@dir, @name)
+ end
+
+ @cacertfile ||= File.join(Puppet[:certdir], "ca.pem")
+
+ unless FileTest.directory?(@dir)
+ Puppet::SSLCertificates.mkdir(@dir)
+ end
+
+ unless @certfile =~ /\.pem$/
+ @certfile += ".pem"
+ end
+ @keyfile = hash[:key] || File.join(
+ Puppet[:privatekeydir], [@name,"pem"].join(".")
+ )
+ unless FileTest.directory?(@dir)
+ Puppet::SSLCertificates.mkdir(@dir)
+ end
+
+ [@keyfile].each { |file|
+ dir = File.dirname(file)
+
+ unless FileTest.directory?(dir)
+ Puppet::SSLCertificates.mkdir(dir)
+ end
+ }
+
+ @days = hash[:length] || 365
+ @selfsign = hash[:selfsign] || false
+ @encrypt = hash[:encrypt] || false
+ @replace = hash[:replace] || false
+ @issuer = hash[:issuer] || nil
+
+ if hash.include?(:type)
+ case hash[:type]
+ when :ca, :client, :server: @type = hash[:type]
+ else
+ raise "Invalid Cert type %s" % hash[:type]
+ end
+ else
+ @type = :client
+ end
+
+ @params = {:name => @name}
+ [:state, :country, :email, :org, :ou].each { |param|
+ if hash.include?(param)
+ @params[param] = hash[param]
+ end
+ }
+
+ if @encrypt
+ if @encrypt =~ /^\//
+ File.open(@encrypt) { |f|
+ @password = f.read.chomp
+ }
+ else
+ raise ":encrypt must be a path to a pass phrase file"
+ end
+ else
+ @password = nil
+ end
+
+ if hash.include?(:selfsign)
+ @selfsign = hash[:selfsign]
+ else
+ @selfsign = false
+ end
+ end
+
+ # this only works for servers, not for users
+ def mkcsr
+ unless defined? @key and @key
+ self.getkey
+ end
+
+ name = OpenSSL::X509::Name.new self.subject
+
+ @csr = OpenSSL::X509::Request.new
+ @csr.version = 0
+ @csr.subject = name
+ @csr.public_key = @key.public_key
+ @csr.sign(@key, OpenSSL::Digest::SHA1.new)
+
+ #File.open(@csrfile, "w") { |f|
+ # f << @csr.to_pem
+ #}
+
+ unless @csr.verify(@key.public_key)
+ raise Puppet::Error, "CSR sign verification failed"
+ end
+
+ return @csr
+ end
+
+ def mkkey
+ # @key is the file
+
+ @key = OpenSSL::PKey::RSA.new(1024)
+# { |p,n|
+# case p
+# when 0; Puppet.info "key info: ." # BN_generate_prime
+# when 1; Puppet.info "key info: +" # BN_generate_prime
+# when 2; Puppet.info "key info: *" # searching good prime,
+# # n = #of try,
+# # but also data from BN_generate_prime
+# when 3; Puppet.info "key info: \n" # found good prime, n==0 - p, n==1 - q,
+# # but also data from BN_generate_prime
+# else; Puppet.info "key info: *" # BN_generate_prime
+# end
+# }
+
+ if @password
+ #passwdproc = proc { @password }
+ keytext = @key.export(
+ OpenSSL::Cipher::DES.new(:EDE3, :CBC),
+ @password
+ )
+ File.open(@keyfile, "w", 0400) { |f|
+ f << keytext
+ }
+ else
+ File.open(@keyfile, "w", 0400) { |f|
+ f << @key.to_pem
+ }
+ end
+
+ #cmd = "#{ossl} genrsa -out #{@key} 1024"
+ end
+
+ def mkselfsigned
+ unless defined? @key and @key
+ self.getkey
+ end
+
+ if defined? @cert and @cert
+ raise Puppet::Error, "Cannot replace existing certificate"
+ end
+
+ args = {
+ :name => self.certname,
+ :days => @days,
+ :issuer => nil,
+ :serial => 0x0,
+ :publickey => @key.public_key
+ }
+ if @type
+ args[:type] = @type
+ else
+ args[:type] = :server
+ end
+ @cert = SSLCertificates.mkcert(args)
+
+ @cert.sign(@key, OpenSSL::Digest::SHA1.new) if @selfsign
+
+ return @cert
+ end
+
+ def subject(string = false)
+ subj = @@params2names.collect { |param, name|
+ if @params.include?(param)
+ [name, @params[param]]
+ end
+ }.reject { |ary| ary.nil? }
+
+ if string
+ return "/" + subj.collect { |ary|
+ "%s=%s" % ary
+ }.join("/") + "/"
+ else
+ return subj
+ end
+ end
+
+ # verify that we can track down the cert chain or whatever
+ def verify
+ "openssl verify -verbose -CAfile /home/luke/.puppet/ssl/certs/ca.pem -purpose sslserver culain.madstop.com.pem"
+ end
+
+ def write
+ files = {
+ @certfile => @cert,
+ @keyfile => @key,
+ }
+ if defined? @cacert
+ files[@cacertfile] = @cacert
+ end
+
+ files.each { |file,thing|
+ if defined? thing and thing
+ if FileTest.exists?(file)
+ next
+ end
+
+ text = nil
+
+ if thing.is_a?(OpenSSL::PKey::RSA) and @password
+ text = thing.export(
+ OpenSSL::Cipher::DES.new(:EDE3, :CBC),
+ @password
+ )
+ else
+ text = thing.to_pem
+ end
+
+ File.open(file, "w", 0660) { |f| f.print text }
+ end
+ }
+
+ if defined? @cacert
+ SSLCertificates.mkhash(Puppet[:certdir], @cacert, @cacertfile)
+ end
+ end
+end
+
+# $Id$
diff --git a/lib/puppet/storage.rb b/lib/puppet/storage.rb
index a12a94178..0c06f5173 100644
--- a/lib/puppet/storage.rb
+++ b/lib/puppet/storage.rb
@@ -52,8 +52,8 @@ module Puppet
begin
@@state = YAML.load(file)
rescue => detail
- Puppet.err "Checksumfile %s is corrupt; replacing" %
- Puppet[:statefile]
+ Puppet.err "Checksumfile %s is corrupt (%s); replacing" %
+ [Puppet[:statefile], detail]
begin
File.rename(Puppet[:statefile],
Puppet[:statefile] + ".bad")
@@ -65,6 +65,11 @@ module Puppet
end
}
+ unless @@state.is_a?(Hash)
+ Puppet.err "State got corrupted"
+ self.init
+ end
+
#Puppet.debug "Loaded state is %s" % @@state.inspect
end
@@ -73,6 +78,7 @@ module Puppet
end
def self.store
+ Puppet.debug "Storing state"
unless FileTest.directory?(File.dirname(Puppet[:statefile]))
begin
Puppet.recmkdir(File.dirname(Puppet[:statefile]))
@@ -89,7 +95,7 @@ module Puppet
end
Puppet::Util.lock(
- Puppet[:statefile], File::CREAT|File::WRONLY, 0600
+ Puppet[:statefile], "w", 0600
) { |file|
file.print YAML.dump(@@state)
}
diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb
index 39aa27858..8caa28588 100644
--- a/lib/puppet/transaction.rb
+++ b/lib/puppet/transaction.rb
@@ -121,9 +121,9 @@ class Transaction
# these children are all Puppet::Type instances
# not all of the children will return a change, and Containers
# return transactions
- ary = child.evaluate
- child.cache(:checked, now)
- ary
+ #ary = child.evaluate
+ #ary
+ child.evaluate
}.flatten.reject { |child|
child.nil? # remove empties
}
diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb
index 5c9bcf027..424646742 100644
--- a/lib/puppet/type.rb
+++ b/lib/puppet/type.rb
@@ -1289,31 +1289,33 @@ class Type < Puppet::Element
# If we've already set the schedule, then just move on
return if self[:schedule].is_a?(Puppet.type(:schedule))
+ return unless self[:schedule]
+
# Schedules don't need to be scheduled
- return if self.is_a?(Puppet.type(:schedule))
+ #return if self.is_a?(Puppet.type(:schedule))
# Nor do components
- return if self.is_a?(Puppet.type(:component))
+ #return if self.is_a?(Puppet.type(:component))
- if self[:schedule]
- if sched = Puppet.type(:schedule)[self[:schedule]]
- self[:schedule] = sched
- else
- self.fail "Could not find schedule %s" % self[:schedule]
- end
- elsif Puppet[:schedule] and ! Puppet[:ignoreschedules]
- # We handle schedule defaults here mostly because otherwise things
- # will behave very very erratically during testing.
- if sched = Puppet.type(:schedule)[Puppet[:schedule]]
- self[:schedule] = sched
- else
- self.fail "Could not find default schedule %s" % Puppet[:schedule]
- end
+ if sched = Puppet.type(:schedule)[self[:schedule]]
+ self[:schedule] = sched
else
- # While it's unlikely we won't have any schedule (since there's a
- # default), it's at least possible during testing
- return true
- end
+ self.fail "Could not find schedule %s" % self[:schedule]
+ end
+# if self[:schedule]
+# elsif Puppet[:schedule] and ! Puppet[:ignoreschedules]
+# # We handle schedule defaults here mostly because otherwise things
+# # will behave very very erratically during testing.
+# if sched = Puppet.type(:schedule)[Puppet[:schedule]]
+# self[:schedule] = sched
+# else
+# self.fail "Could not find default schedule %s" % Puppet[:schedule]
+# end
+# else
+# # While it's unlikely we won't have any schedule (since there's a
+# # default), it's at least possible during testing
+# return true
+# end
end
# Check whether we are scheduled to run right now or not.
@@ -1533,6 +1535,8 @@ class Type < Puppet::Element
# this returns any changes resulting from testing, thus 'collect'
# rather than 'each'
def evaluate
+ now = Time.now.to_i
+
#Puppet.err "Evaluating %s" % self.path.join(":")
unless defined? @evalcount
self.err "No evalcount defined on '%s' of type '%s'" %
@@ -1576,7 +1580,9 @@ class Type < Puppet::Element
#end
changes << @children.collect { |child|
- child.evaluate
+ ch = child.evaluate
+ child.cache(:checked, now)
+ ch
}
#unless self.class.depthfirst?
# changes << self.collect { |child|
@@ -1604,6 +1610,7 @@ class Type < Puppet::Element
# self.debug "change: %s" % change.state.name
#}
end
+ self.cache(:checked, now)
return changes.flatten
end
@@ -2057,6 +2064,7 @@ require 'puppet/type/group'
require 'puppet/type/package'
require 'puppet/type/pfile'
require 'puppet/type/pfilebucket'
+require 'puppet/type/schedule'
require 'puppet/type/service'
require 'puppet/type/symlink'
require 'puppet/type/user'
diff --git a/lib/puppet/type/package/rpm.rb b/lib/puppet/type/package/rpm.rb
index cf84c3536..11906558e 100755
--- a/lib/puppet/type/package/rpm.rb
+++ b/lib/puppet/type/package/rpm.rb
@@ -3,13 +3,14 @@ module Puppet
def query
fields = {
:name => "NAME",
- :ensure => "VERSION",
+ :version => "VERSION",
:description => "DESCRIPTION"
}
cmd = "rpm -q #{self.name} --qf '%s\n'" %
"%{NAME} %{VERSION}-%{RELEASE}"
+ self.debug "Executing %s" % cmd.inspect
# list out all of the packages
output = %x{#{cmd} 2>/dev/null}.chomp
@@ -22,7 +23,7 @@ module Puppet
regex = %r{^(\S+)\s+(\S+)}
#fields = [:name, :ensure, :description]
- fields = [:name, :ensure]
+ fields = [:name, :version]
hash = {}
if match = regex.match(output)
fields.zip(match.captures) { |field,value|
@@ -34,6 +35,8 @@ module Puppet
output
end
+ hash[:ensure] = :present
+
return hash
end
diff --git a/lib/puppet/type/pfile.rb b/lib/puppet/type/pfile.rb
index 12b162845..10ca20976 100644
--- a/lib/puppet/type/pfile.rb
+++ b/lib/puppet/type/pfile.rb
@@ -254,7 +254,7 @@ module Puppet
# than this last bit, so it doesn't really make sense.
if child = klass[path]
unless @children.include?(child)
- self.notice "Not managing more explicit file %s" %
+ self.debug "Not managing more explicit file %s" %
path
return nil
end
diff --git a/lib/puppet/type/schedule.rb b/lib/puppet/type/schedule.rb
index 515ae0194..b3bcb9d1b 100755
--- a/lib/puppet/type/schedule.rb
+++ b/lib/puppet/type/schedule.rb
@@ -33,7 +33,7 @@ module Puppet
Puppet automatically creates a schedule for each valid period with the
same name as that period (e.g., hourly and daily). Additionally,
a schedule named *puppet* is created and used as the default,
- with the following attributes:
+ with the following attributes::
schedule { puppet:
period => hourly,
diff --git a/lib/puppet/type/service.rb b/lib/puppet/type/service.rb
index fca9ff4d3..13aad3a00 100644
--- a/lib/puppet/type/service.rb
+++ b/lib/puppet/type/service.rb
@@ -77,7 +77,6 @@ module Puppet
[self.class,should.inspect]
should = 0
end
- self.debug "Service should is %s" % should
return should
end
diff --git a/lib/puppet/util.rb b/lib/puppet/util.rb
index 650eb484a..9974d55e3 100644
--- a/lib/puppet/util.rb
+++ b/lib/puppet/util.rb
@@ -89,13 +89,29 @@ module Util
def self.lock(*opts)
lock = opts[0] + ".lock"
while File.exists?(lock)
+ stamp = File.stat(lock).mtime.to_i
+ if Time.now.to_i - stamp > 5
+ Puppet.notice "Lock file %s is %s seconds old; removing"
+ File.delete(lock)
+ end
#Puppet.debug "%s is locked" % opts[0]
sleep 0.1
end
File.open(lock, "w") { |f| f.print " "; f.flush }
+ writing = false
+ if opts[1] == "w"
+ writing = true
+ tmp = opts[0] + ".tmp"
+ orig = opts[0]
+ opts[0] = tmp
+ end
begin
File.open(*opts) { |file| yield file }
- rescue
+ if writing
+ File.rename(tmp, orig)
+ end
+ rescue => detail
+ Puppet.err "Storage error: %s" % detail
raise
ensure
# I don't really understand how the lock file could disappear,
diff --git a/test/executables/puppetd.rb b/test/executables/puppetd.rb
index 68b9b5f08..90733ed19 100755
--- a/test/executables/puppetd.rb
+++ b/test/executables/puppetd.rb
@@ -25,6 +25,7 @@ class TestPuppetDExe < Test::Unit::TestCase
cmd = "puppetd"
cmd += " --verbose"
+ cmd += " --onetime"
#cmd += " --fqdn %s" % fqdn
cmd += " --port %s" % @@port
cmd += " --confdir %s" % Puppet[:puppetconf]
diff --git a/test/language/snippets.rb b/test/language/snippets.rb
index 413fd1035..0e217bbbe 100755
--- a/test/language/snippets.rb
+++ b/test/language/snippets.rb
@@ -442,10 +442,15 @@ class TestSnippets < Test::Unit::TestCase
Puppet::Type.eachtype { |type|
type.each { |obj|
- unless obj.name == "puppet[top]"
+ unless obj.name == "puppet[top]" or
+ obj.is_a?(Puppet.type(:schedule))
assert(obj.parent, "%s has no parent" % obj.name)
end
assert(obj.name)
+
+ if obj.is_a?(Puppet.type(:file))
+ @@tmpfiles << obj.name
+ end
}
}
assert_nothing_raised {