summaryrefslogtreecommitdiffstats
path: root/lib/puppet/client.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/puppet/client.rb')
-rw-r--r--lib/puppet/client.rb594
1 files changed, 47 insertions, 547 deletions
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$