From 7e07e3dc843798bdbc7a03428ca054adaff2fb72 Mon Sep 17 00:00:00 2001 From: luke Date: Thu, 8 Feb 2007 01:39:39 +0000 Subject: Moving all of the client and server code into a single network/ directory. In other words, more code structure cleanup. git-svn-id: https://reductivelabs.com/svn/puppet/trunk@2179 980ebf18-57e1-0310-9a29-db15c13687c0 --- lib/puppet.rb | 6 +- lib/puppet/client.rb | 194 ---------- lib/puppet/client/ca.rb | 21 - lib/puppet/client/dipper.rb | 80 ---- lib/puppet/client/file.rb | 20 - lib/puppet/client/log.rb | 17 - lib/puppet/client/master.rb | 654 -------------------------------- lib/puppet/client/proxy.rb | 28 -- lib/puppet/client/reporter.rb | 34 -- lib/puppet/client/resource.rb | 65 ---- lib/puppet/client/runner.rb | 17 - lib/puppet/client/status.rb | 7 - lib/puppet/daemon.rb | 4 +- lib/puppet/dsl.rb | 2 +- lib/puppet/network/client.rb | 190 ++++++++++ lib/puppet/network/client/ca.rb | 23 ++ lib/puppet/network/client/dipper.rb | 76 ++++ lib/puppet/network/client/file.rb | 20 + lib/puppet/network/client/log.rb | 17 + lib/puppet/network/client/master.rb | 654 ++++++++++++++++++++++++++++++++ lib/puppet/network/client/proxy.rb | 28 ++ lib/puppet/network/client/reporter.rb | 34 ++ lib/puppet/network/client/resource.rb | 65 ++++ lib/puppet/network/client/runner.rb | 17 + lib/puppet/network/client/status.rb | 7 + lib/puppet/network/networkclient.rb | 167 ++++++++ lib/puppet/network/server.rb | 207 ++++++++++ lib/puppet/network/server/authconfig.rb | 173 +++++++++ lib/puppet/network/server/authstore.rb | 227 +++++++++++ lib/puppet/network/server/ca.rb | 152 ++++++++ lib/puppet/network/server/filebucket.rb | 167 ++++++++ lib/puppet/network/server/fileserver.rb | 589 ++++++++++++++++++++++++++++ lib/puppet/network/server/logger.rb | 52 +++ lib/puppet/network/server/master.rb | 212 +++++++++++ lib/puppet/network/server/report.rb | 174 +++++++++ lib/puppet/network/server/resource.rb | 190 ++++++++++ lib/puppet/network/server/rights.rb | 74 ++++ lib/puppet/network/server/runner.rb | 61 +++ lib/puppet/network/server/servlet.rb | 277 ++++++++++++++ lib/puppet/networkclient.rb | 181 --------- lib/puppet/reports/log.rb | 2 +- lib/puppet/reports/rrdgraph.rb | 2 +- lib/puppet/reports/store.rb | 2 +- lib/puppet/reports/tagmail.rb | 2 +- lib/puppet/server.rb | 221 ----------- lib/puppet/server/authconfig.rb | 177 --------- lib/puppet/server/authstore.rb | 229 ----------- lib/puppet/server/ca.rb | 155 -------- lib/puppet/server/filebucket.rb | 169 --------- lib/puppet/server/fileserver.rb | 591 ----------------------------- lib/puppet/server/logger.rb | 54 --- lib/puppet/server/master.rb | 214 ----------- lib/puppet/server/report.rb | 176 --------- lib/puppet/server/resource.rb | 191 ---------- lib/puppet/server/rights.rb | 78 ---- lib/puppet/server/runner.rb | 64 ---- lib/puppet/server/servlet.rb | 277 -------------- lib/puppet/type/pfile.rb | 18 +- lib/puppet/type/pfile/source.rb | 8 +- lib/puppet/type/pfilebucket.rb | 6 +- lib/puppet/type/tidy.rb | 2 +- lib/puppet/util/log.rb | 2 +- 62 files changed, 3881 insertions(+), 3942 deletions(-) delete mode 100644 lib/puppet/client.rb delete mode 100644 lib/puppet/client/ca.rb delete mode 100644 lib/puppet/client/dipper.rb delete mode 100644 lib/puppet/client/file.rb delete mode 100644 lib/puppet/client/log.rb delete mode 100644 lib/puppet/client/master.rb delete mode 100644 lib/puppet/client/proxy.rb delete mode 100644 lib/puppet/client/reporter.rb delete mode 100644 lib/puppet/client/resource.rb delete mode 100644 lib/puppet/client/runner.rb delete mode 100644 lib/puppet/client/status.rb create mode 100644 lib/puppet/network/client.rb create mode 100644 lib/puppet/network/client/ca.rb create mode 100644 lib/puppet/network/client/dipper.rb create mode 100644 lib/puppet/network/client/file.rb create mode 100644 lib/puppet/network/client/log.rb create mode 100644 lib/puppet/network/client/master.rb create mode 100644 lib/puppet/network/client/proxy.rb create mode 100644 lib/puppet/network/client/reporter.rb create mode 100644 lib/puppet/network/client/resource.rb create mode 100644 lib/puppet/network/client/runner.rb create mode 100644 lib/puppet/network/client/status.rb create mode 100644 lib/puppet/network/networkclient.rb create mode 100644 lib/puppet/network/server.rb create mode 100644 lib/puppet/network/server/authconfig.rb create mode 100755 lib/puppet/network/server/authstore.rb create mode 100644 lib/puppet/network/server/ca.rb create mode 100755 lib/puppet/network/server/filebucket.rb create mode 100755 lib/puppet/network/server/fileserver.rb create mode 100755 lib/puppet/network/server/logger.rb create mode 100644 lib/puppet/network/server/master.rb create mode 100755 lib/puppet/network/server/report.rb create mode 100755 lib/puppet/network/server/resource.rb create mode 100755 lib/puppet/network/server/rights.rb create mode 100755 lib/puppet/network/server/runner.rb create mode 100644 lib/puppet/network/server/servlet.rb delete mode 100644 lib/puppet/networkclient.rb delete mode 100644 lib/puppet/server.rb delete mode 100644 lib/puppet/server/authconfig.rb delete mode 100755 lib/puppet/server/authstore.rb delete mode 100644 lib/puppet/server/ca.rb delete mode 100755 lib/puppet/server/filebucket.rb delete mode 100755 lib/puppet/server/fileserver.rb delete mode 100755 lib/puppet/server/logger.rb delete mode 100644 lib/puppet/server/master.rb delete mode 100755 lib/puppet/server/report.rb delete mode 100755 lib/puppet/server/resource.rb delete mode 100755 lib/puppet/server/rights.rb delete mode 100755 lib/puppet/server/runner.rb delete mode 100644 lib/puppet/server/servlet.rb (limited to 'lib') diff --git a/lib/puppet.rb b/lib/puppet.rb index f12645357..d6d9840bb 100644 --- a/lib/puppet.rb +++ b/lib/puppet.rb @@ -210,7 +210,7 @@ module Puppet # Handle restarting. trap(:HUP) do - if client = @services.find { |s| s.is_a? Puppet::Client::MasterClient } and client.running? + if client = @services.find { |s| s.is_a? Puppet::Network::Client::MasterClient } and client.running? client.restart else Puppet.restart @@ -221,7 +221,7 @@ module Puppet trap(:USR1) do done = 0 Puppet.notice "Caught USR1; triggering client run" - @services.find_all { |s| s.is_a? Puppet::Client }.each do |client| + @services.find_all { |s| s.is_a? Puppet::Network::Client }.each do |client| if client.respond_to? :running? if client.running? Puppet.info "Ignoring running %s" % client.class @@ -387,7 +387,7 @@ module Puppet end end -require 'puppet/server' +require 'puppet/network/server' require 'puppet/type' require 'puppet/util/storage' if Puppet[:storeconfigs] diff --git a/lib/puppet/client.rb b/lib/puppet/client.rb deleted file mode 100644 index e668aa27a..000000000 --- a/lib/puppet/client.rb +++ /dev/null @@ -1,194 +0,0 @@ -# the available clients - -require 'puppet' -require 'puppet/networkclient' - -module Puppet - # 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 Puppet::Daemon - - include Puppet::Util - - # 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 :secureinit - attr_accessor :schedule, :lastrun, :local, :stopping - - class << self - attr_reader :drivername, :handler - attr_accessor :netclient - end - - def initcerts - unless self.readcert - #if self.is_a? Puppet::Client::CA - unless self.requestcert - return nil - end - #else - # return nil - #end - #unless self.requestcert - #end - end - - # unless we have a driver, we're a local client and we can't add - # certs anyway, so it doesn't matter - unless @driver - return true - end - - self.setcerts - end - - def initialize(hash) - # to whom do we connect? - @server = nil - @nil = nil - @secureinit = hash[:NoSecureInit] || true - - if hash.include?(:FQDN) - @fqdn = hash[:FQDN] - else - self.fqdn - end - - if hash.include?(:Cache) - @cache = hash[:Cache] - else - @cache = true - end - - driverparam = self.class.drivername - if hash.include?(:Server) - if $noclientnetworking - raise NetworkClientError.new("Networking not available: %s" % - $nonetworking) - end - - args = {:Server => hash[:Server]} - args[:Port] = hash[:Port] || Puppet[:masterport] - - if self.readcert - args[:Certificate] = @cert - args[:Key] = @key - args[:CAFile] = @cacertfile - end - - netclient = nil - unless netclient = self.class.netclient - unless handler = self.class.handler - raise Puppet::DevError, - "Class %s has no handler defined" % self.class - end - namespace = self.class.handler.interface.prefix - netclient = Puppet::NetworkClient.netclient(namespace) - self.class.netclient = netclient - end - @driver = netclient.new(args) - @local = false - elsif hash.include?(driverparam) - @driver = hash[driverparam] - @local = true - else - raise ClientError, "%s must be passed a Server or %s" % - [self.class, driverparam] - end - end - - # Are we a local client? - def local? - if defined? @local and @local - true - else - false - end - end - - # A wrapper method to run and then store the last run time - def runnow - if self.stopping - Puppet.notice "In shutdown progress; skipping run" - return - end - begin - self.run - self.lastrun = Time.now.to_i - rescue => detail - if Puppet[:trace] - puts detail.backtrace - end - Puppet.err "Could not run %s: %s" % [self.class, detail] - end - end - - def run - raise Puppet::DevError, "Client type %s did not override run" % - self.class - end - - def scheduled? - if sched = self.schedule - return sched.match?(self.lastrun) - else - return true - end - end - - def setcerts - @driver.cert = @cert - @driver.key = @key - @driver.ca_file = @cacertfile - end - - def shutdown - if self.stopping - Puppet.notice "Already in shutdown" - else - self.stopping = true - if self.respond_to? :running? and self.running? - Puppet::Util::Storage.store - end - rmpidfile() - end - end - - # Start listening for events. We're pretty much just listening for - # timer events here. - def start - setpidfile() - # Create our timer. Puppet will handle observing it and such. - timer = Puppet.newtimer( - :interval => Puppet[:runinterval], - :tolerance => 1, - :start? => true - ) do - if self.scheduled? - self.runnow - end - end - - # Run once before we start following the timer - self.runnow - 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/runner' - require 'puppet/client/status' - require 'puppet/client/reporter' - require 'puppet/client/resource' - end -end - -# $Id$ diff --git a/lib/puppet/client/ca.rb b/lib/puppet/client/ca.rb deleted file mode 100644 index 8904c4a13..000000000 --- a/lib/puppet/client/ca.rb +++ /dev/null @@ -1,21 +0,0 @@ -class Puppet::Client::CA < Puppet::Client::ProxyClient - @drivername = :CA - - # set up the appropriate interface methods - @handler = Puppet::Server::CA - self.mkmethods - - def initialize(hash = {}) - if hash.include?(:CA) - if hash[:CA].is_a? Hash - hash[:CA] = Puppet::Server::CA.new(hash[:CA]) - else - hash[:CA] = Puppet::Server::CA.new() - end - end - - super(hash) - end -end - -# $Id$ diff --git a/lib/puppet/client/dipper.rb b/lib/puppet/client/dipper.rb deleted file mode 100644 index 1422c24f2..000000000 --- a/lib/puppet/client/dipper.rb +++ /dev/null @@ -1,80 +0,0 @@ -module Puppet - class Client - # The client class for filebuckets. - class Dipper < Puppet::Client - @drivername = :Bucket - - @handler = Puppet::Server::FileBucket - - attr_accessor :name - - # Create our bucket client - def initialize(hash = {}) - if hash.include?(:Path) - bucket = Puppet::Server::FileBucket.new( - :Path => hash[:Path] - ) - hash.delete(:Path) - hash[:Bucket] = bucket - end - - super(hash) - end - - # Back up a file to our bucket - def backup(file) - unless FileTest.exists?(file) - raise(BucketError, "File %s does not exist" % file) - end - contents = File.read(file) - unless local? - contents = Base64.encode64(contents) - end - return @driver.addfile(contents,file) - end - - # Restore the file - def restore(file,sum) - restore = true - if FileTest.exists?(file) - cursum = Digest::MD5.hexdigest(File.read(file)) - - # if the checksum has changed... - # this might be extra effort - if cursum == sum - restore = false - end - end - - if restore - if newcontents = @driver.getfile(sum) - unless local? - newcontents = Base64.decode64(newcontents) - end - 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 - 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 deleted file mode 100644 index 116624003..000000000 --- a/lib/puppet/client/file.rb +++ /dev/null @@ -1,20 +0,0 @@ -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 deleted file mode 100644 index e20c0532c..000000000 --- a/lib/puppet/client/log.rb +++ /dev/null @@ -1,17 +0,0 @@ -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 deleted file mode 100644 index 046e0c5aa..000000000 --- a/lib/puppet/client/master.rb +++ /dev/null @@ -1,654 +0,0 @@ -# The client for interacting with the puppetmaster config server. -require 'sync' -require 'timeout' - -class Puppet::Client::MasterClient < Puppet::Client - unless defined? @@sync - @@sync = Sync.new - end - - @handler = Puppet::Server::Master - - Puppet.setdefaults("puppetd", - :puppetdlockfile => [ "$statedir/puppetdlock", - "A lock file to temporarily stop puppetd from doing anything."], - :usecacheonfailure => [true, - "Whether to use the cached configuration when the remote - configuration will not compile. This option is useful for testing - new configurations, where you want to fix the broken configuration - rather than reverting to a known-good one." - ], - :downcasefacts => [false, - "Whether facts should be made all lowercase when sent to the server."] - ) - - Puppet.setdefaults(:puppetd, - :configtimeout => [30, - "How long the client should wait for the configuration to be retrieved - before considering it a failure. This can help reduce flapping if too - many clients contact the server at one time." - ], - :reportserver => ["$server", - "The server to which to send transaction reports." - ], - :report => [false, - "Whether to send reports after every transaction." - ] - ) - - # Plugin information. - Puppet.setdefaults("puppet", - :pluginpath => ["$vardir/plugins", - "Where Puppet should look for plugins. Multiple directories should - be colon-separated, like normal PATH variables."], - :plugindest => ["$vardir/plugins", - "Where Puppet should store plugins that it pulls down from the central - server."], - :pluginsource => ["puppet://$server/plugins", - "From where to retrieve plugins. The standard Puppet ``file`` type - is used for retrieval, so anything that is a valid file source can - be used here."], - :pluginsync => [false, - "Whether plugins should be synced with the central server."], - :pluginsignore => [".svn CVS", - "What files to ignore when pulling down plugins."] - ) - - # Central fact information. - Puppet.setdefaults("puppet", - :factpath => ["$vardir/facts", - "Where Puppet should look for facts. Multiple directories should - be colon-separated, like normal PATH variables."], - :factdest => ["$vardir/facts", - "Where Puppet should store facts that it pulls down from the central - server."], - :factsource => ["puppet://$server/facts", - "From where to retrieve facts. The standard Puppet ``file`` type - is used for retrieval, so anything that is a valid file source can - be used here."], - :factsync => [false, - "Whether facts should be synced with the central server."], - :factsignore => [".svn CVS", - "What files to ignore when pulling down facts."] - ) - - @drivername = :Master - - attr_accessor :objects - attr_reader :compile_time - - class << self - # Puppetd should only have one instance running, and we need a way - # to retrieve it. - attr_accessor :instance - include Puppet::Util - end - - def self.facts - # Retrieve the facts from the central server. - if Puppet[:factsync] - self.getfacts() - end - - down = Puppet[:downcasefacts] - - facts = {} - Facter.each { |name,fact| - if down - facts[name] = fact.to_s.downcase - else - facts[name] = fact.to_s - end - } - - # Add our client version to the list of facts, so people can use it - # in their manifests - facts["clientversion"] = Puppet.version.to_s - - facts - end - - # This method actually applies the configuration. - def apply(tags = nil, ignoreschedules = false) - unless defined? @objects - raise Puppet::Error, "Cannot apply; objects not defined" - end - - transaction = @objects.evaluate - - if tags - transaction.tags = tags - end - - if ignoreschedules - transaction.ignoreschedules = true - end - - transaction.addtimes :config_retrieval => @configtime - - 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[:trace] - puts detail.backtrace - end - ensure - Puppet::Util::Storage.store - end - - if Puppet[:report] - report(transaction) - end - - return transaction - ensure - if defined? transaction and transaction - transaction.cleanup - end - end - - # Cache the config - def cache(text) - Puppet.info "Caching configuration at %s" % self.cachefile - confdir = File.dirname(Puppet[:localconfig]) - 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 - - def clear - #@objects = nil - @objects.remove(true) - Puppet::Type.allclear - end - - # Initialize and load storage - def dostorage - begin - Puppet::Util::Storage.load - @compile_time ||= Puppet::Util::Storage.cache(:configuration)[:compile_time] - rescue => detail - if Puppet[:trace] - puts detail.backtrace - end - Puppet.err "Corrupt state file %s: %s" % [Puppet[:statefile], detail] - begin - File.unlink(Puppet[:statefile]) - 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 self.compile_time - return false - end - - # We're willing to give a 2 second drift - if @driver.freshness - @compile_time.to_i < 1 - return true - else - return false - end - end - - # Let the daemon run again, freely in the filesystem. Frolick, little - # daemon! - def enable - Puppet::Util::Pidlock.new(Puppet[:puppetdlockfile]).unlock(:anonymous => true) - end - - # Stop the daemon from making any configuration runs. - def disable - Puppet::Util::Pidlock.new(Puppet[:puppetdlockfile]).lock(:anonymous => true) - end - - # Retrieve the config from a remote server. If this fails, then - # use the cached copy. - def getconfig - dostorage() - if self.fresh? - Puppet.info "Config is up to date" - unless defined? @objects - begin - @objects = YAML.load(self.retrievecache).to_type - rescue => detail - Puppet.warning "Could not load cached configuration: %s" % detail - end - end - return - end - Puppet.debug("getting config") - - # Retrieve the plugins. - if Puppet[:pluginsync] - getplugins() - end - - facts = self.class.facts - - unless facts.length > 0 - raise Puppet::ClientError.new( - "Could not retrieve any facts" - ) - end - - unless objects = get_actual_config(facts) - @objects = nil - return - end - - unless objects.is_a?(Puppet::TransBucket) - raise NetworkClientError, - "Invalid returned objects of type %s" % objects.class - end - - self.setclasses(objects.classes) - - # Clear all existing objects, so we can recreate our stack. - if defined? @objects - Puppet::Type.allclear - - # Make sure all of the objects are really gone. - @objects.remove(true) - end - @objects = nil - - # Now convert the objects to real Puppet objects - @objects = objects.to_type - - if @objects.nil? - raise Puppet::Error, "Configuration could not be processed" - end - - # and perform any necessary final actions before we evaluate. - @objects.finalize - - return @objects - end - - # A simple proxy method, so it's easy to test. - def getplugins - self.class.getplugins - end - - # Just so we can specify that we are "the" instance. - def initialize(*args) - Puppet.config.use(:puppet, :sslcertificates, :puppetd) - super - - # This might be nil - @configtime = 0 - - self.class.instance = self - @running = false - - mkdefault_objects - end - - # Make the default objects necessary for function. - def mkdefault_objects - # First create the default scheduling objects - Puppet::Type.type(:schedule).mkdefaultschedules - - # And filebuckets - Puppet::Type.type(:filebucket).mkdefaultbucket - end - - # Mark that we should restart. The Puppet module checks whether we're running, - # so this only gets called if we're in the middle of a run. - def restart - # If we're currently running, then just mark for later - Puppet.notice "Received signal to restart; waiting until run is complete" - @restart = true - end - - # Should we restart? - def restart? - if defined? @restart - @restart - else - false - end - 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(tags = nil, ignoreschedules = false) - lockfile = Puppet::Util::Pidlock.new(Puppet[:puppetdlockfile]) - - Puppet::Util.sync(:puppetrun).synchronize(Sync::EX) do - if !lockfile.lock - Puppet.notice "Lock file %s exists; skipping configuration run" % - lockfile.lockfile - else - @running = true - @configtime = thinmark do - self.getconfig - end - - if defined? @objects and @objects - unless @local - Puppet.notice "Starting configuration run" - end - benchmark(:notice, "Finished configuration run") do - self.apply(tags, ignoreschedules) - end - end - @running = false - end - - lockfile.unlock - - # Did we get HUPped during the run? If so, then restart now that we're - # done with the run. - if self.restart? - Process.kill(:HUP, $$) - end - end - end - - def running? - @running - end - - # Store the classes in the classfile, but only if we're not local. - def setclasses(ary) - if @local - return - end - unless ary and ary.length > 0 - Puppet.info "No classes to store" - return - end - 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 - - private - - # Download files from the remote server, returning a list of all - # changed files. - def self.download(args) - objects = Puppet::Type.type(:component).create( - :name => "#{args[:name]}_collector" - ) - hash = { - :path => args[:dest], - :recurse => true, - :source => args[:source], - :tag => "#{args[:name]}s", - :owner => Process.uid, - :group => Process.gid, - :backup => false - } - - if args[:ignore] - hash[:ignore] = args[:ignore].split(/\s+/) - end - objects.push Puppet::Type.type(:file).create(hash) - - Puppet.info "Retrieving #{args[:name]}s" - - begin - trans = objects.evaluate - trans.ignoretags = true - Timeout::timeout(self.timeout) do - trans.evaluate - end - rescue Puppet::Error, Timeout::Error => detail - if Puppet[:debug] - puts detail.backtrace - end - Puppet.err "Could not retrieve #{args[:name]}s: %s" % detail - end - - # Now source all of the changed objects, but only source those - # that are top-level. - files = [] - trans.changed?.find_all do |object| - yield object if block_given? - files << object[:path] - end - trans.cleanup - - # Now clean up after ourselves - objects.remove - files - end - - # Retrieve facts from the central server. - def self.getfacts - # Clear all existing definitions. - Facter.clear - - # Download the new facts - path = Puppet[:factpath].split(":") - files = [] - download(:dest => Puppet[:factdest], :source => Puppet[:factsource], - :ignore => Puppet[:factsignore], :name => "fact") do |object| - - next unless path.include?(File.dirname(object[:path])) - - files << object[:path] - - end - ensure - # Reload everything. - if Facter.respond_to? :loadfacts - Facter.loadfacts - elsif Facter.respond_to? :load - Facter.load - else - raise Puppet::Error, - "You must upgrade your version of Facter to use centralized facts" - end - - # This loads all existing facts and any new ones. We have to remove and - # reload because there's no way to unload specific facts. - loadfacts() - end - - # Retrieve the plugins from the central server. We only have to load the - # changed plugins, because Puppet::Type loads plugins on demand. - def self.getplugins - path = Puppet[:pluginpath].split(":") - download(:dest => Puppet[:plugindest], :source => Puppet[:pluginsource], - :ignore => Puppet[:pluginsignore], :name => "plugin") do |object| - - next unless path.include?(File.dirname(object[:path])) - - begin - Puppet.info "Reloading plugin %s" % - File.basename(File.basename(object[:path])).sub(".rb",'') - load object[:path] - rescue => detail - Puppet.warning "Could not reload plugin %s: %s" % - [object[:path], detail] - end - end - end - - def self.loaddir(dir, type) - return unless FileTest.directory?(dir) - - Dir.entries(dir).find_all { |e| e =~ /\.rb$/ }.each do |file| - fqfile = File.join(dir, file) - begin - Puppet.info "Loading #{type} %s" % File.basename(file.sub(".rb",'')) - Timeout::timeout(self.timeout) do - load fqfile - end - rescue => detail - Puppet.warning "Could not load #{type} %s: %s" % [fqfile, detail] - end - end - end - - def self.loadfacts - Puppet[:factpath].split(":").each do |dir| - loaddir(dir, "fact") - end - end - - def self.timeout - @timeout = Puppet[:configtimeout] - case @timeout - when String: - if @timeout =~ /^\d+$/ - @timeout = Integer(@timeout) - else - raise ArgumentError, "Configuration timeout must be an integer" - end - when Integer: # nothing - else - raise ArgumentError, "Configuration timeout must be an integer" - end - end - - # Send off the transaction report. - def report(transaction) - begin - report = transaction.report() - if Puppet[:rrdgraph] == true - report.graph() - end - reportclient().report(report) - rescue => detail - Puppet.err "Reporting failed: %s" % detail - end - end - - def reportclient - unless defined? @reportclient - @reportclient = Puppet::Client::Reporter.new( - :Server => Puppet[:reportserver] - ) - end - - @reportclient - end - - loadfacts() - - private - - # Actually retrieve the configuration, either from the server or from a local master. - def get_actual_config(facts) - if @local - return get_local_config(facts) - else - begin - Timeout::timeout(self.class.timeout) do - return get_remote_config(facts) - end - rescue Timeout::Error - Puppet.err "Configuration retrieval timed out" - return nil - end - end - end - - # Retrieve a configuration from a local master. - def get_local_config(facts) - # If we're local, we don't have to do any of the conversion - # stuff. - objects = @driver.getconfig(facts, "yaml") - @compile_time = Time.now - - if objects == "" - raise Puppet::Error, "Could not retrieve configuration" - end - - return objects - end - - # Retrieve a config from a remote master. - def get_remote_config(facts) - textobjects = "" - - textfacts = CGI.escape(YAML.dump(facts)) - - benchmark(:debug, "Retrieved configuration") do - # 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 - - unless Puppet[:usecacheonfailure] - @objects = nil - Puppet.warning "Not using cache on failed configuration" - return - end - end - 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.warning "Could not get config; using cached copy" - fromcache = true - else - @compile_time = Time.now - Puppet::Util::Storage.cache(:configuration)[:compile_time] = @compile_time - end - - begin - textobjects = CGI.unescape(textobjects) - 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 - - return objects - end -end - -# $Id$ diff --git a/lib/puppet/client/proxy.rb b/lib/puppet/client/proxy.rb deleted file mode 100644 index 6aff635f4..000000000 --- a/lib/puppet/client/proxy.rb +++ /dev/null @@ -1,28 +0,0 @@ -# unlike the other client classes (again, this design sucks) this class -# is basically just a proxy class -- it calls its methods on the driver -# and that's about it -class Puppet::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] - 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/reporter.rb b/lib/puppet/client/reporter.rb deleted file mode 100644 index a92842e7c..000000000 --- a/lib/puppet/client/reporter.rb +++ /dev/null @@ -1,34 +0,0 @@ -class Puppet::Client::Reporter < Puppet::Client - @drivername = :Report - - # set up the appropriate interface methods - @handler = Puppet::Server::Report - - def initialize(hash = {}) - if hash.include?(:Report) - hash[:Report] = Puppet::Server::Report.new() - end - - super(hash) - end - - # Send our report. We get the transaction report and convert it to YAML - # as appropriate. - def report(transreport) - report = YAML.dump(transreport) - - unless self.local - report = CGI.escape(report) - end - - # Now send the report - file = nil - benchmark(:info, "Sent transaction report") do - file = @driver.report(report) - end - - file - end -end - -# $Id$ diff --git a/lib/puppet/client/resource.rb b/lib/puppet/client/resource.rb deleted file mode 100644 index 6081b2b66..000000000 --- a/lib/puppet/client/resource.rb +++ /dev/null @@ -1,65 +0,0 @@ -class Puppet::Client::Resource < Puppet::Client - @drivername = :ResourceServer - - @handler = Puppet::Server::Resource - - def apply(bucket) - - case bucket - when Puppet::TransObject - tmp = Puppet::TransBucket.new - tmp.push bucket - bucket = tmp - bucket.name = Facter["hostname"].value - bucket.type = "resource" - when Puppet::TransBucket - # nothing - else - raise Puppet::DevError, "You must pass a transportable object, not a %s" % - bucket.class - end - - unless @local - bucket = Base64.encode64(YAML::dump(bucket)) - end - report = @driver.apply(bucket, "yaml") - - return report - end - - def describe(type, name, retrieve = false, ignore = false) - Puppet.info "Describing %s[%s]" % [type.to_s.capitalize, name] - text = @driver.describe(type, name, retrieve, ignore, "yaml") - - object = nil - if @local - object = text - else - object = YAML::load(Base64.decode64(text)) - end - - return object - end - - def initialize(hash = {}) - if hash.include?(:ResourceServer) - unless hash[:ResourceServer].is_a?(Puppet::Server::Resource) - raise Puppet::DevError, "Must pass an actual PElement server object" - end - end - - super(hash) - end - - def list(type, ignore = false, base = false) - bucket = @driver.list(type, ignore, base, "yaml") - - unless @local - bucket = YAML::load(Base64.decode64(bucket)) - end - - return bucket - end -end - -# $Id$ diff --git a/lib/puppet/client/runner.rb b/lib/puppet/client/runner.rb deleted file mode 100644 index 9bedf2374..000000000 --- a/lib/puppet/client/runner.rb +++ /dev/null @@ -1,17 +0,0 @@ -class Puppet::Client::Runner < Puppet::Client::ProxyClient - @drivername = :Runner - - # set up the appropriate interface methods - @handler = Puppet::Server::Runner - self.mkmethods - - def initialize(hash = {}) - if hash.include?(:Runner) - hash[:Runner] = Puppet::Server::Runner.new() - end - - super(hash) - end -end - -# $Id$ diff --git a/lib/puppet/client/status.rb b/lib/puppet/client/status.rb deleted file mode 100644 index ed1445e04..000000000 --- a/lib/puppet/client/status.rb +++ /dev/null @@ -1,7 +0,0 @@ -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 9fdb33bcf..91661e9e7 100755 --- a/lib/puppet/daemon.rb +++ b/lib/puppet/daemon.rb @@ -189,7 +189,7 @@ module Puppet # to create one if we don't already have one (or if we're not a CA # server). caclient = nil - if @driver.is_a? Puppet::Client::CA or @driver.is_a? Puppet::Server::CA + if @driver.is_a? Puppet::Network::Client::CA or @driver.is_a? Puppet::Network::Server::CA caclient = @driver else # Create a CA client with which to request the cert. @@ -197,7 +197,7 @@ module Puppet raise Puppet::DevError, "Incorrect setup for a local CA request" end - caclient = Puppet::Client::CA.new( + caclient = Puppet::Network::Client::CA.new( :Port => @driver.puppet_port, :Server => @driver.puppet_server ) diff --git a/lib/puppet/dsl.rb b/lib/puppet/dsl.rb index 44c03e8a9..09a4e2b61 100644 --- a/lib/puppet/dsl.rb +++ b/lib/puppet/dsl.rb @@ -68,7 +68,7 @@ module Puppet def apply bucket = export() objects = bucket.to_type - master = Puppet::Client::MasterClient.new :Master => "whatever" + master = Puppet::Network::Client::MasterClient.new :Master => "whatever" master.objects = objects master.apply diff --git a/lib/puppet/network/client.rb b/lib/puppet/network/client.rb new file mode 100644 index 000000000..604912025 --- /dev/null +++ b/lib/puppet/network/client.rb @@ -0,0 +1,190 @@ +# the available clients + +require 'puppet' +require 'puppet/network/networkclient' + +# 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 Puppet::Network::Client + include Puppet::Daemon + include Puppet::Util + + # 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 :secureinit + attr_accessor :schedule, :lastrun, :local, :stopping + + class << self + attr_reader :drivername, :handler + attr_accessor :netclient + end + + def initcerts + unless self.readcert + #if self.is_a? Puppet::Network::Client::CA + unless self.requestcert + return nil + end + #else + # return nil + #end + #unless self.requestcert + #end + end + + # unless we have a driver, we're a local client and we can't add + # certs anyway, so it doesn't matter + unless @driver + return true + end + + self.setcerts + end + + def initialize(hash) + # to whom do we connect? + @server = nil + @nil = nil + @secureinit = hash[:NoSecureInit] || true + + if hash.include?(:FQDN) + @fqdn = hash[:FQDN] + else + self.fqdn + end + + if hash.include?(:Cache) + @cache = hash[:Cache] + else + @cache = true + end + + driverparam = self.class.drivername + if hash.include?(:Server) + if $noclientnetworking + raise NetworkClientError.new("Networking not available: %s" % + $nonetworking) + end + + args = {:Server => hash[:Server]} + args[:Port] = hash[:Port] || Puppet[:masterport] + + if self.readcert + args[:Certificate] = @cert + args[:Key] = @key + args[:CAFile] = @cacertfile + end + + netclient = nil + unless netclient = self.class.netclient + unless handler = self.class.handler + raise Puppet::DevError, + "Class %s has no handler defined" % self.class + end + namespace = self.class.handler.interface.prefix + netclient = Puppet::Network::NetworkClient.netclient(namespace) + self.class.netclient = netclient + end + @driver = netclient.new(args) + @local = false + elsif hash.include?(driverparam) + @driver = hash[driverparam] + @local = true + else + raise ClientError, "%s must be passed a Server or %s" % + [self.class, driverparam] + end + end + + # Are we a local client? + def local? + if defined? @local and @local + true + else + false + end + end + + # A wrapper method to run and then store the last run time + def runnow + if self.stopping + Puppet.notice "In shutdown progress; skipping run" + return + end + begin + self.run + self.lastrun = Time.now.to_i + rescue => detail + if Puppet[:trace] + puts detail.backtrace + end + Puppet.err "Could not run %s: %s" % [self.class, detail] + end + end + + def run + raise Puppet::DevError, "Client type %s did not override run" % + self.class + end + + def scheduled? + if sched = self.schedule + return sched.match?(self.lastrun) + else + return true + end + end + + def setcerts + @driver.cert = @cert + @driver.key = @key + @driver.ca_file = @cacertfile + end + + def shutdown + if self.stopping + Puppet.notice "Already in shutdown" + else + self.stopping = true + if self.respond_to? :running? and self.running? + Puppet::Util::Storage.store + end + rmpidfile() + end + end + + # Start listening for events. We're pretty much just listening for + # timer events here. + def start + setpidfile() + # Create our timer. Puppet will handle observing it and such. + timer = Puppet.newtimer( + :interval => Puppet[:runinterval], + :tolerance => 1, + :start? => true + ) do + if self.scheduled? + self.runnow + end + end + + # Run once before we start following the timer + self.runnow + end + + require 'puppet/network/client/proxy' + require 'puppet/network/client/ca' + require 'puppet/network/client/dipper' + require 'puppet/network/client/file' + require 'puppet/network/client/log' + require 'puppet/network/client/master' + require 'puppet/network/client/runner' + require 'puppet/network/client/status' + require 'puppet/network/client/reporter' + require 'puppet/network/client/resource' +end + +# $Id$ diff --git a/lib/puppet/network/client/ca.rb b/lib/puppet/network/client/ca.rb new file mode 100644 index 000000000..9a99c1145 --- /dev/null +++ b/lib/puppet/network/client/ca.rb @@ -0,0 +1,23 @@ +require 'puppet/network/client/proxy' + +class Puppet::Network::Client::CA < Puppet::Network::Client::ProxyClient + @drivername = :CA + + # set up the appropriate interface methods + @handler = Puppet::Network::Server::CA + self.mkmethods + + def initialize(hash = {}) + if hash.include?(:CA) + if hash[:CA].is_a? Hash + hash[:CA] = Puppet::Network::Server::CA.new(hash[:CA]) + else + hash[:CA] = Puppet::Network::Server::CA.new() + end + end + + super(hash) + end +end + +# $Id$ diff --git a/lib/puppet/network/client/dipper.rb b/lib/puppet/network/client/dipper.rb new file mode 100644 index 000000000..8eaffc1a0 --- /dev/null +++ b/lib/puppet/network/client/dipper.rb @@ -0,0 +1,76 @@ +# The client class for filebuckets. +class Puppet::Network::Client::Dipper < Puppet::Network::Client + @drivername = :Bucket + + @handler = Puppet::Network::Server::FileBucket + + attr_accessor :name + + # Create our bucket client + def initialize(hash = {}) + if hash.include?(:Path) + bucket = Puppet::Network::Server::FileBucket.new( + :Path => hash[:Path] + ) + hash.delete(:Path) + hash[:Bucket] = bucket + end + + super(hash) + end + + # Back up a file to our bucket + def backup(file) + unless FileTest.exists?(file) + raise(BucketError, "File %s does not exist" % file) + end + contents = File.read(file) + unless local? + contents = Base64.encode64(contents) + end + return @driver.addfile(contents,file) + end + + # Restore the file + def restore(file,sum) + restore = true + if FileTest.exists?(file) + cursum = Digest::MD5.hexdigest(File.read(file)) + + # if the checksum has changed... + # this might be extra effort + if cursum == sum + restore = false + end + end + + if restore + if newcontents = @driver.getfile(sum) + unless local? + newcontents = Base64.decode64(newcontents) + end + 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 + return newsum + else + return nil + end + end +end + +# $Id$ diff --git a/lib/puppet/network/client/file.rb b/lib/puppet/network/client/file.rb new file mode 100644 index 000000000..7596aec1f --- /dev/null +++ b/lib/puppet/network/client/file.rb @@ -0,0 +1,20 @@ +class Puppet::Network::Client::FileClient < Puppet::Network::Client::ProxyClient + @drivername = :FileServer + + # set up the appropriate interface methods + @handler = Puppet::Network::Server::FileServer + + self.mkmethods + + def initialize(hash = {}) + if hash.include?(:FileServer) + unless hash[:FileServer].is_a?(Puppet::Network::Server::FileServer) + raise Puppet::DevError, "Must pass an actual FS object" + end + end + + super(hash) + end +end + +# $Id$ diff --git a/lib/puppet/network/client/log.rb b/lib/puppet/network/client/log.rb new file mode 100644 index 000000000..eddb8e0ca --- /dev/null +++ b/lib/puppet/network/client/log.rb @@ -0,0 +1,17 @@ +class Puppet::Network::Client::LogClient < Puppet::Network::Client::ProxyClient + @drivername = :Logger + + # set up the appropriate interface methods + @handler = Puppet::Network::Server::Logger + self.mkmethods + + def initialize(hash = {}) + if hash.include?(:Logger) + hash[:Logger] = Puppet::Network::Server::Logger.new() + end + + super(hash) + end +end + +# $Id$ diff --git a/lib/puppet/network/client/master.rb b/lib/puppet/network/client/master.rb new file mode 100644 index 000000000..9f07f48ef --- /dev/null +++ b/lib/puppet/network/client/master.rb @@ -0,0 +1,654 @@ +# The client for interacting with the puppetmaster config server. +require 'sync' +require 'timeout' + +class Puppet::Network::Client::MasterClient < Puppet::Network::Client + unless defined? @@sync + @@sync = Sync.new + end + + @handler = Puppet::Network::Server::Master + + Puppet.setdefaults("puppetd", + :puppetdlockfile => [ "$statedir/puppetdlock", + "A lock file to temporarily stop puppetd from doing anything."], + :usecacheonfailure => [true, + "Whether to use the cached configuration when the remote + configuration will not compile. This option is useful for testing + new configurations, where you want to fix the broken configuration + rather than reverting to a known-good one." + ], + :downcasefacts => [false, + "Whether facts should be made all lowercase when sent to the server."] + ) + + Puppet.setdefaults(:puppetd, + :configtimeout => [30, + "How long the client should wait for the configuration to be retrieved + before considering it a failure. This can help reduce flapping if too + many clients contact the server at one time." + ], + :reportserver => ["$server", + "The server to which to send transaction reports." + ], + :report => [false, + "Whether to send reports after every transaction." + ] + ) + + # Plugin information. + Puppet.setdefaults("puppet", + :pluginpath => ["$vardir/plugins", + "Where Puppet should look for plugins. Multiple directories should + be colon-separated, like normal PATH variables."], + :plugindest => ["$vardir/plugins", + "Where Puppet should store plugins that it pulls down from the central + server."], + :pluginsource => ["puppet://$server/plugins", + "From where to retrieve plugins. The standard Puppet ``file`` type + is used for retrieval, so anything that is a valid file source can + be used here."], + :pluginsync => [false, + "Whether plugins should be synced with the central server."], + :pluginsignore => [".svn CVS", + "What files to ignore when pulling down plugins."] + ) + + # Central fact information. + Puppet.setdefaults("puppet", + :factpath => ["$vardir/facts", + "Where Puppet should look for facts. Multiple directories should + be colon-separated, like normal PATH variables."], + :factdest => ["$vardir/facts", + "Where Puppet should store facts that it pulls down from the central + server."], + :factsource => ["puppet://$server/facts", + "From where to retrieve facts. The standard Puppet ``file`` type + is used for retrieval, so anything that is a valid file source can + be used here."], + :factsync => [false, + "Whether facts should be synced with the central server."], + :factsignore => [".svn CVS", + "What files to ignore when pulling down facts."] + ) + + @drivername = :Master + + attr_accessor :objects + attr_reader :compile_time + + class << self + # Puppetd should only have one instance running, and we need a way + # to retrieve it. + attr_accessor :instance + include Puppet::Util + end + + def self.facts + # Retrieve the facts from the central server. + if Puppet[:factsync] + self.getfacts() + end + + down = Puppet[:downcasefacts] + + facts = {} + Facter.each { |name,fact| + if down + facts[name] = fact.to_s.downcase + else + facts[name] = fact.to_s + end + } + + # Add our client version to the list of facts, so people can use it + # in their manifests + facts["clientversion"] = Puppet.version.to_s + + facts + end + + # This method actually applies the configuration. + def apply(tags = nil, ignoreschedules = false) + unless defined? @objects + raise Puppet::Error, "Cannot apply; objects not defined" + end + + transaction = @objects.evaluate + + if tags + transaction.tags = tags + end + + if ignoreschedules + transaction.ignoreschedules = true + end + + transaction.addtimes :config_retrieval => @configtime + + 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[:trace] + puts detail.backtrace + end + ensure + Puppet::Util::Storage.store + end + + if Puppet[:report] + report(transaction) + end + + return transaction + ensure + if defined? transaction and transaction + transaction.cleanup + end + end + + # Cache the config + def cache(text) + Puppet.info "Caching configuration at %s" % self.cachefile + confdir = File.dirname(Puppet[:localconfig]) + 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 + + def clear + #@objects = nil + @objects.remove(true) + Puppet::Type.allclear + end + + # Initialize and load storage + def dostorage + begin + Puppet::Util::Storage.load + @compile_time ||= Puppet::Util::Storage.cache(:configuration)[:compile_time] + rescue => detail + if Puppet[:trace] + puts detail.backtrace + end + Puppet.err "Corrupt state file %s: %s" % [Puppet[:statefile], detail] + begin + File.unlink(Puppet[:statefile]) + 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 self.compile_time + return false + end + + # We're willing to give a 2 second drift + if @driver.freshness - @compile_time.to_i < 1 + return true + else + return false + end + end + + # Let the daemon run again, freely in the filesystem. Frolick, little + # daemon! + def enable + Puppet::Util::Pidlock.new(Puppet[:puppetdlockfile]).unlock(:anonymous => true) + end + + # Stop the daemon from making any configuration runs. + def disable + Puppet::Util::Pidlock.new(Puppet[:puppetdlockfile]).lock(:anonymous => true) + end + + # Retrieve the config from a remote server. If this fails, then + # use the cached copy. + def getconfig + dostorage() + if self.fresh? + Puppet.info "Config is up to date" + unless defined? @objects + begin + @objects = YAML.load(self.retrievecache).to_type + rescue => detail + Puppet.warning "Could not load cached configuration: %s" % detail + end + end + return + end + Puppet.debug("getting config") + + # Retrieve the plugins. + if Puppet[:pluginsync] + getplugins() + end + + facts = self.class.facts + + unless facts.length > 0 + raise Puppet::Network::ClientError.new( + "Could not retrieve any facts" + ) + end + + unless objects = get_actual_config(facts) + @objects = nil + return + end + + unless objects.is_a?(Puppet::TransBucket) + raise NetworkClientError, + "Invalid returned objects of type %s" % objects.class + end + + self.setclasses(objects.classes) + + # Clear all existing objects, so we can recreate our stack. + if defined? @objects + Puppet::Type.allclear + + # Make sure all of the objects are really gone. + @objects.remove(true) + end + @objects = nil + + # Now convert the objects to real Puppet objects + @objects = objects.to_type + + if @objects.nil? + raise Puppet::Error, "Configuration could not be processed" + end + + # and perform any necessary final actions before we evaluate. + @objects.finalize + + return @objects + end + + # A simple proxy method, so it's easy to test. + def getplugins + self.class.getplugins + end + + # Just so we can specify that we are "the" instance. + def initialize(*args) + Puppet.config.use(:puppet, :sslcertificates, :puppetd) + super + + # This might be nil + @configtime = 0 + + self.class.instance = self + @running = false + + mkdefault_objects + end + + # Make the default objects necessary for function. + def mkdefault_objects + # First create the default scheduling objects + Puppet::Type.type(:schedule).mkdefaultschedules + + # And filebuckets + Puppet::Type.type(:filebucket).mkdefaultbucket + end + + # Mark that we should restart. The Puppet module checks whether we're running, + # so this only gets called if we're in the middle of a run. + def restart + # If we're currently running, then just mark for later + Puppet.notice "Received signal to restart; waiting until run is complete" + @restart = true + end + + # Should we restart? + def restart? + if defined? @restart + @restart + else + false + end + 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(tags = nil, ignoreschedules = false) + lockfile = Puppet::Util::Pidlock.new(Puppet[:puppetdlockfile]) + + Puppet::Util.sync(:puppetrun).synchronize(Sync::EX) do + if !lockfile.lock + Puppet.notice "Lock file %s exists; skipping configuration run" % + lockfile.lockfile + else + @running = true + @configtime = thinmark do + self.getconfig + end + + if defined? @objects and @objects + unless @local + Puppet.notice "Starting configuration run" + end + benchmark(:notice, "Finished configuration run") do + self.apply(tags, ignoreschedules) + end + end + @running = false + end + + lockfile.unlock + + # Did we get HUPped during the run? If so, then restart now that we're + # done with the run. + if self.restart? + Process.kill(:HUP, $$) + end + end + end + + def running? + @running + end + + # Store the classes in the classfile, but only if we're not local. + def setclasses(ary) + if @local + return + end + unless ary and ary.length > 0 + Puppet.info "No classes to store" + return + end + 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 + + private + + # Download files from the remote server, returning a list of all + # changed files. + def self.download(args) + objects = Puppet::Type.type(:component).create( + :name => "#{args[:name]}_collector" + ) + hash = { + :path => args[:dest], + :recurse => true, + :source => args[:source], + :tag => "#{args[:name]}s", + :owner => Process.uid, + :group => Process.gid, + :backup => false + } + + if args[:ignore] + hash[:ignore] = args[:ignore].split(/\s+/) + end + objects.push Puppet::Type.type(:file).create(hash) + + Puppet.info "Retrieving #{args[:name]}s" + + begin + trans = objects.evaluate + trans.ignoretags = true + Timeout::timeout(self.timeout) do + trans.evaluate + end + rescue Puppet::Error, Timeout::Error => detail + if Puppet[:debug] + puts detail.backtrace + end + Puppet.err "Could not retrieve #{args[:name]}s: %s" % detail + end + + # Now source all of the changed objects, but only source those + # that are top-level. + files = [] + trans.changed?.find_all do |object| + yield object if block_given? + files << object[:path] + end + trans.cleanup + + # Now clean up after ourselves + objects.remove + files + end + + # Retrieve facts from the central server. + def self.getfacts + # Clear all existing definitions. + Facter.clear + + # Download the new facts + path = Puppet[:factpath].split(":") + files = [] + download(:dest => Puppet[:factdest], :source => Puppet[:factsource], + :ignore => Puppet[:factsignore], :name => "fact") do |object| + + next unless path.include?(File.dirname(object[:path])) + + files << object[:path] + + end + ensure + # Reload everything. + if Facter.respond_to? :loadfacts + Facter.loadfacts + elsif Facter.respond_to? :load + Facter.load + else + raise Puppet::Error, + "You must upgrade your version of Facter to use centralized facts" + end + + # This loads all existing facts and any new ones. We have to remove and + # reload because there's no way to unload specific facts. + loadfacts() + end + + # Retrieve the plugins from the central server. We only have to load the + # changed plugins, because Puppet::Type loads plugins on demand. + def self.getplugins + path = Puppet[:pluginpath].split(":") + download(:dest => Puppet[:plugindest], :source => Puppet[:pluginsource], + :ignore => Puppet[:pluginsignore], :name => "plugin") do |object| + + next unless path.include?(File.dirname(object[:path])) + + begin + Puppet.info "Reloading plugin %s" % + File.basename(File.basename(object[:path])).sub(".rb",'') + load object[:path] + rescue => detail + Puppet.warning "Could not reload plugin %s: %s" % + [object[:path], detail] + end + end + end + + def self.loaddir(dir, type) + return unless FileTest.directory?(dir) + + Dir.entries(dir).find_all { |e| e =~ /\.rb$/ }.each do |file| + fqfile = File.join(dir, file) + begin + Puppet.info "Loading #{type} %s" % File.basename(file.sub(".rb",'')) + Timeout::timeout(self.timeout) do + load fqfile + end + rescue => detail + Puppet.warning "Could not load #{type} %s: %s" % [fqfile, detail] + end + end + end + + def self.loadfacts + Puppet[:factpath].split(":").each do |dir| + loaddir(dir, "fact") + end + end + + def self.timeout + @timeout = Puppet[:configtimeout] + case @timeout + when String: + if @timeout =~ /^\d+$/ + @timeout = Integer(@timeout) + else + raise ArgumentError, "Configuration timeout must be an integer" + end + when Integer: # nothing + else + raise ArgumentError, "Configuration timeout must be an integer" + end + end + + # Send off the transaction report. + def report(transaction) + begin + report = transaction.report() + if Puppet[:rrdgraph] == true + report.graph() + end + reportclient().report(report) + rescue => detail + Puppet.err "Reporting failed: %s" % detail + end + end + + def reportclient + unless defined? @reportclient + @reportclient = Puppet::Network::Client::Reporter.new( + :Server => Puppet[:reportserver] + ) + end + + @reportclient + end + + loadfacts() + + private + + # Actually retrieve the configuration, either from the server or from a local master. + def get_actual_config(facts) + if @local + return get_local_config(facts) + else + begin + Timeout::timeout(self.class.timeout) do + return get_remote_config(facts) + end + rescue Timeout::Error + Puppet.err "Configuration retrieval timed out" + return nil + end + end + end + + # Retrieve a configuration from a local master. + def get_local_config(facts) + # If we're local, we don't have to do any of the conversion + # stuff. + objects = @driver.getconfig(facts, "yaml") + @compile_time = Time.now + + if objects == "" + raise Puppet::Error, "Could not retrieve configuration" + end + + return objects + end + + # Retrieve a config from a remote master. + def get_remote_config(facts) + textobjects = "" + + textfacts = CGI.escape(YAML.dump(facts)) + + benchmark(:debug, "Retrieved configuration") do + # 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 + + unless Puppet[:usecacheonfailure] + @objects = nil + Puppet.warning "Not using cache on failed configuration" + return + end + end + 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.warning "Could not get config; using cached copy" + fromcache = true + else + @compile_time = Time.now + Puppet::Util::Storage.cache(:configuration)[:compile_time] = @compile_time + end + + begin + textobjects = CGI.unescape(textobjects) + 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 + + return objects + end +end + +# $Id$ diff --git a/lib/puppet/network/client/proxy.rb b/lib/puppet/network/client/proxy.rb new file mode 100644 index 000000000..e1295a96f --- /dev/null +++ b/lib/puppet/network/client/proxy.rb @@ -0,0 +1,28 @@ +# unlike the other client classes (again, this design sucks) this class +# is basically just a proxy class -- it calls its methods on the driver +# and that's about it +class Puppet::Network::Client::ProxyClient < Puppet::Network::Client + def self.mkmethods + interface = @handler.interface + namespace = interface.prefix + + + interface.methods.each { |ary| + method = ary[0] + Puppet.debug "%s: defining %s.%s" % [self, namespace, method] + define_method(method) { |*args| + begin + @driver.send(method, *args) + rescue XMLRPC::FaultException => detail + #Puppet.err "Could not call %s.%s: %s" % + # [namespace, method, detail.faultString] + #raise NetworkClientError, + # "XMLRPC Error: %s" % detail.faultString + raise NetworkClientError, detail.faultString + end + } + } + end +end + +# $Id$ diff --git a/lib/puppet/network/client/reporter.rb b/lib/puppet/network/client/reporter.rb new file mode 100644 index 000000000..dd340da02 --- /dev/null +++ b/lib/puppet/network/client/reporter.rb @@ -0,0 +1,34 @@ +class Puppet::Network::Client::Reporter < Puppet::Network::Client + @drivername = :Report + + # set up the appropriate interface methods + @handler = Puppet::Network::Server::Report + + def initialize(hash = {}) + if hash.include?(:Report) + hash[:Report] = Puppet::Network::Server::Report.new() + end + + super(hash) + end + + # Send our report. We get the transaction report and convert it to YAML + # as appropriate. + def report(transreport) + report = YAML.dump(transreport) + + unless self.local + report = CGI.escape(report) + end + + # Now send the report + file = nil + benchmark(:info, "Sent transaction report") do + file = @driver.report(report) + end + + file + end +end + +# $Id$ diff --git a/lib/puppet/network/client/resource.rb b/lib/puppet/network/client/resource.rb new file mode 100644 index 000000000..71a19bf91 --- /dev/null +++ b/lib/puppet/network/client/resource.rb @@ -0,0 +1,65 @@ +class Puppet::Network::Client::Resource < Puppet::Network::Client + @drivername = :ResourceServer + + @handler = Puppet::Network::Server::Resource + + def apply(bucket) + + case bucket + when Puppet::TransObject + tmp = Puppet::TransBucket.new + tmp.push bucket + bucket = tmp + bucket.name = Facter["hostname"].value + bucket.type = "resource" + when Puppet::TransBucket + # nothing + else + raise Puppet::DevError, "You must pass a transportable object, not a %s" % + bucket.class + end + + unless @local + bucket = Base64.encode64(YAML::dump(bucket)) + end + report = @driver.apply(bucket, "yaml") + + return report + end + + def describe(type, name, retrieve = false, ignore = false) + Puppet.info "Describing %s[%s]" % [type.to_s.capitalize, name] + text = @driver.describe(type, name, retrieve, ignore, "yaml") + + object = nil + if @local + object = text + else + object = YAML::load(Base64.decode64(text)) + end + + return object + end + + def initialize(hash = {}) + if hash.include?(:ResourceServer) + unless hash[:ResourceServer].is_a?(Puppet::Network::Server::Resource) + raise Puppet::DevError, "Must pass an actual PElement server object" + end + end + + super(hash) + end + + def list(type, ignore = false, base = false) + bucket = @driver.list(type, ignore, base, "yaml") + + unless @local + bucket = YAML::load(Base64.decode64(bucket)) + end + + return bucket + end +end + +# $Id$ diff --git a/lib/puppet/network/client/runner.rb b/lib/puppet/network/client/runner.rb new file mode 100644 index 000000000..40d13ac86 --- /dev/null +++ b/lib/puppet/network/client/runner.rb @@ -0,0 +1,17 @@ +class Puppet::Network::Client::Runner < Puppet::Network::Client::ProxyClient + @drivername = :Runner + + # set up the appropriate interface methods + @handler = Puppet::Network::Server::Runner + self.mkmethods + + def initialize(hash = {}) + if hash.include?(:Runner) + hash[:Runner] = Puppet::Network::Server::Runner.new() + end + + super(hash) + end +end + +# $Id$ diff --git a/lib/puppet/network/client/status.rb b/lib/puppet/network/client/status.rb new file mode 100644 index 000000000..6c1a96e85 --- /dev/null +++ b/lib/puppet/network/client/status.rb @@ -0,0 +1,7 @@ +class Puppet::Network::Client::StatusClient < Puppet::Network::Client::ProxyClient + # set up the appropriate interface methods + @handler = Puppet::Network::Server::ServerStatus + self.mkmethods +end + +# $Id$ diff --git a/lib/puppet/network/networkclient.rb b/lib/puppet/network/networkclient.rb new file mode 100644 index 000000000..62d8906e0 --- /dev/null +++ b/lib/puppet/network/networkclient.rb @@ -0,0 +1,167 @@ +require 'puppet/sslcertificates' +require 'openssl' +require 'puppet/daemon' +require 'puppet/network/server' +require 'puppet/external/base64' + +require 'webrick' +require 'cgi' +require 'xmlrpc/client' +require 'xmlrpc/server' +require 'yaml' + +module Puppet + module Network + class ClientError < Puppet::Error; end + class NetworkClientError < Puppet::Error; end + class NetworkClient < XMLRPC::Client + attr_accessor :puppet_server, :puppet_port + @clients = {} + + class << self + include Puppet::Util + include Puppet::Util::ClassGen + end + + # Create a netclient for each handler + def self.mkclients + # add the methods associated with each namespace + Puppet::Network::Server::Handler.each { |handler| + interface = handler.interface + namespace = interface.prefix + + # Create a subclass for every client type. This is + # so that all of the methods are on their own class, + # so that they namespaces can define the same methods if + # they want. + constant = handler.to_s.sub(/^.+::/, '') + name = namespace.downcase + newclient = genclass(name, :hash => @clients, + :constant => constant) + + interface.methods.each { |ary| + method = ary[0] + if public_method_defined?(method) + raise Puppet::DevError, "Method %s is already defined" % + method + end + newclient.send(:define_method,method) { |*args| + Puppet.debug "Calling %s.%s" % [namespace, 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 + raise NetworkClientError, + "Certificates were not trusted: %s" % detail + 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] + raise NetworkClientError, msg + rescue SocketError => detail + error = NetworkClientError.new( + "Could not find server %s" % @puppetserver + ) + error.set_backtrace detail.backtrace + raise error + rescue => detail + Puppet.err "Could not call %s.%s: %s" % + [namespace, method, detail.inspect] + error = NetworkClientError.new(detail.to_s) + error.set_backtrace detail.backtrace + raise error + end + } + } + } + end + + def self.netclient(namespace) + if @clients.empty? + self.mkclients() + end + + namespace = symbolize(namespace) + + @clients[namespace] + end + + def ca_file=(cafile) + @http.ca_file = cafile + store = OpenSSL::X509::Store.new + store.add_file(cafile) + 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] + + @puppet_server = hash[:Server] + @puppet_port = hash[:Port] + + @puppetserver = hash[:Server] + + super( + hash[:Server], + hash[:Path], + hash[:Port], + nil, # proxy_host + nil, # proxy_port + nil, # user + nil, # password + true, # use_ssl + 120 # a two minute timeout, instead of 30 seconds + ) + + if hash[:Certificate] + self.cert = hash[:Certificate] + else + unless defined? $nocertwarned + Puppet.err "No certificate; running with reduced functionality." + $nocertwarned = true + end + 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 + + def local? + false + end + end + end +end + +# $Id$ diff --git a/lib/puppet/network/server.rb b/lib/puppet/network/server.rb new file mode 100644 index 000000000..e9205d48b --- /dev/null +++ b/lib/puppet/network/server.rb @@ -0,0 +1,207 @@ +# the server +# +# allow things to connect to us and communicate, and stuff + +require 'puppet' +require 'puppet/daemon' +require 'webrick' +require 'webrick/https' +require 'cgi' +require 'xmlrpc/server' +require 'xmlrpc/client' + +module Puppet + class ServerError < RuntimeError; end + module Network + class Server < WEBrick::HTTPServer + include Puppet::Daemon + + Puppet.config.setdefaults(:puppetd, + :listen => [false, "Whether puppetd should listen for + connections. If this is true, then by default only the + ``runner`` server is started, which allows remote authorized + and authenticated nodes to connect and trigger ``puppetd`` + runs."] + ) + + # Create our config object if necessary. This works even if + # there's no configuration file. + def authconfig + unless defined? @authconfig + @authconfig = Puppet::Network::AuthConfig.new() + end + + @authconfig + end + + # Read the CA cert and CRL and populate an OpenSSL::X509::Store + # with them, with flags appropriate for checking client + # certificates for revocation + def x509store + if Puppet[:cacrl] == 'none' + # No CRL, no store needed + return nil + end + unless File.exist?(Puppet[:cacrl]) + raise Puppet::Error, "Could not find CRL" + end + crl = OpenSSL::X509::CRL.new(File.read(Puppet[:cacrl])) + store = OpenSSL::X509::Store.new + store.purpose = OpenSSL::X509::PURPOSE_ANY + store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK + store.add_file(@cacertfile) + store.add_crl(crl) + return store + end + + def initialize(hash = {}) + Puppet.info "Starting server for Puppet version %s" % Puppet.version + daemonize = nil + if hash.include?(:Daemonize) + daemonize = hash[:Daemonize] + end + + # FIXME we should have some kind of access control here, using + # :RequestHandler + hash[:Port] ||= Puppet[:masterport] + hash[:Logger] ||= self.httplog + hash[:AccessLog] ||= [ + [ self.httplog, WEBrick::AccessLog::COMMON_LOG_FORMAT ], + [ self.httplog, WEBrick::AccessLog::REFERER_LOG_FORMAT ] + ] + + if hash.include?(:Handlers) + unless hash[:Handlers].is_a?(Hash) + raise ServerError, "Handlers must have arguments" + end + + @handlers = hash[:Handlers].collect { |handler, args| + hclass = nil + unless hclass = Handler.handler(handler) + raise ServerError, "Invalid handler %s" % handler + end + hclass.new(args) + } + else + raise ServerError, "A server must have handlers" + end + + # okay, i need to retrieve my cert and set it up, somehow + # the default case will be that i'm also the ca + if ca = @handlers.find { |handler| handler.is_a?(Puppet::Network::Server::CA) } + @driver = ca + @secureinit = true + self.fqdn + else + if hash.include?(:NoSecureInit) + @secureinit = false + else + @secureinit = true + end + end + + unless self.readcert + unless self.requestcert + raise Puppet::Error, "Cannot start without certificates" + end + end + + hash[:SSLCertificateStore] = x509store + hash[:SSLCertificate] = @cert + hash[:SSLPrivateKey] = @key + hash[:SSLStartImmediately] = true + hash[:SSLEnable] = true + hash[:SSLCACertificateFile] = @cacertfile + hash[:SSLVerifyClient] = OpenSSL::SSL::VERIFY_PEER + hash[:SSLCertName] = nil + + super(hash) + + Puppet.info "Listening on port %s" % hash[:Port] + + # this creates a new servlet for every connection, + # but all servlets have the same list of handlers + # thus, the servlets can have their own state -- passing + # around the requests and such -- but the handlers + # have a global state + + # mount has to be called after the server is initialized + self.mount("/RPC2", Puppet::Network::Server::Servlet, @handlers) + end + + # the base class for the different handlers + class Handler + attr_accessor :server + class << self + include Puppet::Util + end + + @subclasses = [] + + def self.each + @subclasses.each { |c| yield c } + end + + def self.handler(name) + name = name.to_s.downcase + @subclasses.find { |h| + h.name.to_s.downcase == name + } + end + + def self.inherited(sub) + @subclasses << sub + end + + def self.interface + if defined? @interface + return @interface + else + raise Puppet::DevError, "Handler %s has no defined interface" % + self + end + end + + def self.name + unless defined? @name + @name = self.to_s.sub(/.+::/, '').intern + end + + return @name + end + + def initialize(hash = {}) + end + end + + + class ServerStatus < Handler + + @interface = XMLRPC::Service::Interface.new("status") { |iface| + iface.add_method("int status()") + } + + @name = :Status + + def status(status = nil, client = nil, clientip = nil) + return 1 + end + end + end + end +end + +require 'puppet/network/server/authstore' +require 'puppet/network/server/authconfig' +require 'puppet/network/server/servlet' +require 'puppet/network/server/master' +require 'puppet/network/server/ca' +require 'puppet/network/server/fileserver' +require 'puppet/network/server/filebucket' +require 'puppet/network/server/resource' +require 'puppet/network/server/runner' +require 'puppet/network/server/logger' +require 'puppet/network/server/report' +require 'puppet/network/client' + +# $Id$ diff --git a/lib/puppet/network/server/authconfig.rb b/lib/puppet/network/server/authconfig.rb new file mode 100644 index 000000000..e4d31d8d8 --- /dev/null +++ b/lib/puppet/network/server/authconfig.rb @@ -0,0 +1,173 @@ +require 'puppet/util/loadedfile' +require 'puppet/network/server/rights' + +module Puppet + class ConfigurationError < Puppet::Error; end + class Network::AuthConfig < Puppet::Util::LoadedFile + Puppet.config.setdefaults(:puppet, + :authconfig => [ "$confdir/namespaceauth.conf", + "The configuration file that defines the rights to the different + namespaces and methods. This can be used as a coarse-grained + authorization system for both ``puppetd`` and ``puppetmasterd``." + ] + ) + + # Just proxy the setting methods to our rights stuff + [:allow, :deny].each do |method| + define_method(method) do |*args| + @rights.send(method, *args) + end + end + + # Here we add a little bit of semantics. They can set auth on a whole namespace + # or on just a single method in the namespace. + def allowed?(name, host, ip) + namespace, method = name.to_s.split(".") + unless namespace and method + raise ArgumentError, "Invalid method name %s" % name + end + + name = name.intern if name.is_a? String + namespace = namespace.intern + method = method.intern + + read() + + if @rights.include?(name) + return @rights[name].allowed?(host, ip) + elsif @rights.include?(namespace) + return @rights[namespace].allowed?(host, ip) + else + return false + end + end + + # Does the file exist? Puppetmasterd does not require it, but + # puppetd does. + def exists? + FileTest.exists?(@file) + end + + def initialize(file = nil, parsenow = true) + @file ||= Puppet[:authconfig] + + unless @file + raise Puppet::DevError, "No authconfig file defined" + end + return unless self.exists? + super(@file) + @rights = Puppet::Network::Rights.new + @configstamp = @configstatted = nil + @configtimeout = 60 + + if parsenow + read() + end + end + + # Read the configuration file. + def read + return unless FileTest.exists?(@file) + + if @configstamp + if @configtimeout and @configstatted + if Time.now - @configstatted > @configtimeout + @configstatted = Time.now + tmp = File.stat(@file).ctime + + if tmp == @configstamp + return + else + Puppet.notice "%s vs %s" % [tmp, @configstamp] + end + else + return + end + else + Puppet.notice "%s and %s" % [@configtimeout, @configstatted] + end + end + + parse() + + @configstamp = File.stat(@file).ctime + @configstatted = Time.now + end + + private + + def parse + newrights = Puppet::Network::Rights.new + begin + File.open(@file) { |f| + right = nil + count = 1 + f.each { |line| + case line + when /^\s*#/: next # skip comments + when /^\s*$/: next # skip blank lines + when /\[([\w.]+)\]/: # "namespace" or "namespace.method" + name = $1 + if newrights.include?(name) + raise FileServerError, "%s is already set at %s" % + [newrights[name], name] + end + newrights.newright(name) + right = newrights[name] + when /^\s*(\w+)\s+(.+)$/: + var = $1 + value = $2 + case var + when "allow": + value.split(/\s*,\s*/).each { |val| + begin + right.info "allowing %s access" % val + right.allow(val) + rescue AuthStoreError => detail + raise ConfigurationError, "%s at line %s of %s" % + [detail.to_s, count, @config] + end + } + when "deny": + value.split(/\s*,\s*/).each { |val| + begin + right.info "denying %s access" % val + right.deny(val) + rescue AuthStoreError => detail + raise ConfigurationError, "%s at line %s of %s" % + [detail.to_s, count, @config] + end + } + else + raise ConfigurationError, + "Invalid argument '%s' at line %s" % [var, count] + end + else + raise ConfigurationError, "Invalid line %s: %s" % [count, line] + end + count += 1 + } + } + rescue Errno::EACCES => detail + Puppet.err "Configuration error: Cannot read %s; cannot serve" % @file + #raise Puppet::Error, "Cannot read %s" % @config + rescue Errno::ENOENT => detail + Puppet.err "Configuration error: '%s' does not exit; cannot serve" % + @file + #raise Puppet::Error, "%s does not exit" % @config + #rescue FileServerError => detail + # Puppet.err "FileServer error: %s" % detail + end + + # Verify each of the rights are valid. + # We let the check raise an error, so that it can raise an error + # pointing to the specific problem. + newrights.each { |name, right| + right.valid? + } + @rights = newrights + end + end +end + +# $Id$ diff --git a/lib/puppet/network/server/authstore.rb b/lib/puppet/network/server/authstore.rb new file mode 100755 index 000000000..51ce93d46 --- /dev/null +++ b/lib/puppet/network/server/authstore.rb @@ -0,0 +1,227 @@ +# standard module for determining whether a given hostname or IP has access to +# the requested resource + +require 'ipaddr' + +module Puppet + class AuthStoreError < Puppet::Error; end + class AuthorizationError < Puppet::Error; end + + class Network::AuthStore + # This has to be an array, not a hash, else it loses its ordering. + ORDER = [ + [:ip, [:ip]], + [:name, [:hostname, :domain]] + ] + + Puppet::Util.logmethods(self, true) + + def allow(pattern) + # a simple way to allow anyone at all to connect + if pattern == "*" + @globalallow = true + else + store(pattern, @allow) + end + end + + def allowed?(name, ip) + if name or ip + # This is probably unnecessary, and can cause some weirdnesses in + # cases where we're operating over localhost but don't have a real + # IP defined. + unless name and ip + raise Puppet::DevError, "Name and IP must be passed to 'allowed?'" + end + # else, we're networked and such + else + # we're local + return true + end + + # yay insecure overrides + if @globalallow + return true + end + + value = nil + ORDER.each { |nametype, array| + if nametype == :ip + value = IPAddr.new(ip) + else + value = name.split(".").reverse + end + + + array.each { |type| + [[@deny, false], [@allow, true]].each { |ary| + hash, retval = ary + if hash.include?(type) + hash[type].each { |pattern| + if match?(nametype, value, pattern) + return retval + end + } + end + } + } + } + + self.info "defaulting to no access for %s" % name + # default to false + return false + end + + def deny(pattern) + store(pattern, @deny) + end + + def initialize + @globalallow = nil + @allow = Hash.new { |hash, key| + hash[key] = [] + } + @deny = Hash.new { |hash, key| + hash[key] = [] + } + end + + private + + def match?(nametype, value, pattern) + if value == pattern # simplest shortcut + return true + end + + case nametype + when :ip: matchip?(value, pattern) + when :name: matchname?(value, pattern) + else + raise Puppet::DevError, "Invalid match type %s" % nametype + end + end + + def matchip?(value, pattern) + # we're just using builtin stuff for this, thankfully + if pattern.include?(value) + return true + else + return false + end + end + + def matchname?(value, pattern) + # yay, horribly inefficient + if pattern[-1] != '*' # the pattern has no metachars and is not equal + # thus, no match + #Puppet.info "%s is not equal with no * in %s" % [value, pattern] + return false + else + # we know the last field of the pattern is '*' + # if everything up to that doesn't match, we're definitely false + if pattern[0..-2] != value[0..pattern.length-2] + #Puppet.notice "subpatterns didn't match; %s vs %s" % + # [pattern[0..-2], value[0..pattern.length-2]] + return false + end + + case value.length <=> pattern.length + when -1: # value is shorter than pattern + if pattern.length - value.length == 1 + # only ever allowed when the value is the domain of a + # splatted pattern + #Puppet.info "allowing splatted domain %s" % [value] + return true + else + return false + end + when 0: # value is the same length as pattern + if pattern[-1] == "*" + #Puppet.notice "same length with *" + return true + else + return false + end + when 1: # value is longer than pattern + # at this point we've already verified that everything up to + # the '*' in the pattern matches, so we are true + return true + end + end + end + + def store(pattern, hash) + type, value = type(pattern) + + if type and value + # this won't work once we get beyond simple stuff... + hash[type] << value + else + raise AuthStoreError, "Invalid pattern %s" % pattern + end + end + + def type(pattern) + type = value = nil + case pattern + when /^(\d+\.){3}\d+$/: + type = :ip + begin + value = IPAddr.new(pattern) + rescue ArgumentError => detail + raise AuthStoreError, "Invalid IP address pattern %s" % pattern + end + when /^(\d+\.){3}\d+\/(\d+)$/: + mask = Integer($2) + if mask < 1 or mask > 32 + raise AuthStoreError, "Invalid IP mask %s" % mask + end + type = :ip + begin + value = IPAddr.new(pattern) + rescue ArgumentError => detail + raise AuthStoreError, "Invalid IP address pattern %s" % pattern + end + when /^(\d+\.){1,3}\*$/: # an ip address with a '*' at the end + type = :ip + match = $1 + match.sub!(".", '') + ary = pattern.split(".") + + mask = case ary.index(match) + when 0: 8 + when 1: 16 + when 2: 24 + else + raise AuthStoreError, "Invalid IP pattern %s" % pattern + end + + ary.pop + while ary.length < 4 + ary.push("0") + end + + begin + value = IPAddr.new(ary.join(".") + "/" + mask.to_s) + rescue ArgumentError => detail + raise AuthStoreError, "Invalid IP address pattern %s" % pattern + end + when /^[\d.]+$/: # necessary so incomplete IP addresses can't look + # like hostnames + raise AuthStoreError, "Invalid IP address pattern %s" % pattern + when /^([a-zA-Z][-\w]*\.)+[-\w]+$/: # a full hostname + type = :hostname + value = pattern.split(".").reverse + when /^\*(\.([a-zA-Z][-\w]*)){1,}$/: + type = :domain + value = pattern.split(".").reverse + else + raise AuthStoreError, "Invalid pattern %s" % pattern + end + + return [type, value] + end + end +end + +# $Id$ diff --git a/lib/puppet/network/server/ca.rb b/lib/puppet/network/server/ca.rb new file mode 100644 index 000000000..8a61399ba --- /dev/null +++ b/lib/puppet/network/server/ca.rb @@ -0,0 +1,152 @@ +require 'openssl' +require 'puppet' +require 'puppet/sslcertificates' +require 'xmlrpc/server' + +# Much of this was taken from QuickCert: +# http://segment7.net/projects/ruby/QuickCert/ + +class Puppet::Network::Server + class CA < Handler + attr_reader :ca + + @interface = XMLRPC::Service::Interface.new("puppetca") { |iface| + iface.add_method("array getcert(csr)") + } + + def autosign + if defined? @autosign + @autosign + else + Puppet[:autosign] + end + end + + # FIXME autosign? should probably accept both hostnames and IP addresses + def autosign?(hostname) + # simple values are easy + if autosign == true or autosign == false + return autosign + end + + # we only otherwise know how to handle files + unless autosign =~ /^\// + raise Puppet::Error, "Invalid autosign value %s" % + autosign.inspect + end + + unless FileTest.exists?(autosign) + unless defined? @@warnedonautosign + @@warnedonautosign = true + Puppet.info "Autosign is enabled but %s is missing" % autosign + end + return false + end + auth = Puppet::Network::AuthStore.new + File.open(autosign) { |f| + f.each { |line| + next if line =~ /^\s*#/ + next if line =~ /^\s*$/ + auth.allow(line.chomp) + } + } + + # for now, just cheat and pass a fake IP address to allowed? + return auth.allowed?(hostname, "127.1.1.1") + end + + def initialize(hash = {}) + Puppet.config.use(:puppet, :certificates, :ca) + if hash.include? :autosign + @autosign = hash[:autosign] + end + + @ca = Puppet::SSLCertificates::CA.new(hash) + end + + # our client sends us a csr, and we either store it for later signing, + # or we sign it right away + def getcert(csrtext, client = nil, clientip = nil) + csr = OpenSSL::X509::Request.new(csrtext) + + # Use the hostname from the CSR, not from the network. + subject = csr.subject + + nameary = subject.to_a.find { |ary| + ary[0] == "CN" + } + + if nameary.nil? + Puppet.err( + "Invalid certificate request: could not retrieve server name" + ) + return "invalid" + end + + hostname = nameary[1] + + unless @ca + Puppet.notice "Host %s asked for signing from non-CA master" % hostname + return "" + end + + # We used to save the public key, but it's basically unnecessary + # and it mucks with the permissions requirements. + # save_pk(hostname, csr.public_key) + + certfile = File.join(Puppet[:certdir], [hostname, "pem"].join(".")) + + # first check to see if we already have a signed cert for the host + cert, cacert = ca.getclientcert(hostname) + if cert and cacert + Puppet.info "Retrieving existing certificate for %s" % hostname + #Puppet.info "Cert: %s; Cacert: %s" % [cert.class, cacert.class] + return [cert.to_pem, cacert.to_pem] + elsif @ca + if self.autosign?(hostname) or client.nil? + if client.nil? + Puppet.info "Signing certificate for CA server" + end + # okay, we don't have a signed cert + # if we're a CA and autosign is turned on, then go ahead and sign + # the csr and return the results + Puppet.info "Signing certificate for %s" % hostname + cert, cacert = @ca.sign(csr) + #Puppet.info "Cert: %s; Cacert: %s" % [cert.class, cacert.class] + return [cert.to_pem, cacert.to_pem] + else # just write out the csr for later signing + if @ca.getclientcsr(hostname) + Puppet.info "Not replacing existing request from %s" % hostname + else + Puppet.notice "Host %s has a waiting certificate request" % + hostname + @ca.storeclientcsr(csr) + end + return ["", ""] + end + else + raise "huh?" + end + end + + private + + # Save the public key. + def save_pk(hostname, public_key) + pkeyfile = File.join(Puppet[:publickeydir], [hostname, "pem"].join('.')) + + if FileTest.exists?(pkeyfile) + currentkey = File.open(pkeyfile) { |k| k.read } + unless currentkey == public_key.to_s + raise Puppet::Error, "public keys for %s differ" % hostname + end + else + File.open(pkeyfile, "w", 0644) { |f| + f.print public_key.to_s + } + end + end + end +end + +# $Id$ diff --git a/lib/puppet/network/server/filebucket.rb b/lib/puppet/network/server/filebucket.rb new file mode 100755 index 000000000..77dbbde5e --- /dev/null +++ b/lib/puppet/network/server/filebucket.rb @@ -0,0 +1,167 @@ +#-------------------- +# accept and serve files + + +require 'webrick' +require 'xmlrpc/server' +require 'xmlrpc/client' +require 'facter' +require 'digest/md5' +require 'puppet/external/base64' + +class Puppet::Network::Server + class BucketError < RuntimeError; end + class FileBucket < Handler + Puppet.config.setdefaults("puppetmasterd", + :bucketdir => { + :default => "$vardir/bucket", + :mode => 0750, + :owner => "$user", + :group => "$group", + :desc => "Where FileBucket files are stored." + } + ) + + Puppet.config.setdefaults("filebucket", + :clientbucketdir => { + :default => "$vardir/clientbucket", + :mode => 0750, + :desc => "Where FileBucket files are stored locally." + } + ) + @interface = XMLRPC::Service::Interface.new("puppetbucket") { |iface| + iface.add_method("string addfile(string, string)") + iface.add_method("string getfile(string)") + } + + Puppet::Util.logmethods(self, true) + attr_reader :name, :path + + # this doesn't work for relative paths + def FileBucket.paths(base,md5) + return [ + File.join(base, md5), + File.join(base, md5, "contents"), + File.join(base, md5, "paths") + ] + end + + def initialize(hash) + if hash.include?(:ConflictCheck) + @conflictchk = hash[:ConflictCheck] + hash.delete(:ConflictCheck) + else + @conflictchk = true + end + + if hash.include?(:Path) + @path = hash[:Path] + hash.delete(:Path) + else + if defined? Puppet + @path = Puppet[:bucketdir] + else + @path = File.expand_path("~/.filebucket") + end + end + + Puppet.config.use(:filebucket) + + @name = "Filebucket[#{@path}]" + end + + # accept a file from a client + def addfile(contents, path, client = nil, clientip = nil) + if client + contents = Base64.decode64(contents) + end + md5 = Digest::MD5.hexdigest(contents) + + bpath, bfile, pathpath = FileBucket.paths(@path,md5) + + # if it's a new directory... + if Puppet.recmkdir(bpath) + msg = "Adding %s(%s)" % [path, md5] + msg += " from #{client}" if client + self.info msg + # ...then just create the file + File.open(bfile, File::WRONLY|File::CREAT, 0440) { |of| + of.print contents + } + else # if the dir already existed... + # ...we need to verify that the contents match the existing file + if @conflictchk + unless FileTest.exists?(bfile) + raise(BucketError, + "No file at %s for sum %s" % [bfile,md5], caller) + end + + curfile = File.read(bfile) + + # If the contents don't match, then we've found a conflict. + # Unlikely, but quite bad. + if curfile != contents + raise(BucketError, + "Got passed new contents for sum %s" % md5, caller) + else + msg = "Got duplicate %s(%s)" % [path, md5] + msg += " from #{client}" if client + self.info msg + end + end + end + + contents = "" + + # in either case, add the passed path to the list of paths + paths = nil + addpath = false + if FileTest.exists?(pathpath) + File.open(pathpath) { |of| + paths = of.readlines.collect { |l| l.chomp } + } + + # unless our path is already there... + unless paths.include?(path) + addpath = true + end + else + addpath = true + end + + # if it's a new file, or if our path isn't in the file yet, add it + if addpath + File.open(pathpath, File::WRONLY|File::CREAT|File::APPEND) { |of| + of.puts path + } + end + + return md5 + end + + def getfile(md5, client = nil, clientip = nil) + bpath, bfile, bpaths = FileBucket.paths(@path,md5) + + unless FileTest.exists?(bfile) + return false + end + + contents = nil + File.open(bfile) { |of| + contents = of.read + } + + if client + return Base64.encode64(contents) + else + return contents + end + end + + def to_s + self.name + end + end +end + +# $Id$ diff --git a/lib/puppet/network/server/fileserver.rb b/lib/puppet/network/server/fileserver.rb new file mode 100755 index 000000000..904d497ca --- /dev/null +++ b/lib/puppet/network/server/fileserver.rb @@ -0,0 +1,589 @@ +require 'puppet' +require 'webrick/httpstatus' +require 'cgi' +require 'delegate' + +class Puppet::Network::Server + class FileServerError < Puppet::Error; end + class FileServer < Handler + attr_accessor :local + + Puppet.setdefaults("fileserver", + :fileserverconfig => ["$confdir/fileserver.conf", + "Where the fileserver configuration is stored."]) + + CHECKPARAMS = [:mode, :type, :owner, :group, :checksum] + + @interface = XMLRPC::Service::Interface.new("fileserver") { |iface| + iface.add_method("string describe(string, string)") + iface.add_method("string list(string, string, boolean, array)") + iface.add_method("string retrieve(string, string)") + } + + # Describe a given file. This returns all of the manageable aspects + # of that file. + def describe(url, links = :ignore, client = nil, clientip = nil) + links = links.intern if links.is_a? String + + if links == :manage + raise Puppet::Network::Server::FileServerError, "Cannot currently copy links" + end + + mount, path = convert(url, client, clientip) + + if client + mount.debug "Describing %s for %s" % [url, client] + end + + obj = nil + unless obj = mount.check(path, links) + return "" + end + + desc = [] + CHECKPARAMS.each { |check| + if property = obj.property(check) + unless property.is + mount.debug "Manually retrieving info for %s" % check + property.retrieve + end + desc << property.is + else + if check == "checksum" and obj.property(:type).is == "file" + mount.notice "File %s does not have data for %s" % + [obj.name, check] + end + desc << nil + end + } + + return desc.join("\t") + end + + # Create a new fileserving module. + def initialize(hash = {}) + @mounts = {} + @files = {} + + if hash[:Local] + @local = hash[:Local] + else + @local = false + end + + if hash[:Config] == false + @noreadconfig = true + else + @config = Puppet::Util::LoadedFile.new( + hash[:Config] || Puppet[:fileserverconfig] + ) + @noreadconfig = false + end + + if hash.include?(:Mount) + @passedconfig = true + unless hash[:Mount].is_a?(Hash) + raise Puppet::DevError, "Invalid mount hash %s" % + hash[:Mount].inspect + end + + hash[:Mount].each { |dir, name| + if FileTest.exists?(dir) + self.mount(dir, name) + end + } + else + @passedconfig = false + readconfig(false) # don't check the file the first time. + end + end + + # List a specific directory's contents. + def list(url, links = :ignore, recurse = false, ignore = false, client = nil, clientip = nil) + mount, path = convert(url, client, clientip) + + if client + mount.debug "Listing %s for %s" % [url, client] + end + + obj = nil + unless FileTest.exists?(path) + return "" + end + + # We pass two paths here, but reclist internally changes one + # of the arguments when called internally. + desc = reclist(mount, path, path, recurse, ignore) + + if desc.length == 0 + mount.notice "Got no information on //%s/%s" % + [mount, path] + return "" + end + + desc.collect { |sub| + sub.join("\t") + }.join("\n") + end + + def local? + self.local + end + + # Mount a new directory with a name. + def mount(path, name) + if @mounts.include?(name) + if @mounts[name] != path + raise FileServerError, "%s is already mounted at %s" % + [@mounts[name].path, name] + else + # it's already mounted; no problem + return + end + end + + # Let the mounts do their own error-checking. + @mounts[name] = Mount.new(name, path) + @mounts[name].info "Mounted %s" % path + + return @mounts[name] + end + + # Retrieve a file from the local disk and pass it to the remote + # client. + def retrieve(url, links = :ignore, client = nil, clientip = nil) + links = links.intern if links.is_a? String + + mount, path = convert(url, client, clientip) + + if client + mount.info "Sending %s to %s" % [url, client] + end + + unless FileTest.exists?(path) + return "" + end + + links = links.intern if links.is_a? String + + if links == :ignore and FileTest.symlink?(path) + return "" + end + + str = nil + if links == :manage + raise Puppet::Error, "Cannot copy links yet." + else + str = File.read(path) + end + + if @local + return str + else + return CGI.escape(str) + end + end + + def umount(name) + @mounts.delete(name) if @mounts.include? name + end + + private + + def authcheck(file, mount, client, clientip) + # If we're local, don't bother passing in information. + if local? + client = nil + clientip = nil + end + unless mount.allowed?(client, clientip) + mount.warning "%s cannot access %s" % + [client, file] + raise Puppet::AuthorizationError, "Cannot access %s" % mount + end + end + + def convert(url, client, clientip) + readconfig + + url = URI.unescape(url) + + mount, stub = splitpath(url, client) + + authcheck(url, mount, client, clientip) + + path = nil + unless path = mount.subdir(stub, client) + mount.notice "Could not find subdirectory %s" % + "//%s/%s" % [mount, stub] + return "" + end + + return mount, path + end + + # Deal with ignore parameters. + def handleignore(children, path, ignore) + ignore.each { |ignore| + Dir.glob(File.join(path,ignore), File::FNM_DOTMATCH) { |match| + children.delete(File.basename(match)) + } + } + return children + end + + # Read the configuration file. + def readconfig(check = true) + return if @noreadconfig + + if check and ! @config.changed? + return + end + + newmounts = {} + begin + File.open(@config.file) { |f| + mount = nil + count = 1 + f.each { |line| + case line + when /^\s*#/: next # skip comments + when /^\s*$/: next # skip blank lines + when /\[(\w+)\]/: + name = $1 + if newmounts.include?(name) + raise FileServerError, "%s is already mounted at %s" % + [newmounts[name], name], count, @config.file + end + mount = Mount.new(name) + newmounts[name] = mount + when /^\s*(\w+)\s+(.+)$/: + var = $1 + value = $2 + case var + when "path": + begin + mount.path = value + rescue FileServerError => detail + Puppet.err "Removing mount %s: %s" % + [mount.name, detail] + newmounts.delete(mount.name) + end + when "allow": + value.split(/\s*,\s*/).each { |val| + begin + mount.info "allowing %s access" % val + mount.allow(val) + rescue AuthStoreError => detail + raise FileServerError.new(detail.to_s, + count, @config.file) + end + } + when "deny": + value.split(/\s*,\s*/).each { |val| + begin + mount.info "denying %s access" % val + mount.deny(val) + rescue AuthStoreError => detail + raise FileServerError.new(detail.to_s, + count, @config.file) + end + } + else + raise FileServerError.new("Invalid argument '%s'" % var, + count, @config.file) + end + else + raise FileServerError.new("Invalid line '%s'" % line.chomp, + count, @config.file) + end + count += 1 + } + } + rescue Errno::EACCES => detail + Puppet.err "FileServer error: Cannot read %s; cannot serve" % @config + #raise Puppet::Error, "Cannot read %s" % @config + rescue Errno::ENOENT => detail + Puppet.err "FileServer error: '%s' does not exist; cannot serve" % + @config + #raise Puppet::Error, "%s does not exit" % @config + #rescue FileServerError => detail + # Puppet.err "FileServer error: %s" % detail + end + + # Verify each of the mounts are valid. + # We let the check raise an error, so that it can raise an error + # pointing to the specific problem. + newmounts.each { |name, mount| + unless mount.valid? + raise FileServerError, "No path specified for mount %s" % + name + end + } + @mounts = newmounts + end + + # Recursively list the directory. FIXME This should be using + # puppet objects, not directly listing. + def reclist(mount, root, path, recurse, ignore) + # Take out the root of the path. + name = path.sub(root, '') + if name == "" + name = "/" + end + + if name == path + raise FileServerError, "Could not match %s in %s" % + [root, path] + end + + desc = [name] + ftype = File.stat(path).ftype + + desc << ftype + if recurse.is_a?(Integer) + recurse -= 1 + end + + ary = [desc] + if recurse == true or (recurse.is_a?(Integer) and recurse > -1) + if ftype == "directory" + children = Dir.entries(path) + if ignore + children = handleignore(children, path, ignore) + end + children.each { |child| + next if child =~ /^\.\.?$/ + reclist(mount, root, File.join(path, child), recurse, ignore).each { |cobj| + ary << cobj + } + } + end + end + + return ary.reject { |c| c.nil? } + end + + # Split the path into the separate mount point and path. + def splitpath(dir, client) + # the dir is based on one of the mounts + # so first retrieve the mount path + mount = nil + path = nil + if dir =~ %r{/(\w+)/?} + tmp = $1 + path = dir.sub(%r{/#{tmp}/?}, '') + + unless mount = @mounts[tmp] + raise FileServerError, "Fileserver module '%s' not mounted" % tmp + end + else + raise FileServerError, "Fileserver error: Invalid path '%s'" % dir + end + + if path == "" + path = nil + else + # Remove any double slashes that might have occurred + path = URI.unescape(path.gsub(/\/\//, "/")) + end + + return mount, path + end + + def to_s + "fileserver" + end + + # A simple class for wrapping mount points. Instances of this class + # don't know about the enclosing object; they're mainly just used for + # authorization. + class Mount < Puppet::Network::AuthStore + attr_reader :name + + Puppet::Util.logmethods(self, true) + + # Run 'retrieve' on a file. This gets the actual parameters, so + # we can pass them to the client. + def check(dir, links) + unless FileTest.exists?(dir) + self.notice "File source %s does not exist" % dir + return nil + end + + obj = fileobj(dir, links) + + # FIXME we should really have a timeout here -- we don't + # want to actually check on every connection, maybe no more + # than every 60 seconds or something. It'd be nice if we + # could use the builtin scheduling to do this. + + # Retrieval is enough here, because we don't want to cache + # any information in the state file, and we don't want to generate + # any state changes or anything. We don't even need to sync + # the checksum, because we're always going to hit the disk + # directly. + obj.retrieve + + return obj + end + + # Create a map for a specific client. + def clientmap(client) + { + "h" => client.sub(/\..*$/, ""), + "H" => client, + "d" => client.sub(/[^.]+\./, "") # domain name + } + end + + # Replace % patterns as appropriate. + def expand(path, client = nil) + # This map should probably be moved into a method. + map = nil + + if client + map = clientmap(client) + else + Puppet.notice "No client; expanding '%s' with local host" % + path + # Else, use the local information + map = localmap() + end + path.gsub(/%(.)/) do |v| + key = $1 + if key == "%" + "%" + else + map[key] || v + end + end + end + + # Do we have any patterns in our path, yo? + def expandable? + if defined? @expandable + @expandable + else + false + end + end + + # Create out object. It must have a name. + def initialize(name, path = nil) + unless name =~ %r{^\w+$} + raise FileServerError, "Invalid name format '%s'" % name + end + @name = name + + if path + self.path = path + else + @path = nil + end + + super() + end + + def fileobj(path, links) + obj = nil + if obj = Puppet.type(:file)[path] + # This can only happen in local fileserving, but it's an + # important one. It'd be nice if we didn't just set + # the check params every time, but I'm not sure it's worth + # the effort. + obj[:check] = CHECKPARAMS + else + obj = Puppet.type(:file).create( + :name => path, + :check => CHECKPARAMS + ) + end + + if links == :manage + links = :follow + end + + # This, ah, might be completely redundant + unless obj[:links] == links + obj[:links] = links + end + + return obj + end + + # Cache this manufactured map, since if it's used it's likely + # to get used a lot. + def localmap + unless defined? @@localmap + @@localmap = { + "h" => Facter.value("hostname"), + "H" => [Facter.value("hostname"), + Facter.value("domain")].join("."), + "d" => Facter.value("domain") + } + end + @@localmap + end + + # Return the path as appropriate, expanding as necessary. + def path(client = nil) + if expandable? + return expand(@path, client) + else + return @path + end + end + + # Set the path. + def path=(path) + # FIXME: For now, just don't validate paths with replacement + # patterns in them. + if path =~ /%./ + # Mark that we're expandable. + @expandable = true + else + unless FileTest.exists?(path) + raise FileServerError, "%s does not exist" % path + end + unless FileTest.directory?(path) + raise FileServerError, "%s is not a directory" % path + end + unless FileTest.readable?(path) + raise FileServerError, "%s is not readable" % path + end + @expandable = false + end + @path = path + end + + # Retrieve a specific directory relative to a mount point. + # If they pass in a client, then expand as necessary. + def subdir(dir = nil, client = nil) + basedir = self.path(client) + + dirname = if dir + File.join(basedir, dir.split("/").join(File::SEPARATOR)) + else + basedir + end + + dirname + end + + def to_s + "mount[#{@name}]" + end + + # Verify our configuration is valid. This should really check to + # make sure at least someone will be allowed, but, eh. + def valid? + return false unless @path + + return true + end + end + end +end + +# $Id$ diff --git a/lib/puppet/network/server/logger.rb b/lib/puppet/network/server/logger.rb new file mode 100755 index 000000000..f6bf9ba88 --- /dev/null +++ b/lib/puppet/network/server/logger.rb @@ -0,0 +1,52 @@ +require 'yaml' + +class Puppet::Network::Server + class LoggerError < RuntimeError; end + + # Receive logs from remote hosts. + class Logger < Handler + @interface = XMLRPC::Service::Interface.new("puppetlogger") { |iface| + iface.add_method("void addlog(string)") + } + + # accept a log message from a client, and route it accordingly + def addlog(message, client = nil, clientip = nil) + unless message + raise Puppet::DevError, "Did not receive message" + end + + Puppet.info message.inspect + # if the client is set, then we're not local + if client + begin + message = YAML.load(CGI.unescape(message)) + #message = message + rescue => detail + raise XMLRPC::FaultException.new( + 1, "Could not unYAML log message from %s" % client + ) + end + end + + unless message + raise Puppet::DevError, "Could not resurrect message" + end + + # Mark it as remote, so it's not sent to syslog + message.remote = true + + if client + if ! message.source or message.source == "Puppet" + message.source = client + end + end + + Puppet::Util::Log.newmessage(message) + + # This is necessary or XMLRPC gets all pukey + return "" + end + end +end + +# $Id$ diff --git a/lib/puppet/network/server/master.rb b/lib/puppet/network/server/master.rb new file mode 100644 index 000000000..b7096cd6d --- /dev/null +++ b/lib/puppet/network/server/master.rb @@ -0,0 +1,212 @@ +require 'openssl' +require 'puppet' +require 'puppet/parser/interpreter' +require 'puppet/sslcertificates' +require 'xmlrpc/server' +require 'yaml' + +class Puppet::Network::Server + class MasterError < Puppet::Error; end + class Master < Handler + include Puppet::Util + + attr_accessor :ast, :local + attr_reader :ca + + @interface = XMLRPC::Service::Interface.new("puppetmaster") { |iface| + iface.add_method("string getconfig(string)") + iface.add_method("int freshness()") + } + + # FIXME At some point, this should be autodocumenting. + def addfacts(facts) + # Add our server version to the fact list + facts["serverversion"] = Puppet.version.to_s + + # And then add the server name and IP + {"servername" => "hostname", + "serverip" => "ipaddress" + }.each do |var, fact| + if obj = Facter[fact] + facts[var] = obj.value + else + Puppet.warning "Could not retrieve fact %s" % fact + end + end + end + + # Manipulate the client name as appropriate. + def clientname(name, ip, facts) + # Always use the hostname from Facter. + client = facts["hostname"] + clientip = facts["ipaddress"] + if Puppet[:node_name] == 'cert' + if name + client = name + end + if ip + clientip = ip + end + end + + return client, clientip + end + + # Tell a client whether there's a fresh config for it + def freshness(client = nil, clientip = nil) + if Puppet.features.rails? and Puppet[:storeconfigs] + host = Puppet::Rails::Host.find_or_create_by_name(client) + host.last_freshcheck = Time.now + if clientip and (! host.ip or host.ip == "") + host.ip = clientip + end + host.save + end + if defined? @interpreter + return @interpreter.parsedate + else + return 0 + end + end + + def initialize(hash = {}) + args = {} + + # Allow specification of a code snippet or of a file + if code = hash[:Code] + args[:Code] = code + else + args[:Manifest] = hash[:Manifest] || Puppet[:manifest] + end + + if hash[:Local] + @local = hash[:Local] + else + @local = false + end + + args[:Local] = @local + + if hash.include?(:CA) and hash[:CA] + @ca = Puppet::SSLCertificates::CA.new() + else + @ca = nil + end + + Puppet.debug("Creating interpreter") + + if hash.include?(:UseNodes) + args[:UseNodes] = hash[:UseNodes] + elsif @local + args[:UseNodes] = false + end + + # This is only used by the cfengine module, or if --loadclasses was + # specified in +puppet+. + if hash.include?(:Classes) + args[:Classes] = hash[:Classes] + end + + @interpreter = Puppet::Parser::Interpreter.new(args) + end + + def getconfig(facts, format = "marshal", client = nil, clientip = nil) + if @local + # we don't need to do anything, since we should already + # have raw objects + Puppet.debug "Our client is local" + else + Puppet.debug "Our client is remote" + + # XXX this should definitely be done in the protocol, somehow + case format + when "marshal": + Puppet.warning "You should upgrade your client. 'Marshal' will not be supported much longer." + begin + facts = Marshal::load(CGI.unescape(facts)) + rescue => detail + raise XMLRPC::FaultException.new( + 1, "Could not rebuild facts" + ) + end + when "yaml": + begin + facts = YAML.load(CGI.unescape(facts)) + rescue => detail + raise XMLRPC::FaultException.new( + 1, "Could not rebuild facts" + ) + end + else + raise XMLRPC::FaultException.new( + 1, "Unavailable config format %s" % format + ) + end + end + + client, clientip = clientname(client, clientip, facts) + + # Add any server-side facts to our server. + addfacts(facts) + + retobjects = nil + + # This is hackish, but there's no "silence" option for benchmarks + # right now + if @local + #begin + retobjects = @interpreter.run(client, facts) + #rescue Puppet::Error => detail + # Puppet.err detail + # raise XMLRPC::FaultException.new( + # 1, detail.to_s + # ) + #rescue => detail + # Puppet.err detail.to_s + # return "" + #end + else + benchmark(:notice, "Compiled configuration for %s" % client) do + begin + retobjects = @interpreter.run(client, facts) + rescue Puppet::Error => detail + Puppet.err detail + raise XMLRPC::FaultException.new( + 1, detail.to_s + ) + rescue => detail + Puppet.err detail.to_s + return "" + end + end + end + + if @local + return retobjects + else + str = nil + case format + when "marshal": + str = Marshal::dump(retobjects) + when "yaml": + str = YAML.dump(retobjects) + else + raise XMLRPC::FaultException.new( + 1, "Unavailable config format %s" % format + ) + end + return CGI.escape(str) + end + end + + def local? + if defined? @local and @local + return true + else + return false + end + end + end +end + +# $Id$ diff --git a/lib/puppet/network/server/report.rb b/lib/puppet/network/server/report.rb new file mode 100755 index 000000000..cd0214e30 --- /dev/null +++ b/lib/puppet/network/server/report.rb @@ -0,0 +1,174 @@ +# A simple server for triggering a new run on a Puppet client. +class Puppet::Network::Server + class Report < Handler + class << self + include Puppet::Util::ClassGen + end + + module ReportBase + include Puppet::Util::Docs + attr_writer :useyaml + + def useyaml? + if defined? @useyaml + @useyaml + else + false + end + end + end + + @interface = XMLRPC::Service::Interface.new("puppetreports") { |iface| + iface.add_method("string report(array)") + } + + Puppet.setdefaults(:reporting, + :reports => ["store", + "The list of reports to generate. All reports are looked for + in puppet/reports/.rb, and multiple report names should be + comma-separated (whitespace is okay)." + ], + :reportdir => {:default => "$vardir/reports", + :mode => 0750, + :owner => "$user", + :group => "$group", + :desc => "The directory in which to store reports + received from the client. Each client gets a separate + subdirectory."} + ) + + @reports = {} + @reportloader = Puppet::Util::Autoload.new(self, "puppet/reports") + + class << self + attr_reader :hooks + end + + # Add a new report type. + def self.newreport(name, options = {}, &block) + name = symbolize(name) + + mod = genmodule(name, :extend => ReportBase, :hash => @reports, :block => block) + + if options[:useyaml] + mod.useyaml = true + end + + mod.send(:define_method, :report_name) do + name + end + end + + # Load a report. + def self.report(name) + name = name.intern if name.is_a? String + unless @reports.include? name + if @reportloader.load(name) + unless @reports.include? name + Puppet.warning( + "Loaded report file for %s but report was not defined" % + name + ) + return nil + end + else + return nil + end + end + @reports[symbolize(name)] + end + + def self.reportdocs + docs = "" + + # Use this method so they all get loaded + reports.sort { |a,b| a.to_s <=> b.to_s }.each do |name| + mod = self.report(name) + docs += "## %s\n\n" % name + + docs += Puppet::Util::Docs.scrub(mod.doc) + "\n\n" + end + + docs + end + + def self.reports + @reportloader.loadall + @reports.keys + end + + def initialize(*args) + super + Puppet.config.use(:reporting) + Puppet.config.use(:metrics) + end + + # Accept a report from a client. + def report(report, client = nil, clientip = nil) + # Unescape the report + unless @local + report = CGI.unescape(report) + end + + begin + process(report) + rescue => detail + Puppet.err "Could not process report %s: %s" % [$1, detail] + if Puppet[:trace] + puts detail.backtrace + end + end + end + + private + + # Process the report using all of the existing hooks. + def process(yaml) + return if Puppet[:reports] == "none" + + # First convert the report to real objects + begin + report = YAML.load(yaml) + rescue => detail + Puppet.warning "Could not load report: %s" % detail + return + end + + # Used for those reports that accept yaml + client = report.host + + reports().each do |name| + if mod = self.class.report(name) + Puppet.info "Processing report %s for %s" % [name, client] + + # We have to use a dup because we're including a module in the + # report. + newrep = report.dup + begin + newrep.extend(mod) + if mod.useyaml? + newrep.process(yaml) + else + newrep.process + end + rescue => detail + if Puppet[:trace] + puts detail.backtrace + end + Puppet.err "Report %s failed: %s" % + [name, detail] + end + else + Puppet.warning "No report named '%s'" % name + end + end + end + + # Handle the parsing of the reports attribute. + def reports + Puppet[:reports].gsub(/(^\s+)|(\s+$)/, '').split(/\s*,\s*/) + end + end +end + +# $Id$ diff --git a/lib/puppet/network/server/resource.rb b/lib/puppet/network/server/resource.rb new file mode 100755 index 000000000..37e331a13 --- /dev/null +++ b/lib/puppet/network/server/resource.rb @@ -0,0 +1,190 @@ +require 'puppet' +require 'puppet/network/server' + +# Serve Puppet elements. Useful for querying, copying, and, um, other stuff. +class Puppet::Network::Server + class Resource < Handler + attr_accessor :local + + @interface = XMLRPC::Service::Interface.new("resource") { |iface| + iface.add_method("string apply(string, string)") + iface.add_method("string describe(string, string, array, array)") + iface.add_method("string list(string, array, string)") + } + + # Apply a TransBucket as a transaction. + def apply(bucket, format = "yaml", client = nil, clientip = nil) + unless @local + begin + case format + when "yaml": + bucket = YAML::load(Base64.decode64(bucket)) + else + raise Puppet::Error, "Unsupported format '%s'" % format + end + rescue => detail + raise Puppet::Error, "Could not load YAML TransBucket: %s" % detail + end + end + + component = bucket.to_type + + # Create a client, but specify the remote machine as the server + # because the class requires it, even though it's unused + client = Puppet::Network::Client::MasterClient.new(:Server => client||"localhost") + + # Set the objects + client.objects = component + + # And then apply the configuration. This way we're reusing all + # the code in there. It should probably just be separated out, though. + transaction = client.apply + + # And then clean up + component.remove + + # It'd be nice to return some kind of report, but... at this point + # we have no such facility. + return "success" + end + + # Describe a given object. This returns the 'is' values for every property + # available on the object type. + def describe(type, name, retrieve = nil, ignore = [], format = "yaml", client = nil, clientip = nil) + Puppet.info "Describing %s[%s]" % [type.to_s.capitalize, name] + @local = true unless client + typeklass = nil + unless typeklass = Puppet.type(type) + raise Puppet::Error, "Puppet type %s is unsupported" % type + end + + obj = nil + + retrieve ||= :all + ignore ||= [] + + if obj = typeklass[name] + obj[:check] = retrieve + else + begin + obj = typeklass.create(:name => name, :check => retrieve) + rescue Puppet::Error => detail + raise Puppet::Error, "%s[%s] could not be created: %s" % + [type, name, detail] + end + end + + unless obj + raise XMLRPC::FaultException.new( + 1, "Could not create %s[%s]" % [type, name] + ) + end + + trans = obj.to_trans + + # Now get rid of any attributes they specifically don't want + ignore.each do |st| + if trans.include? st + trans.delete(st) + end + end + + # And get rid of any attributes that are nil + trans.each do |attr, value| + if value.nil? + trans.delete(attr) + end + end + + unless @local + case format + when "yaml": + trans = Base64.encode64(YAML::dump(trans)) + else + raise XMLRPC::FaultException.new( + 1, "Unavailable config format %s" % format + ) + end + end + + return trans + end + + # Create a new fileserving module. + def initialize(hash = {}) + if hash[:Local] + @local = hash[:Local] + else + @local = false + end + end + + # List all of the elements of a given type. + def list(type, ignore = [], base = nil, format = "yaml", client = nil, clientip = nil) + @local = true unless client + typeklass = nil + unless typeklass = Puppet.type(type) + raise Puppet::Error, "Puppet type %s is unsupported" % type + end + + # They can pass in false + ignore ||= [] + ignore = [ignore] unless ignore.is_a? Array + bucket = Puppet::TransBucket.new + bucket.type = typeklass.name + + typeklass.list.each do |obj| + next if ignore.include? obj.name + + object = Puppet::TransObject.new(obj.name, typeklass.name) + bucket << object + end + + unless @local + case format + when "yaml": + begin + bucket = Base64.encode64(YAML::dump(bucket)) + rescue => detail + Puppet.err detail + raise XMLRPC::FaultException.new( + 1, detail.to_s + ) + end + else + raise XMLRPC::FaultException.new( + 1, "Unavailable config format %s" % format + ) + end + end + + return bucket + end + + private + + def authcheck(file, mount, client, clientip) + unless mount.allowed?(client, clientip) + mount.warning "%s cannot access %s" % + [client, file] + raise Puppet::AuthorizationError, "Cannot access %s" % mount + end + end + + # Deal with ignore parameters. + def handleignore(children, path, ignore) + ignore.each { |ignore| + Dir.glob(File.join(path,ignore), File::FNM_DOTMATCH) { |match| + children.delete(File.basename(match)) + } + } + return children + end + + def to_s + "resource" + end + end +end + +# $Id$ diff --git a/lib/puppet/network/server/rights.rb b/lib/puppet/network/server/rights.rb new file mode 100755 index 000000000..11da3b705 --- /dev/null +++ b/lib/puppet/network/server/rights.rb @@ -0,0 +1,74 @@ +require 'ipaddr' +require 'puppet/network/server/authstore' + +# Define a set of rights and who has access to them. +class Puppet::Network::Rights < Hash + # We basically just proxy directly to our rights. Each Right stores + # its own auth abilities. + [:allow, :allowed?, :deny].each do |method| + define_method(method) do |name, *args| + name = name.intern if name.is_a? String + + if obj = right(name) + obj.send(method, *args) + else + raise ArgumentError, "Unknown right '%s'" % name + end + end + end + + def [](name) + name = name.intern if name.is_a? String + super(name) + end + + # Define a new right to which access can be provided. + def newright(name) + name = name.intern if name.is_a? String + shortname = Right.shortname(name) + if self.include? name + raise ArgumentError, "Right '%s' is already defined" % name + else + self[name] = Right.new(name, shortname) + end + end + + private + + # Retrieve a right by name. + def right(name) + name = name.intern if name.is_a? String + self[name] + end + + # A right. + class Right < Puppet::Network::AuthStore + attr_accessor :name, :shortname + + Puppet::Util.logmethods(self, true) + + def self.shortname(name) + name.to_s[0..0] + end + + def initialize(name, shortname = nil) + @name = name + @shortname = shortname + unless @shortname + @shortname = Right.shortname(name) + end + super() + end + + def to_s + "access[%s]" % @name + end + + # There's no real check to do at this point + def valid? + true + end + end +end + +# $Id$ diff --git a/lib/puppet/network/server/runner.rb b/lib/puppet/network/server/runner.rb new file mode 100755 index 000000000..c0ec8fb9d --- /dev/null +++ b/lib/puppet/network/server/runner.rb @@ -0,0 +1,61 @@ +class Puppet::Network::Server + class MissingMasterError < RuntimeError; end # Cannot find the master client + # A simple server for triggering a new run on a Puppet client. + class Runner < Handler + @interface = XMLRPC::Service::Interface.new("puppetrunner") { |iface| + iface.add_method("string run(string, string)") + } + + # Run the client configuration right now, optionally specifying + # tags and whether to ignore schedules + def run(tags = nil, ignoreschedules = false, fg = true, client = nil, clientip = nil) + # We need to retrieve the client + master = Puppet::Network::Client::MasterClient.instance + + unless master + raise MissingMasterError, "Could not find the master client" + end + + if Puppet::Util::Pidlock.new(Puppet[:puppetdlockfile]).locked? + Puppet.notice "Could not trigger run; already running" + return "running" + end + + if tags == "" + tags = nil + end + + if ignoreschedules == "" + ignoreschedules == nil + end + + msg = "" + if client + msg = "%s(%s) " % [client, clientip] + end + msg += "triggered run" % + if tags + msg += " with tags %s" % tags + end + + if ignoreschedules + msg += " without schedules" + end + + Puppet.notice msg + + # And then we need to tell it to run, with this extra info. + if fg + master.run(tags, ignoreschedules) + else + Puppet.newthread do + master.run(tags, ignoreschedules) + end + end + + return "success" + end + end +end + +# $Id$ diff --git a/lib/puppet/network/server/servlet.rb b/lib/puppet/network/server/servlet.rb new file mode 100644 index 000000000..0a7253eff --- /dev/null +++ b/lib/puppet/network/server/servlet.rb @@ -0,0 +1,277 @@ +require 'xmlrpc/server' + +class Puppet::Network::Server + class ServletError < RuntimeError; end + class Servlet < XMLRPC::WEBrickServlet + ERR_UNAUTHORIZED = 30 + + attr_accessor :request + + # this is just a duplicate of the normal method; it's here for + # debugging when i need it + def self.get_instance(server, *options) + self.new(server, *options) + end + + # This is a hackish way to avoid an auth message every time we have a + # normal operation + def self.log(msg) + unless defined? @logs + @logs = {} + end + if @logs.include?(msg) + @logs[msg] += 1 + else + Puppet.info msg + @logs[msg] = 1 + end + end + + def add_handler(interface, handler) + @loadedhandlers << interface.prefix + super + end + + # Verify that our client has access. We allow untrusted access to + # puppetca methods but no others. + def authorize(request, method) + namespace = method.sub(/\..+/, '') + client = request.peeraddr[2] + if defined? @client and @client + client = @client + end + ip = request.peeraddr[3] + if request.client_cert + if @puppetserver.authconfig.exists? + allowed = @puppetserver.authconfig.allowed?(method, client, ip) + + if allowed + Puppet.debug "Allowing %s(%s) trusted access to %s" % + [client, ip, method] + return true + else + Puppet.debug "Denying %s(%s) trusted access to %s" % + [client, ip, method] + return false + end + else + # This is pretty hackish, but... + # This means we can't actually test this method at this point. + # The next release of Puppet will almost definitely require + # this file to exist or will default to denying all access. + if Puppet.name == "puppetmasterd" or defined? Test::Unit::TestCase + Puppet.debug "Allowing %s(%s) trusted access to %s" % + [client, ip, method] + return true + else + Puppet.debug "Denying %s(%s) trusted access to %s on %s" % + [client, ip, method, Puppet.name] + return false + end + end + else + if method =~ /^puppetca\./ + Puppet.notice "Allowing %s(%s) untrusted access to CA methods" % + [client, ip] + else + Puppet.err "Unauthenticated client %s(%s) cannot call %s" % + [client, ip, method] + return false + end + end + end + + def available?(method) + namespace = method.sub(/\..+/, '') + client = request.peeraddr[2] + ip = request.peeraddr[3] + if @loadedhandlers.include?(namespace) + return true + else + Puppet.warning "Client %s(%s) requested unavailable functionality %s" % + [client, ip, namespace] + return false + end + end + + def initialize(server, handlers) + @puppetserver = server + @notified = {} + # the servlet base class does not consume any arguments + # and its BasicServer base class only accepts a 'class_delim' + # option which won't change in Puppet at all + # thus, we don't need to pass any args to our base class, + # and we can consume them all ourselves + super() + + @loadedhandlers = [] + handlers.each { |handler| + #Puppet.debug "adding handler for %s" % handler.class + self.add_handler(handler.class.interface, handler) + } + + # Initialize these to nil, but they will get set to values + # by the 'service' method. These have to instance variables + # because I don't have a clear line from the service method to + # the service hook. + @request = nil + @client = nil + @clientip = nil + + self.set_service_hook { |obj, *args| + if @client and @clientip + args.push(@client, @clientip) + end + begin + obj.call(*args) + rescue XMLRPC::FaultException + raise + rescue Puppet::AuthorizationError => detail + #Puppet.warning obj.inspect + #Puppet.warning args.inspect + Puppet.err "Permission denied: %s" % detail.to_s + raise XMLRPC::FaultException.new( + 1, detail.to_s + ) + rescue Puppet::Error => detail + #Puppet.warning obj.inspect + #Puppet.warning args.inspect + if Puppet[:trace] + puts detail.backtrace + end + Puppet.err detail.to_s + error = XMLRPC::FaultException.new( + 1, detail.to_s + ) + error.set_backtrace detail.backtrace + raise error + rescue => detail + #Puppet.warning obj.inspect + #Puppet.warning args.inspect + if Puppet[:trace] + puts detail.backtrace + end + Puppet.err "Could not call: %s" % detail.to_s + error = XMLRPC::FaultException.new(1, detail.to_s) + error.set_backtrace detail.backtrace + raise error + end + } + end + + # Handle the actual request. This does some basic collection of + # data, and then just calls the parent method. + def service(request, response) + @request = request + + # The only way that @client can be nil is if the request is local. + if peer = request.peeraddr + @client = peer[2] + @clientip = peer[3] + else + raise XMLRPC::FaultException.new( + ERR_UNCAUGHT_EXCEPTION, + "Could not retrieve client information" + ) + end + + # If they have a certificate (which will almost always be true) + # then we get the hostname from the cert, instead of via IP + # info + if cert = request.client_cert + nameary = cert.subject.to_a.find { |ary| + ary[0] == "CN" + } + + if nameary.nil? + Puppet.warning "Could not retrieve server name from cert" + else + unless @client == nameary[1] + Puppet.debug "Overriding %s with cert name %s" % + [@client, nameary[1]] + @client = nameary[1] + end + end + end + begin + super + rescue => detail + Puppet.err "Could not service request: %s: %s" % + [detail.class, detail] + end + @client = nil + @clientip = nil + @request = nil + end + + private + + # this is pretty much just a copy of the original method but with more + # feedback + # here's where we have our authorization hooks + def dispatch(methodname, *args) + + if defined? @request and @request + unless self.available?(methodname) + raise XMLRPC::FaultException.new( + ERR_UNAUTHORIZED, + "Functionality %s not available" % + methodname.sub(/\..+/, '') + ) + end + unless self.authorize(@request, methodname) + raise XMLRPC::FaultException.new( + ERR_UNAUTHORIZED, + "Host %s not authorized to call %s" % + [@request.host, methodname] + ) + end + else + raise Puppet::DevError, "Did not get request in dispatch" + end + + #Puppet.warning "dispatch on %s called with %s" % + # [methodname, args.inspect] + for name, obj in @handler + if obj.kind_of? Proc + unless methodname == name + #Puppet.debug "obj is proc but %s != %s" % + # [methodname, name] + next + end + else + unless methodname =~ /^#{name}(.+)$/ + #Puppet.debug "methodname did not match" + next + end + unless obj.respond_to? $1 + #Puppet.debug "methodname does not respond to %s" % $1 + next + end + obj = obj.method($1) + end + + if check_arity(obj, args.size) + if @service_hook.nil? + return obj.call(*args) + else + return @service_hook.call(obj, *args) + end + else + Puppet.debug "arity is incorrect" + end + end + + if @default_handler.nil? + raise XMLRPC::FaultException.new( + ERR_METHOD_MISSING, + "Method #{methodname} missing or wrong number of parameters!" + ) + else + @default_handler.call(methodname, *args) + end + end + end +end + +# $Id$ diff --git a/lib/puppet/networkclient.rb b/lib/puppet/networkclient.rb deleted file mode 100644 index f082665c0..000000000 --- a/lib/puppet/networkclient.rb +++ /dev/null @@ -1,181 +0,0 @@ -require 'puppet' -require 'puppet/sslcertificates' -require 'puppet/type' -require 'facter' -require 'openssl' -require 'puppet/transaction' -require 'puppet/transportable' -require 'puppet/daemon' -require 'puppet/server' -require 'puppet/external/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 < Puppet::Error; end - class ClientError < Puppet::Error; end - #--------------------------------------------------------------- - if $noclientnetworking - Puppet.err "Could not load client network libs: %s" % $noclientnetworking - else - class NetworkClient < XMLRPC::Client - attr_accessor :puppet_server, :puppet_port - @clients = {} - - class << self - include Puppet::Util - include Puppet::Util::ClassGen - end - - # Create a netclient for each handler - def self.mkclients - # add the methods associated with each namespace - Puppet::Server::Handler.each { |handler| - interface = handler.interface - namespace = interface.prefix - - # Create a subclass for every client type. This is - # so that all of the methods are on their own class, - # so that they namespaces can define the same methods if - # they want. - constant = handler.to_s.sub(/^.+::/, '') - name = namespace.downcase - newclient = genclass(name, :hash => @clients, - :constant => constant) - - interface.methods.each { |ary| - method = ary[0] - if public_method_defined?(method) - raise Puppet::DevError, "Method %s is already defined" % - method - end - newclient.send(:define_method,method) { |*args| - Puppet.debug "Calling %s.%s" % [namespace, 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 - raise NetworkClientError, - "Certificates were not trusted: %s" % detail - 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] - raise NetworkClientError, msg - rescue SocketError => detail - error = NetworkClientError.new( - "Could not find server %s" % @puppetserver - ) - error.set_backtrace detail.backtrace - raise error - rescue => detail - Puppet.err "Could not call %s.%s: %s" % - [namespace, method, detail.inspect] - error = NetworkClientError.new(detail.to_s) - error.set_backtrace detail.backtrace - raise error - end - } - } - } - end - - def self.netclient(namespace) - if @clients.empty? - self.mkclients() - end - - namespace = symbolize(namespace) - - @clients[namespace] - end - - def ca_file=(cafile) - @http.ca_file = cafile - store = OpenSSL::X509::Store.new - store.add_file(cafile) - 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] - - @puppet_server = hash[:Server] - @puppet_port = hash[:Port] - - @puppetserver = hash[:Server] - - super( - hash[:Server], - hash[:Path], - hash[:Port], - nil, # proxy_host - nil, # proxy_port - nil, # user - nil, # password - true, # use_ssl - 120 # a two minute timeout, instead of 30 seconds - ) - - if hash[:Certificate] - self.cert = hash[:Certificate] - else - unless defined? $nocertwarned - Puppet.err "No certificate; running with reduced functionality." - $nocertwarned = true - end - 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 - - def local? - false - end - end - end -end - -# $Id$ diff --git a/lib/puppet/reports/log.rb b/lib/puppet/reports/log.rb index 614a07c7a..c33bf0a67 100644 --- a/lib/puppet/reports/log.rb +++ b/lib/puppet/reports/log.rb @@ -1,6 +1,6 @@ require 'puppet' -Puppet::Server::Report.newreport(:log) do +Puppet::Network::Server::Report.newreport(:log) do desc "Send all received logs to the local log destinations." def process diff --git a/lib/puppet/reports/rrdgraph.rb b/lib/puppet/reports/rrdgraph.rb index ef353a1b9..0fbe6e5ca 100644 --- a/lib/puppet/reports/rrdgraph.rb +++ b/lib/puppet/reports/rrdgraph.rb @@ -1,6 +1,6 @@ require 'puppet' -Puppet::Server::Report.newreport(:rrdgraph) do +Puppet::Network::Server::Report.newreport(:rrdgraph) do desc "Graph all available data about hosts using the RRD library. You must have the RRD binary library installed to use this report, which you can get from [Tobias Oetiker's site](http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/pub/contrib/). diff --git a/lib/puppet/reports/store.rb b/lib/puppet/reports/store.rb index 23bbc037e..ed4f08a9e 100644 --- a/lib/puppet/reports/store.rb +++ b/lib/puppet/reports/store.rb @@ -1,6 +1,6 @@ require 'puppet' -Puppet::Server::Report.newreport(:store, :useyaml => true) do +Puppet::Network::Server::Report.newreport(:store, :useyaml => true) do Puppet.config.use(:reporting) desc "Store the yaml report on disk. Each host sends its report as a YAML dump diff --git a/lib/puppet/reports/tagmail.rb b/lib/puppet/reports/tagmail.rb index fef01bb39..238de4538 100644 --- a/lib/puppet/reports/tagmail.rb +++ b/lib/puppet/reports/tagmail.rb @@ -14,7 +14,7 @@ Puppet.config.setdefaults(:reporting, require 'net/smtp' -Puppet::Server::Report.newreport(:tagmail) do +Puppet::Network::Server::Report.newreport(:tagmail) do desc "This report sends specific log messages to specific email addresses based on the tags in the log messages. See the [tag documentation](/trac/puppet/wiki/UsingTags) for more information diff --git a/lib/puppet/server.rb b/lib/puppet/server.rb deleted file mode 100644 index aabc87b50..000000000 --- a/lib/puppet/server.rb +++ /dev/null @@ -1,221 +0,0 @@ -# the server -# -# allow things to connect to us and communicate, and stuff - -require 'puppet' -require 'puppet/daemon' - -$noservernetworking = false - -begin - require 'webrick' - require 'webrick/https' - require 'cgi' - require 'xmlrpc/server' - require 'xmlrpc/client' -rescue LoadError => detail - $noservernetworking = detail -end - -module Puppet - class ServerError < RuntimeError; end - #--------------------------------------------------------------- - if $noservernetworking - Puppet.err "Could not create server: %s" % $noservernetworking - class Server; end - else - class Server < WEBrick::HTTPServer - include Puppet::Daemon - - Puppet.config.setdefaults(:puppetd, - :listen => [false, "Whether puppetd should listen for - connections. If this is true, then by default only the - ``runner`` server is started, which allows remote authorized - and authenticated nodes to connect and trigger ``puppetd`` - runs."] - ) - - # Create our config object if necessary. This works even if - # there's no configuration file. - def authconfig - unless defined? @authconfig - @authconfig = Puppet::Server::AuthConfig.new() - end - - @authconfig - end - - # Read the CA cert and CRL and populate an OpenSSL::X509::Store - # with them, with flags appropriate for checking client - # certificates for revocation - def x509store - if Puppet[:cacrl] == 'none' - # No CRL, no store needed - return nil - end - unless File.exist?(Puppet[:cacrl]) - raise Puppet::Error, "Could not find CRL" - end - crl = OpenSSL::X509::CRL.new(File.read(Puppet[:cacrl])) - store = OpenSSL::X509::Store.new - store.purpose = OpenSSL::X509::PURPOSE_ANY - store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK - store.add_file(@cacertfile) - store.add_crl(crl) - return store - end - - def initialize(hash = {}) - Puppet.info "Starting server for Puppet version %s" % Puppet.version - daemonize = nil - if hash.include?(:Daemonize) - daemonize = hash[:Daemonize] - end - - # FIXME we should have some kind of access control here, using - # :RequestHandler - hash[:Port] ||= Puppet[:masterport] - hash[:Logger] ||= self.httplog - hash[:AccessLog] ||= [ - [ self.httplog, WEBrick::AccessLog::COMMON_LOG_FORMAT ], - [ self.httplog, WEBrick::AccessLog::REFERER_LOG_FORMAT ] - ] - - if hash.include?(:Handlers) - unless hash[:Handlers].is_a?(Hash) - raise ServerError, "Handlers must have arguments" - end - - @handlers = hash[:Handlers].collect { |handler, args| - hclass = nil - unless hclass = Handler.handler(handler) - raise ServerError, "Invalid handler %s" % handler - end - hclass.new(args) - } - else - raise ServerError, "A server must have handlers" - end - - # okay, i need to retrieve my cert and set it up, somehow - # the default case will be that i'm also the ca - if ca = @handlers.find { |handler| handler.is_a?(Puppet::Server::CA) } - @driver = ca - @secureinit = true - self.fqdn - else - if hash.include?(:NoSecureInit) - @secureinit = false - else - @secureinit = true - end - end - - unless self.readcert - unless self.requestcert - raise Puppet::Error, "Cannot start without certificates" - end - end - - hash[:SSLCertificateStore] = x509store - hash[:SSLCertificate] = @cert - hash[:SSLPrivateKey] = @key - hash[:SSLStartImmediately] = true - hash[:SSLEnable] = true - hash[:SSLCACertificateFile] = @cacertfile - hash[:SSLVerifyClient] = OpenSSL::SSL::VERIFY_PEER - hash[:SSLCertName] = nil - - super(hash) - - Puppet.info "Listening on port %s" % hash[:Port] - - # this creates a new servlet for every connection, - # but all servlets have the same list of handlers - # thus, the servlets can have their own state -- passing - # around the requests and such -- but the handlers - # have a global state - - # mount has to be called after the server is initialized - self.mount("/RPC2", Puppet::Server::Servlet, @handlers) - end - - # the base class for the different handlers - class Handler - attr_accessor :server - class << self - include Puppet::Util - end - - @subclasses = [] - - def self.each - @subclasses.each { |c| yield c } - end - - def self.handler(name) - name = name.to_s.downcase - @subclasses.find { |h| - h.name.to_s.downcase == name - } - end - - def self.inherited(sub) - @subclasses << sub - end - - def self.interface - if defined? @interface - return @interface - else - raise Puppet::DevError, "Handler %s has no defined interface" % - self - end - end - - def self.name - unless defined? @name - @name = self.to_s.sub(/.+::/, '').intern - end - - return @name - end - - def initialize(hash = {}) - end - end - - - class ServerStatus < Handler - - @interface = XMLRPC::Service::Interface.new("status") { |iface| - iface.add_method("int status()") - } - - @name = :Status - - def status(status = nil, client = nil, clientip = nil) - return 1 - end - end - - end - end - - #--------------------------------------------------------------- -end - -require 'puppet/server/authstore' -require 'puppet/server/authconfig' -require 'puppet/server/servlet' -require 'puppet/server/master' -require 'puppet/server/ca' -require 'puppet/server/fileserver' -require 'puppet/server/filebucket' -require 'puppet/server/resource' -require 'puppet/server/runner' -require 'puppet/server/logger' -require 'puppet/server/report' -require 'puppet/client' - -# $Id$ diff --git a/lib/puppet/server/authconfig.rb b/lib/puppet/server/authconfig.rb deleted file mode 100644 index d43371a77..000000000 --- a/lib/puppet/server/authconfig.rb +++ /dev/null @@ -1,177 +0,0 @@ -require 'puppet/util/loadedfile' -require 'puppet/server/rights' - -module Puppet -class Server - -class ConfigurationError < Puppet::Error; end - -class AuthConfig < Puppet::Util::LoadedFile - Puppet.config.setdefaults(:puppet, - :authconfig => [ "$confdir/namespaceauth.conf", - "The configuration file that defines the rights to the different - namespaces and methods. This can be used as a coarse-grained - authorization system for both ``puppetd`` and ``puppetmasterd``." - ] - ) - - # Just proxy the setting methods to our rights stuff - [:allow, :deny].each do |method| - define_method(method) do |*args| - @rights.send(method, *args) - end - end - - # Here we add a little bit of semantics. They can set auth on a whole namespace - # or on just a single method in the namespace. - def allowed?(name, host, ip) - namespace, method = name.to_s.split(".") - unless namespace and method - raise ArgumentError, "Invalid method name %s" % name - end - - name = name.intern if name.is_a? String - namespace = namespace.intern - method = method.intern - - read() - - if @rights.include?(name) - return @rights[name].allowed?(host, ip) - elsif @rights.include?(namespace) - return @rights[namespace].allowed?(host, ip) - else - return false - end - end - - # Does the file exist? Puppetmasterd does not require it, but - # puppetd does. - def exists? - FileTest.exists?(@file) - end - - def initialize(file = nil, parsenow = true) - @file ||= Puppet[:authconfig] - - unless @file - raise Puppet::DevError, "No authconfig file defined" - end - return unless self.exists? - super(@file) - @rights = Rights.new - @configstamp = @configstatted = nil - @configtimeout = 60 - - if parsenow - read() - end - end - - # Read the configuration file. - def read - return unless FileTest.exists?(@file) - - if @configstamp - if @configtimeout and @configstatted - if Time.now - @configstatted > @configtimeout - @configstatted = Time.now - tmp = File.stat(@file).ctime - - if tmp == @configstamp - return - else - Puppet.notice "%s vs %s" % [tmp, @configstamp] - end - else - return - end - else - Puppet.notice "%s and %s" % [@configtimeout, @configstatted] - end - end - - parse() - - @configstamp = File.stat(@file).ctime - @configstatted = Time.now - end - - private - - def parse - newrights = Puppet::Server::Rights.new - begin - File.open(@file) { |f| - right = nil - count = 1 - f.each { |line| - case line - when /^\s*#/: next # skip comments - when /^\s*$/: next # skip blank lines - when /\[([\w.]+)\]/: # "namespace" or "namespace.method" - name = $1 - if newrights.include?(name) - raise FileServerError, "%s is already set at %s" % - [newrights[name], name] - end - newrights.newright(name) - right = newrights[name] - when /^\s*(\w+)\s+(.+)$/: - var = $1 - value = $2 - case var - when "allow": - value.split(/\s*,\s*/).each { |val| - begin - right.info "allowing %s access" % val - right.allow(val) - rescue AuthStoreError => detail - raise ConfigurationError, "%s at line %s of %s" % - [detail.to_s, count, @config] - end - } - when "deny": - value.split(/\s*,\s*/).each { |val| - begin - right.info "denying %s access" % val - right.deny(val) - rescue AuthStoreError => detail - raise ConfigurationError, "%s at line %s of %s" % - [detail.to_s, count, @config] - end - } - else - raise ConfigurationError, - "Invalid argument '%s' at line %s" % [var, count] - end - else - raise ConfigurationError, "Invalid line %s: %s" % [count, line] - end - count += 1 - } - } - rescue Errno::EACCES => detail - Puppet.err "Configuration error: Cannot read %s; cannot serve" % @file - #raise Puppet::Error, "Cannot read %s" % @config - rescue Errno::ENOENT => detail - Puppet.err "Configuration error: '%s' does not exit; cannot serve" % - @file - #raise Puppet::Error, "%s does not exit" % @config - #rescue FileServerError => detail - # Puppet.err "FileServer error: %s" % detail - end - - # Verify each of the rights are valid. - # We let the check raise an error, so that it can raise an error - # pointing to the specific problem. - newrights.each { |name, right| - right.valid? - } - @rights = newrights - end -end -end -end - -# $Id$ diff --git a/lib/puppet/server/authstore.rb b/lib/puppet/server/authstore.rb deleted file mode 100755 index b0f63b68a..000000000 --- a/lib/puppet/server/authstore.rb +++ /dev/null @@ -1,229 +0,0 @@ -# standard module for determining whether a given hostname or IP has access to -# the requested resource - -require 'ipaddr' - -module Puppet -class Server - class AuthStoreError < Puppet::Error; end - class AuthorizationError < Puppet::Error; end - - class AuthStore - # This has to be an array, not a hash, else it loses its ordering. - ORDER = [ - [:ip, [:ip]], - [:name, [:hostname, :domain]] - ] - - Puppet::Util.logmethods(self, true) - - def allow(pattern) - # a simple way to allow anyone at all to connect - if pattern == "*" - @globalallow = true - else - store(pattern, @allow) - end - end - - def allowed?(name, ip) - if name or ip - # This is probably unnecessary, and can cause some weirdnesses in - # cases where we're operating over localhost but don't have a real - # IP defined. - unless name and ip - raise Puppet::DevError, "Name and IP must be passed to 'allowed?'" - end - # else, we're networked and such - else - # we're local - return true - end - - # yay insecure overrides - if @globalallow - return true - end - - value = nil - ORDER.each { |nametype, array| - if nametype == :ip - value = IPAddr.new(ip) - else - value = name.split(".").reverse - end - - - array.each { |type| - [[@deny, false], [@allow, true]].each { |ary| - hash, retval = ary - if hash.include?(type) - hash[type].each { |pattern| - if match?(nametype, value, pattern) - return retval - end - } - end - } - } - } - - self.info "defaulting to no access for %s" % name - # default to false - return false - end - - def deny(pattern) - store(pattern, @deny) - end - - def initialize - @globalallow = nil - @allow = Hash.new { |hash, key| - hash[key] = [] - } - @deny = Hash.new { |hash, key| - hash[key] = [] - } - end - - private - - def match?(nametype, value, pattern) - if value == pattern # simplest shortcut - return true - end - - case nametype - when :ip: matchip?(value, pattern) - when :name: matchname?(value, pattern) - else - raise Puppet::DevError, "Invalid match type %s" % nametype - end - end - - def matchip?(value, pattern) - # we're just using builtin stuff for this, thankfully - if pattern.include?(value) - return true - else - return false - end - end - - def matchname?(value, pattern) - # yay, horribly inefficient - if pattern[-1] != '*' # the pattern has no metachars and is not equal - # thus, no match - #Puppet.info "%s is not equal with no * in %s" % [value, pattern] - return false - else - # we know the last field of the pattern is '*' - # if everything up to that doesn't match, we're definitely false - if pattern[0..-2] != value[0..pattern.length-2] - #Puppet.notice "subpatterns didn't match; %s vs %s" % - # [pattern[0..-2], value[0..pattern.length-2]] - return false - end - - case value.length <=> pattern.length - when -1: # value is shorter than pattern - if pattern.length - value.length == 1 - # only ever allowed when the value is the domain of a - # splatted pattern - #Puppet.info "allowing splatted domain %s" % [value] - return true - else - return false - end - when 0: # value is the same length as pattern - if pattern[-1] == "*" - #Puppet.notice "same length with *" - return true - else - return false - end - when 1: # value is longer than pattern - # at this point we've already verified that everything up to - # the '*' in the pattern matches, so we are true - return true - end - end - end - - def store(pattern, hash) - type, value = type(pattern) - - if type and value - # this won't work once we get beyond simple stuff... - hash[type] << value - else - raise AuthStoreError, "Invalid pattern %s" % pattern - end - end - - def type(pattern) - type = value = nil - case pattern - when /^(\d+\.){3}\d+$/: - type = :ip - begin - value = IPAddr.new(pattern) - rescue ArgumentError => detail - raise AuthStoreError, "Invalid IP address pattern %s" % pattern - end - when /^(\d+\.){3}\d+\/(\d+)$/: - mask = Integer($2) - if mask < 1 or mask > 32 - raise AuthStoreError, "Invalid IP mask %s" % mask - end - type = :ip - begin - value = IPAddr.new(pattern) - rescue ArgumentError => detail - raise AuthStoreError, "Invalid IP address pattern %s" % pattern - end - when /^(\d+\.){1,3}\*$/: # an ip address with a '*' at the end - type = :ip - match = $1 - match.sub!(".", '') - ary = pattern.split(".") - - mask = case ary.index(match) - when 0: 8 - when 1: 16 - when 2: 24 - else - raise AuthStoreError, "Invalid IP pattern %s" % pattern - end - - ary.pop - while ary.length < 4 - ary.push("0") - end - - begin - value = IPAddr.new(ary.join(".") + "/" + mask.to_s) - rescue ArgumentError => detail - raise AuthStoreError, "Invalid IP address pattern %s" % pattern - end - when /^[\d.]+$/: # necessary so incomplete IP addresses can't look - # like hostnames - raise AuthStoreError, "Invalid IP address pattern %s" % pattern - when /^([a-zA-Z][-\w]*\.)+[-\w]+$/: # a full hostname - type = :hostname - value = pattern.split(".").reverse - when /^\*(\.([a-zA-Z][-\w]*)){1,}$/: - type = :domain - value = pattern.split(".").reverse - else - raise AuthStoreError, "Invalid pattern %s" % pattern - end - - return [type, value] - end - end -end -end -# -# $Id$ diff --git a/lib/puppet/server/ca.rb b/lib/puppet/server/ca.rb deleted file mode 100644 index 10fafc940..000000000 --- a/lib/puppet/server/ca.rb +++ /dev/null @@ -1,155 +0,0 @@ -require 'openssl' -require 'puppet' -require 'puppet/sslcertificates' -require 'xmlrpc/server' - -# Much of this was taken from QuickCert: -# http://segment7.net/projects/ruby/QuickCert/ - -module Puppet -class Server - class CAError < Puppet::Error; end - class CA < Handler - attr_reader :ca - - @interface = XMLRPC::Service::Interface.new("puppetca") { |iface| - iface.add_method("array getcert(csr)") - } - - def autosign - if defined? @autosign - @autosign - else - Puppet[:autosign] - end - end - - # FIXME autosign? should probably accept both hostnames and IP addresses - def autosign?(hostname) - # simple values are easy - if autosign == true or autosign == false - return autosign - end - - # we only otherwise know how to handle files - unless autosign =~ /^\// - raise Puppet::Error, "Invalid autosign value %s" % - autosign.inspect - end - - unless FileTest.exists?(autosign) - unless defined? @@warnedonautosign - @@warnedonautosign = true - Puppet.info "Autosign is enabled but %s is missing" % autosign - end - return false - end - auth = Puppet::Server::AuthStore.new - File.open(autosign) { |f| - f.each { |line| - next if line =~ /^\s*#/ - next if line =~ /^\s*$/ - auth.allow(line.chomp) - } - } - - # for now, just cheat and pass a fake IP address to allowed? - return auth.allowed?(hostname, "127.1.1.1") - end - - def initialize(hash = {}) - Puppet.config.use(:puppet, :certificates, :ca) - if hash.include? :autosign - @autosign = hash[:autosign] - end - - @ca = Puppet::SSLCertificates::CA.new(hash) - end - - # our client sends us a csr, and we either store it for later signing, - # or we sign it right away - def getcert(csrtext, client = nil, clientip = nil) - csr = OpenSSL::X509::Request.new(csrtext) - - # Use the hostname from the CSR, not from the network. - subject = csr.subject - - nameary = subject.to_a.find { |ary| - ary[0] == "CN" - } - - if nameary.nil? - Puppet.err( - "Invalid certificate request: could not retrieve server name" - ) - return "invalid" - end - - hostname = nameary[1] - - unless @ca - Puppet.notice "Host %s asked for signing from non-CA master" % hostname - return "" - end - - # We used to save the public key, but it's basically unnecessary - # and it mucks with the permissions requirements. - # save_pk(hostname, csr.public_key) - - certfile = File.join(Puppet[:certdir], [hostname, "pem"].join(".")) - - # first check to see if we already have a signed cert for the host - cert, cacert = ca.getclientcert(hostname) - if cert and cacert - Puppet.info "Retrieving existing certificate for %s" % hostname - #Puppet.info "Cert: %s; Cacert: %s" % [cert.class, cacert.class] - return [cert.to_pem, cacert.to_pem] - elsif @ca - if self.autosign?(hostname) or client.nil? - if client.nil? - Puppet.info "Signing certificate for CA server" - end - # okay, we don't have a signed cert - # if we're a CA and autosign is turned on, then go ahead and sign - # the csr and return the results - Puppet.info "Signing certificate for %s" % hostname - cert, cacert = @ca.sign(csr) - #Puppet.info "Cert: %s; Cacert: %s" % [cert.class, cacert.class] - return [cert.to_pem, cacert.to_pem] - else # just write out the csr for later signing - if @ca.getclientcsr(hostname) - Puppet.info "Not replacing existing request from %s" % hostname - else - Puppet.notice "Host %s has a waiting certificate request" % - hostname - @ca.storeclientcsr(csr) - end - return ["", ""] - end - else - raise "huh?" - end - end - - private - - # Save the public key. - def save_pk(hostname, public_key) - pkeyfile = File.join(Puppet[:publickeydir], [hostname, "pem"].join('.')) - - if FileTest.exists?(pkeyfile) - currentkey = File.open(pkeyfile) { |k| k.read } - unless currentkey == public_key.to_s - raise Puppet::Error, "public keys for %s differ" % hostname - end - else - File.open(pkeyfile, "w", 0644) { |f| - f.print public_key.to_s - } - end - end - end -end -end - -# $Id$ diff --git a/lib/puppet/server/filebucket.rb b/lib/puppet/server/filebucket.rb deleted file mode 100755 index 56d994366..000000000 --- a/lib/puppet/server/filebucket.rb +++ /dev/null @@ -1,169 +0,0 @@ -#-------------------- -# accept and serve files - - -require 'webrick' -require 'xmlrpc/server' -require 'xmlrpc/client' -require 'facter' -require 'digest/md5' -require 'puppet/external/base64' - -module Puppet -class Server - class BucketError < RuntimeError; end - class FileBucket < Handler - Puppet.config.setdefaults("puppetmasterd", - :bucketdir => { - :default => "$vardir/bucket", - :mode => 0750, - :owner => "$user", - :group => "$group", - :desc => "Where FileBucket files are stored." - } - ) - - Puppet.config.setdefaults("filebucket", - :clientbucketdir => { - :default => "$vardir/clientbucket", - :mode => 0750, - :desc => "Where FileBucket files are stored locally." - } - ) - @interface = XMLRPC::Service::Interface.new("puppetbucket") { |iface| - iface.add_method("string addfile(string, string)") - iface.add_method("string getfile(string)") - } - - Puppet::Util.logmethods(self, true) - attr_reader :name, :path - - # this doesn't work for relative paths - def FileBucket.paths(base,md5) - return [ - File.join(base, md5), - File.join(base, md5, "contents"), - File.join(base, md5, "paths") - ] - end - - def initialize(hash) - if hash.include?(:ConflictCheck) - @conflictchk = hash[:ConflictCheck] - hash.delete(:ConflictCheck) - else - @conflictchk = true - end - - if hash.include?(:Path) - @path = hash[:Path] - hash.delete(:Path) - else - if defined? Puppet - @path = Puppet[:bucketdir] - else - @path = File.expand_path("~/.filebucket") - end - end - - Puppet.config.use(:filebucket) - - @name = "Filebucket[#{@path}]" - end - - # accept a file from a client - def addfile(contents, path, client = nil, clientip = nil) - if client - contents = Base64.decode64(contents) - end - md5 = Digest::MD5.hexdigest(contents) - - bpath, bfile, pathpath = FileBucket.paths(@path,md5) - - # if it's a new directory... - if Puppet.recmkdir(bpath) - msg = "Adding %s(%s)" % [path, md5] - msg += " from #{client}" if client - self.info msg - # ...then just create the file - File.open(bfile, File::WRONLY|File::CREAT, 0440) { |of| - of.print contents - } - else # if the dir already existed... - # ...we need to verify that the contents match the existing file - if @conflictchk - unless FileTest.exists?(bfile) - raise(BucketError, - "No file at %s for sum %s" % [bfile,md5], caller) - end - - curfile = File.read(bfile) - - # If the contents don't match, then we've found a conflict. - # Unlikely, but quite bad. - if curfile != contents - raise(BucketError, - "Got passed new contents for sum %s" % md5, caller) - else - msg = "Got duplicate %s(%s)" % [path, md5] - msg += " from #{client}" if client - self.info msg - end - end - end - - contents = "" - - # in either case, add the passed path to the list of paths - paths = nil - addpath = false - if FileTest.exists?(pathpath) - File.open(pathpath) { |of| - paths = of.readlines.collect { |l| l.chomp } - } - - # unless our path is already there... - unless paths.include?(path) - addpath = true - end - else - addpath = true - end - - # if it's a new file, or if our path isn't in the file yet, add it - if addpath - File.open(pathpath, File::WRONLY|File::CREAT|File::APPEND) { |of| - of.puts path - } - end - - return md5 - end - - def getfile(md5, client = nil, clientip = nil) - bpath, bfile, bpaths = FileBucket.paths(@path,md5) - - unless FileTest.exists?(bfile) - return false - end - - contents = nil - File.open(bfile) { |of| - contents = of.read - } - - if client - return Base64.encode64(contents) - else - return contents - end - end - - def to_s - self.name - end - end -end -end -# -# $Id$ diff --git a/lib/puppet/server/fileserver.rb b/lib/puppet/server/fileserver.rb deleted file mode 100755 index 3ea44d785..000000000 --- a/lib/puppet/server/fileserver.rb +++ /dev/null @@ -1,591 +0,0 @@ -require 'puppet' -require 'webrick/httpstatus' -require 'cgi' -require 'delegate' - -module Puppet -class FileServerError < Puppet::Error; end -class Server - class FileServer < Handler - attr_accessor :local - - Puppet.setdefaults("fileserver", - :fileserverconfig => ["$confdir/fileserver.conf", - "Where the fileserver configuration is stored."]) - - CHECKPARAMS = [:mode, :type, :owner, :group, :checksum] - - @interface = XMLRPC::Service::Interface.new("fileserver") { |iface| - iface.add_method("string describe(string, string)") - iface.add_method("string list(string, string, boolean, array)") - iface.add_method("string retrieve(string, string)") - } - - # Describe a given file. This returns all of the manageable aspects - # of that file. - def describe(url, links = :ignore, client = nil, clientip = nil) - links = links.intern if links.is_a? String - - if links == :manage - raise Puppet::FileServerError, "Cannot currently copy links" - end - - mount, path = convert(url, client, clientip) - - if client - mount.debug "Describing %s for %s" % [url, client] - end - - obj = nil - unless obj = mount.check(path, links) - return "" - end - - desc = [] - CHECKPARAMS.each { |check| - if property = obj.property(check) - unless property.is - mount.debug "Manually retrieving info for %s" % check - property.retrieve - end - desc << property.is - else - if check == "checksum" and obj.property(:type).is == "file" - mount.notice "File %s does not have data for %s" % - [obj.name, check] - end - desc << nil - end - } - - return desc.join("\t") - end - - # Create a new fileserving module. - def initialize(hash = {}) - @mounts = {} - @files = {} - - if hash[:Local] - @local = hash[:Local] - else - @local = false - end - - if hash[:Config] == false - @noreadconfig = true - else - @config = Puppet::Util::LoadedFile.new( - hash[:Config] || Puppet[:fileserverconfig] - ) - @noreadconfig = false - end - - if hash.include?(:Mount) - @passedconfig = true - unless hash[:Mount].is_a?(Hash) - raise Puppet::DevError, "Invalid mount hash %s" % - hash[:Mount].inspect - end - - hash[:Mount].each { |dir, name| - if FileTest.exists?(dir) - self.mount(dir, name) - end - } - else - @passedconfig = false - readconfig(false) # don't check the file the first time. - end - end - - # List a specific directory's contents. - def list(url, links = :ignore, recurse = false, ignore = false, client = nil, clientip = nil) - mount, path = convert(url, client, clientip) - - if client - mount.debug "Listing %s for %s" % [url, client] - end - - obj = nil - unless FileTest.exists?(path) - return "" - end - - # We pass two paths here, but reclist internally changes one - # of the arguments when called internally. - desc = reclist(mount, path, path, recurse, ignore) - - if desc.length == 0 - mount.notice "Got no information on //%s/%s" % - [mount, path] - return "" - end - - desc.collect { |sub| - sub.join("\t") - }.join("\n") - end - - def local? - self.local - end - - # Mount a new directory with a name. - def mount(path, name) - if @mounts.include?(name) - if @mounts[name] != path - raise FileServerError, "%s is already mounted at %s" % - [@mounts[name].path, name] - else - # it's already mounted; no problem - return - end - end - - # Let the mounts do their own error-checking. - @mounts[name] = Mount.new(name, path) - @mounts[name].info "Mounted %s" % path - - return @mounts[name] - end - - # Retrieve a file from the local disk and pass it to the remote - # client. - def retrieve(url, links = :ignore, client = nil, clientip = nil) - links = links.intern if links.is_a? String - - mount, path = convert(url, client, clientip) - - if client - mount.info "Sending %s to %s" % [url, client] - end - - unless FileTest.exists?(path) - return "" - end - - links = links.intern if links.is_a? String - - if links == :ignore and FileTest.symlink?(path) - return "" - end - - str = nil - if links == :manage - raise Puppet::Error, "Cannot copy links yet." - else - str = File.read(path) - end - - if @local - return str - else - return CGI.escape(str) - end - end - - def umount(name) - @mounts.delete(name) if @mounts.include? name - end - - private - - def authcheck(file, mount, client, clientip) - # If we're local, don't bother passing in information. - if local? - client = nil - clientip = nil - end - unless mount.allowed?(client, clientip) - mount.warning "%s cannot access %s" % - [client, file] - raise Puppet::Server::AuthorizationError, "Cannot access %s" % mount - end - end - - def convert(url, client, clientip) - readconfig - - url = URI.unescape(url) - - mount, stub = splitpath(url, client) - - authcheck(url, mount, client, clientip) - - path = nil - unless path = mount.subdir(stub, client) - mount.notice "Could not find subdirectory %s" % - "//%s/%s" % [mount, stub] - return "" - end - - return mount, path - end - - # Deal with ignore parameters. - def handleignore(children, path, ignore) - ignore.each { |ignore| - Dir.glob(File.join(path,ignore), File::FNM_DOTMATCH) { |match| - children.delete(File.basename(match)) - } - } - return children - end - - # Read the configuration file. - def readconfig(check = true) - return if @noreadconfig - - if check and ! @config.changed? - return - end - - newmounts = {} - begin - File.open(@config.file) { |f| - mount = nil - count = 1 - f.each { |line| - case line - when /^\s*#/: next # skip comments - when /^\s*$/: next # skip blank lines - when /\[(\w+)\]/: - name = $1 - if newmounts.include?(name) - raise FileServerError, "%s is already mounted at %s" % - [newmounts[name], name], count, @config.file - end - mount = Mount.new(name) - newmounts[name] = mount - when /^\s*(\w+)\s+(.+)$/: - var = $1 - value = $2 - case var - when "path": - begin - mount.path = value - rescue FileServerError => detail - Puppet.err "Removing mount %s: %s" % - [mount.name, detail] - newmounts.delete(mount.name) - end - when "allow": - value.split(/\s*,\s*/).each { |val| - begin - mount.info "allowing %s access" % val - mount.allow(val) - rescue AuthStoreError => detail - raise FileServerError.new(detail.to_s, - count, @config.file) - end - } - when "deny": - value.split(/\s*,\s*/).each { |val| - begin - mount.info "denying %s access" % val - mount.deny(val) - rescue AuthStoreError => detail - raise FileServerError.new(detail.to_s, - count, @config.file) - end - } - else - raise FileServerError.new("Invalid argument '%s'" % var, - count, @config.file) - end - else - raise FileServerError.new("Invalid line '%s'" % line.chomp, - count, @config.file) - end - count += 1 - } - } - rescue Errno::EACCES => detail - Puppet.err "FileServer error: Cannot read %s; cannot serve" % @config - #raise Puppet::Error, "Cannot read %s" % @config - rescue Errno::ENOENT => detail - Puppet.err "FileServer error: '%s' does not exist; cannot serve" % - @config - #raise Puppet::Error, "%s does not exit" % @config - #rescue FileServerError => detail - # Puppet.err "FileServer error: %s" % detail - end - - # Verify each of the mounts are valid. - # We let the check raise an error, so that it can raise an error - # pointing to the specific problem. - newmounts.each { |name, mount| - unless mount.valid? - raise FileServerError, "No path specified for mount %s" % - name - end - } - @mounts = newmounts - end - - # Recursively list the directory. FIXME This should be using - # puppet objects, not directly listing. - def reclist(mount, root, path, recurse, ignore) - # Take out the root of the path. - name = path.sub(root, '') - if name == "" - name = "/" - end - - if name == path - raise FileServerError, "Could not match %s in %s" % - [root, path] - end - - desc = [name] - ftype = File.stat(path).ftype - - desc << ftype - if recurse.is_a?(Integer) - recurse -= 1 - end - - ary = [desc] - if recurse == true or (recurse.is_a?(Integer) and recurse > -1) - if ftype == "directory" - children = Dir.entries(path) - if ignore - children = handleignore(children, path, ignore) - end - children.each { |child| - next if child =~ /^\.\.?$/ - reclist(mount, root, File.join(path, child), recurse, ignore).each { |cobj| - ary << cobj - } - } - end - end - - return ary.reject { |c| c.nil? } - end - - # Split the path into the separate mount point and path. - def splitpath(dir, client) - # the dir is based on one of the mounts - # so first retrieve the mount path - mount = nil - path = nil - if dir =~ %r{/(\w+)/?} - tmp = $1 - path = dir.sub(%r{/#{tmp}/?}, '') - - unless mount = @mounts[tmp] - raise FileServerError, "Fileserver module '%s' not mounted" % tmp - end - else - raise FileServerError, "Fileserver error: Invalid path '%s'" % dir - end - - if path == "" - path = nil - else - # Remove any double slashes that might have occurred - path = URI.unescape(path.gsub(/\/\//, "/")) - end - - return mount, path - end - - def to_s - "fileserver" - end - - # A simple class for wrapping mount points. Instances of this class - # don't know about the enclosing object; they're mainly just used for - # authorization. - class Mount < AuthStore - attr_reader :name - - Puppet::Util.logmethods(self, true) - - # Run 'retrieve' on a file. This gets the actual parameters, so - # we can pass them to the client. - def check(dir, links) - unless FileTest.exists?(dir) - self.notice "File source %s does not exist" % dir - return nil - end - - obj = fileobj(dir, links) - - # FIXME we should really have a timeout here -- we don't - # want to actually check on every connection, maybe no more - # than every 60 seconds or something. It'd be nice if we - # could use the builtin scheduling to do this. - - # Retrieval is enough here, because we don't want to cache - # any information in the state file, and we don't want to generate - # any state changes or anything. We don't even need to sync - # the checksum, because we're always going to hit the disk - # directly. - obj.retrieve - - return obj - end - - # Create a map for a specific client. - def clientmap(client) - { - "h" => client.sub(/\..*$/, ""), - "H" => client, - "d" => client.sub(/[^.]+\./, "") # domain name - } - end - - # Replace % patterns as appropriate. - def expand(path, client = nil) - # This map should probably be moved into a method. - map = nil - - if client - map = clientmap(client) - else - Puppet.notice "No client; expanding '%s' with local host" % - path - # Else, use the local information - map = localmap() - end - path.gsub(/%(.)/) do |v| - key = $1 - if key == "%" - "%" - else - map[key] || v - end - end - end - - # Do we have any patterns in our path, yo? - def expandable? - if defined? @expandable - @expandable - else - false - end - end - - # Create out object. It must have a name. - def initialize(name, path = nil) - unless name =~ %r{^\w+$} - raise FileServerError, "Invalid name format '%s'" % name - end - @name = name - - if path - self.path = path - else - @path = nil - end - - super() - end - - def fileobj(path, links) - obj = nil - if obj = Puppet.type(:file)[path] - # This can only happen in local fileserving, but it's an - # important one. It'd be nice if we didn't just set - # the check params every time, but I'm not sure it's worth - # the effort. - obj[:check] = CHECKPARAMS - else - obj = Puppet.type(:file).create( - :name => path, - :check => CHECKPARAMS - ) - end - - if links == :manage - links = :follow - end - - # This, ah, might be completely redundant - unless obj[:links] == links - obj[:links] = links - end - - return obj - end - - # Cache this manufactured map, since if it's used it's likely - # to get used a lot. - def localmap - unless defined? @@localmap - @@localmap = { - "h" => Facter.value("hostname"), - "H" => [Facter.value("hostname"), - Facter.value("domain")].join("."), - "d" => Facter.value("domain") - } - end - @@localmap - end - - # Return the path as appropriate, expanding as necessary. - def path(client = nil) - if expandable? - return expand(@path, client) - else - return @path - end - end - - # Set the path. - def path=(path) - # FIXME: For now, just don't validate paths with replacement - # patterns in them. - if path =~ /%./ - # Mark that we're expandable. - @expandable = true - else - unless FileTest.exists?(path) - raise FileServerError, "%s does not exist" % path - end - unless FileTest.directory?(path) - raise FileServerError, "%s is not a directory" % path - end - unless FileTest.readable?(path) - raise FileServerError, "%s is not readable" % path - end - @expandable = false - end - @path = path - end - - # Retrieve a specific directory relative to a mount point. - # If they pass in a client, then expand as necessary. - def subdir(dir = nil, client = nil) - basedir = self.path(client) - - dirname = if dir - File.join(basedir, dir.split("/").join(File::SEPARATOR)) - else - basedir - end - - dirname - end - - def to_s - "mount[#{@name}]" - end - - # Verify our configuration is valid. This should really check to - # make sure at least someone will be allowed, but, eh. - def valid? - return false unless @path - - return true - end - end - end -end -end - -# $Id$ diff --git a/lib/puppet/server/logger.rb b/lib/puppet/server/logger.rb deleted file mode 100755 index aa3521573..000000000 --- a/lib/puppet/server/logger.rb +++ /dev/null @@ -1,54 +0,0 @@ -require 'yaml' - -module Puppet -class Server - class LoggerError < RuntimeError; end - - # Receive logs from remote hosts. - class Logger < Handler - @interface = XMLRPC::Service::Interface.new("puppetlogger") { |iface| - iface.add_method("void addlog(string)") - } - - # accept a log message from a client, and route it accordingly - def addlog(message, client = nil, clientip = nil) - unless message - raise Puppet::DevError, "Did not receive message" - end - - Puppet.info message.inspect - # if the client is set, then we're not local - if client - begin - message = YAML.load(CGI.unescape(message)) - #message = message - rescue => detail - raise XMLRPC::FaultException.new( - 1, "Could not unYAML log message from %s" % client - ) - end - end - - unless message - raise Puppet::DevError, "Could not resurrect message" - end - - # Mark it as remote, so it's not sent to syslog - message.remote = true - - if client - if ! message.source or message.source == "Puppet" - message.source = client - end - end - - Puppet::Util::Log.newmessage(message) - - # This is necessary or XMLRPC gets all pukey - return "" - end - end -end -end - -# $Id$ diff --git a/lib/puppet/server/master.rb b/lib/puppet/server/master.rb deleted file mode 100644 index cda6027d0..000000000 --- a/lib/puppet/server/master.rb +++ /dev/null @@ -1,214 +0,0 @@ -require 'openssl' -require 'puppet' -require 'puppet/parser/interpreter' -require 'puppet/sslcertificates' -require 'xmlrpc/server' -require 'yaml' - -module Puppet -class Server - class MasterError < Puppet::Error; end - class Master < Handler - include Puppet::Util - - attr_accessor :ast, :local - attr_reader :ca - - @interface = XMLRPC::Service::Interface.new("puppetmaster") { |iface| - iface.add_method("string getconfig(string)") - iface.add_method("int freshness()") - } - - # FIXME At some point, this should be autodocumenting. - def addfacts(facts) - # Add our server version to the fact list - facts["serverversion"] = Puppet.version.to_s - - # And then add the server name and IP - {"servername" => "hostname", - "serverip" => "ipaddress" - }.each do |var, fact| - if obj = Facter[fact] - facts[var] = obj.value - else - Puppet.warning "Could not retrieve fact %s" % fact - end - end - end - - # Manipulate the client name as appropriate. - def clientname(name, ip, facts) - # Always use the hostname from Facter. - client = facts["hostname"] - clientip = facts["ipaddress"] - if Puppet[:node_name] == 'cert' - if name - client = name - end - if ip - clientip = ip - end - end - - return client, clientip - end - - # Tell a client whether there's a fresh config for it - def freshness(client = nil, clientip = nil) - if Puppet.features.rails? and Puppet[:storeconfigs] - host = Puppet::Rails::Host.find_or_create_by_name(client) - host.last_freshcheck = Time.now - if clientip and (! host.ip or host.ip == "") - host.ip = clientip - end - host.save - end - if defined? @interpreter - return @interpreter.parsedate - else - return 0 - end - end - - def initialize(hash = {}) - args = {} - - # Allow specification of a code snippet or of a file - if code = hash[:Code] - args[:Code] = code - else - args[:Manifest] = hash[:Manifest] || Puppet[:manifest] - end - - if hash[:Local] - @local = hash[:Local] - else - @local = false - end - - args[:Local] = @local - - if hash.include?(:CA) and hash[:CA] - @ca = Puppet::SSLCertificates::CA.new() - else - @ca = nil - end - - Puppet.debug("Creating interpreter") - - if hash.include?(:UseNodes) - args[:UseNodes] = hash[:UseNodes] - elsif @local - args[:UseNodes] = false - end - - # This is only used by the cfengine module, or if --loadclasses was - # specified in +puppet+. - if hash.include?(:Classes) - args[:Classes] = hash[:Classes] - end - - @interpreter = Puppet::Parser::Interpreter.new(args) - end - - def getconfig(facts, format = "marshal", client = nil, clientip = nil) - if @local - # we don't need to do anything, since we should already - # have raw objects - Puppet.debug "Our client is local" - else - Puppet.debug "Our client is remote" - - # XXX this should definitely be done in the protocol, somehow - case format - when "marshal": - Puppet.warning "You should upgrade your client. 'Marshal' will not be supported much longer." - begin - facts = Marshal::load(CGI.unescape(facts)) - rescue => detail - raise XMLRPC::FaultException.new( - 1, "Could not rebuild facts" - ) - end - when "yaml": - begin - facts = YAML.load(CGI.unescape(facts)) - rescue => detail - raise XMLRPC::FaultException.new( - 1, "Could not rebuild facts" - ) - end - else - raise XMLRPC::FaultException.new( - 1, "Unavailable config format %s" % format - ) - end - end - - client, clientip = clientname(client, clientip, facts) - - # Add any server-side facts to our server. - addfacts(facts) - - retobjects = nil - - # This is hackish, but there's no "silence" option for benchmarks - # right now - if @local - #begin - retobjects = @interpreter.run(client, facts) - #rescue Puppet::Error => detail - # Puppet.err detail - # raise XMLRPC::FaultException.new( - # 1, detail.to_s - # ) - #rescue => detail - # Puppet.err detail.to_s - # return "" - #end - else - benchmark(:notice, "Compiled configuration for %s" % client) do - begin - retobjects = @interpreter.run(client, facts) - rescue Puppet::Error => detail - Puppet.err detail - raise XMLRPC::FaultException.new( - 1, detail.to_s - ) - rescue => detail - Puppet.err detail.to_s - return "" - end - end - end - - if @local - return retobjects - else - str = nil - case format - when "marshal": - str = Marshal::dump(retobjects) - when "yaml": - str = YAML.dump(retobjects) - else - raise XMLRPC::FaultException.new( - 1, "Unavailable config format %s" % format - ) - end - return CGI.escape(str) - end - end - - def local? - if defined? @local and @local - return true - else - return false - end - end - end -end -end - -# $Id$ diff --git a/lib/puppet/server/report.rb b/lib/puppet/server/report.rb deleted file mode 100755 index 4298f8ee6..000000000 --- a/lib/puppet/server/report.rb +++ /dev/null @@ -1,176 +0,0 @@ -module Puppet -class Server - # A simple server for triggering a new run on a Puppet client. - class Report < Handler - class << self - include Puppet::Util::ClassGen - end - - module ReportBase - include Puppet::Util::Docs - attr_writer :useyaml - - def useyaml? - if defined? @useyaml - @useyaml - else - false - end - end - end - - @interface = XMLRPC::Service::Interface.new("puppetreports") { |iface| - iface.add_method("string report(array)") - } - - Puppet.setdefaults(:reporting, - :reports => ["store", - "The list of reports to generate. All reports are looked for - in puppet/reports/.rb, and multiple report names should be - comma-separated (whitespace is okay)." - ], - :reportdir => {:default => "$vardir/reports", - :mode => 0750, - :owner => "$user", - :group => "$group", - :desc => "The directory in which to store reports - received from the client. Each client gets a separate - subdirectory."} - ) - - @reports = {} - @reportloader = Puppet::Util::Autoload.new(self, "puppet/reports") - - class << self - attr_reader :hooks - end - - # Add a new report type. - def self.newreport(name, options = {}, &block) - name = symbolize(name) - - mod = genmodule(name, :extend => ReportBase, :hash => @reports, :block => block) - - if options[:useyaml] - mod.useyaml = true - end - - mod.send(:define_method, :report_name) do - name - end - end - - # Load a report. - def self.report(name) - name = name.intern if name.is_a? String - unless @reports.include? name - if @reportloader.load(name) - unless @reports.include? name - Puppet.warning( - "Loaded report file for %s but report was not defined" % - name - ) - return nil - end - else - return nil - end - end - @reports[symbolize(name)] - end - - def self.reportdocs - docs = "" - - # Use this method so they all get loaded - reports.sort { |a,b| a.to_s <=> b.to_s }.each do |name| - mod = self.report(name) - docs += "## %s\n\n" % name - - docs += Puppet::Util::Docs.scrub(mod.doc) + "\n\n" - end - - docs - end - - def self.reports - @reportloader.loadall - @reports.keys - end - - def initialize(*args) - super - Puppet.config.use(:reporting) - Puppet.config.use(:metrics) - end - - # Accept a report from a client. - def report(report, client = nil, clientip = nil) - # Unescape the report - unless @local - report = CGI.unescape(report) - end - - begin - process(report) - rescue => detail - Puppet.err "Could not process report %s: %s" % [$1, detail] - if Puppet[:trace] - puts detail.backtrace - end - end - end - - private - - # Process the report using all of the existing hooks. - def process(yaml) - return if Puppet[:reports] == "none" - - # First convert the report to real objects - begin - report = YAML.load(yaml) - rescue => detail - Puppet.warning "Could not load report: %s" % detail - return - end - - # Used for those reports that accept yaml - client = report.host - - reports().each do |name| - if mod = self.class.report(name) - Puppet.info "Processing report %s for %s" % [name, client] - - # We have to use a dup because we're including a module in the - # report. - newrep = report.dup - begin - newrep.extend(mod) - if mod.useyaml? - newrep.process(yaml) - else - newrep.process - end - rescue => detail - if Puppet[:trace] - puts detail.backtrace - end - Puppet.err "Report %s failed: %s" % - [name, detail] - end - else - Puppet.warning "No report named '%s'" % name - end - end - end - - # Handle the parsing of the reports attribute. - def reports - Puppet[:reports].gsub(/(^\s+)|(\s+$)/, '').split(/\s*,\s*/) - end - end -end -end - -# $Id$ diff --git a/lib/puppet/server/resource.rb b/lib/puppet/server/resource.rb deleted file mode 100755 index d2bad52f3..000000000 --- a/lib/puppet/server/resource.rb +++ /dev/null @@ -1,191 +0,0 @@ -require 'puppet' -require 'puppet/server' - -module Puppet - -# Serve Puppet elements. Useful for querying, copying, and, um, other stuff. -class Server::Resource < Server::Handler - attr_accessor :local - - @interface = XMLRPC::Service::Interface.new("resource") { |iface| - iface.add_method("string apply(string, string)") - iface.add_method("string describe(string, string, array, array)") - iface.add_method("string list(string, array, string)") - } - - # Apply a TransBucket as a transaction. - def apply(bucket, format = "yaml", client = nil, clientip = nil) - unless @local - begin - case format - when "yaml": - bucket = YAML::load(Base64.decode64(bucket)) - else - raise Puppet::Error, "Unsupported format '%s'" % format - end - rescue => detail - raise Puppet::Error, "Could not load YAML TransBucket: %s" % detail - end - end - - component = bucket.to_type - - # Create a client, but specify the remote machine as the server - # because the class requires it, even though it's unused - client = Puppet::Client::MasterClient.new(:Server => client||"localhost") - - # Set the objects - client.objects = component - - # And then apply the configuration. This way we're reusing all - # the code in there. It should probably just be separated out, though. - transaction = client.apply - - # And then clean up - component.remove - - # It'd be nice to return some kind of report, but... at this point - # we have no such facility. - return "success" - end - - # Describe a given object. This returns the 'is' values for every property - # available on the object type. - def describe(type, name, retrieve = nil, ignore = [], format = "yaml", client = nil, clientip = nil) - Puppet.info "Describing %s[%s]" % [type.to_s.capitalize, name] - @local = true unless client - typeklass = nil - unless typeklass = Puppet.type(type) - raise Puppet::Error, "Puppet type %s is unsupported" % type - end - - obj = nil - - retrieve ||= :all - ignore ||= [] - - if obj = typeklass[name] - obj[:check] = retrieve - else - begin - obj = typeklass.create(:name => name, :check => retrieve) - rescue Puppet::Error => detail - raise Puppet::Error, "%s[%s] could not be created: %s" % - [type, name, detail] - end - end - - unless obj - raise XMLRPC::FaultException.new( - 1, "Could not create %s[%s]" % [type, name] - ) - end - - trans = obj.to_trans - - # Now get rid of any attributes they specifically don't want - ignore.each do |st| - if trans.include? st - trans.delete(st) - end - end - - # And get rid of any attributes that are nil - trans.each do |attr, value| - if value.nil? - trans.delete(attr) - end - end - - unless @local - case format - when "yaml": - trans = Base64.encode64(YAML::dump(trans)) - else - raise XMLRPC::FaultException.new( - 1, "Unavailable config format %s" % format - ) - end - end - - return trans - end - - # Create a new fileserving module. - def initialize(hash = {}) - if hash[:Local] - @local = hash[:Local] - else - @local = false - end - end - - # List all of the elements of a given type. - def list(type, ignore = [], base = nil, format = "yaml", client = nil, clientip = nil) - @local = true unless client - typeklass = nil - unless typeklass = Puppet.type(type) - raise Puppet::Error, "Puppet type %s is unsupported" % type - end - - # They can pass in false - ignore ||= [] - ignore = [ignore] unless ignore.is_a? Array - bucket = TransBucket.new - bucket.type = typeklass.name - - typeklass.list.each do |obj| - next if ignore.include? obj.name - - object = TransObject.new(obj.name, typeklass.name) - bucket << object - end - - unless @local - case format - when "yaml": - begin - bucket = Base64.encode64(YAML::dump(bucket)) - rescue => detail - Puppet.err detail - raise XMLRPC::FaultException.new( - 1, detail.to_s - ) - end - else - raise XMLRPC::FaultException.new( - 1, "Unavailable config format %s" % format - ) - end - end - - return bucket - end - - private - - def authcheck(file, mount, client, clientip) - unless mount.allowed?(client, clientip) - mount.warning "%s cannot access %s" % - [client, file] - raise Puppet::Server::AuthorizationError, "Cannot access %s" % mount - end - end - - # Deal with ignore parameters. - def handleignore(children, path, ignore) - ignore.each { |ignore| - Dir.glob(File.join(path,ignore), File::FNM_DOTMATCH) { |match| - children.delete(File.basename(match)) - } - } - return children - end - - def to_s - "resource" - end -end -end - -# $Id$ diff --git a/lib/puppet/server/rights.rb b/lib/puppet/server/rights.rb deleted file mode 100755 index 0ed12a122..000000000 --- a/lib/puppet/server/rights.rb +++ /dev/null @@ -1,78 +0,0 @@ -require 'ipaddr' -require 'puppet/server/authstore' - -module Puppet -class Server - # Define a set of rights and who has access to them. - class Rights < Hash - # We basically just proxy directly to our rights. Each Right stores - # its own auth abilities. - [:allow, :allowed?, :deny].each do |method| - define_method(method) do |name, *args| - name = name.intern if name.is_a? String - - if obj = right(name) - obj.send(method, *args) - else - raise ArgumentError, "Unknown right '%s'" % name - end - end - end - - def [](name) - name = name.intern if name.is_a? String - super(name) - end - - # Define a new right to which access can be provided. - def newright(name) - name = name.intern if name.is_a? String - shortname = Right.shortname(name) - if self.include? name - raise ArgumentError, "Right '%s' is already defined" % name - else - self[name] = Right.new(name, shortname) - end - end - - private - - # Retrieve a right by name. - def right(name) - name = name.intern if name.is_a? String - self[name] - end - - # A right. - class Right < AuthStore - attr_accessor :name, :shortname - - Puppet::Util.logmethods(self, true) - - def self.shortname(name) - name.to_s[0..0] - end - - def initialize(name, shortname = nil) - @name = name - @shortname = shortname - unless @shortname - @shortname = Right.shortname(name) - end - super() - end - - def to_s - "access[%s]" % @name - end - - # There's no real check to do at this point - def valid? - true - end - end - end -end -end -# -# $Id$ diff --git a/lib/puppet/server/runner.rb b/lib/puppet/server/runner.rb deleted file mode 100755 index 46fd7a7ae..000000000 --- a/lib/puppet/server/runner.rb +++ /dev/null @@ -1,64 +0,0 @@ -module Puppet -class Server - class MissingMasterError < RuntimeError # Cannot find the master client - end - # A simple server for triggering a new run on a Puppet client. - class Runner < Handler - @interface = XMLRPC::Service::Interface.new("puppetrunner") { |iface| - iface.add_method("string run(string, string)") - } - - # Run the client configuration right now, optionally specifying - # tags and whether to ignore schedules - def run(tags = nil, ignoreschedules = false, fg = true, client = nil, clientip = nil) - # We need to retrieve the client - master = Puppet::Client::MasterClient.instance - - unless master - raise MissingMasterError, "Could not find the master client" - end - - if Puppet::Util::Pidlock.new(Puppet[:puppetdlockfile]).locked? - Puppet.notice "Could not trigger run; already running" - return "running" - end - - if tags == "" - tags = nil - end - - if ignoreschedules == "" - ignoreschedules == nil - end - - msg = "" - if client - msg = "%s(%s) " % [client, clientip] - end - msg += "triggered run" % - if tags - msg += " with tags %s" % tags - end - - if ignoreschedules - msg += " without schedules" - end - - Puppet.notice msg - - # And then we need to tell it to run, with this extra info. - if fg - master.run(tags, ignoreschedules) - else - Puppet.newthread do - master.run(tags, ignoreschedules) - end - end - - return "success" - end - end -end -end - -# $Id$ diff --git a/lib/puppet/server/servlet.rb b/lib/puppet/server/servlet.rb deleted file mode 100644 index 81219ef44..000000000 --- a/lib/puppet/server/servlet.rb +++ /dev/null @@ -1,277 +0,0 @@ -require 'xmlrpc/server' - -module Puppet -class Server - class ServletError < RuntimeError; end - class Servlet < XMLRPC::WEBrickServlet - ERR_UNAUTHORIZED = 30 - - attr_accessor :request - - # this is just a duplicate of the normal method; it's here for - # debugging when i need it - def self.get_instance(server, *options) - self.new(server, *options) - end - - # This is a hackish way to avoid an auth message every time we have a - # normal operation - def self.log(msg) - unless defined? @logs - @logs = {} - end - if @logs.include?(msg) - @logs[msg] += 1 - else - Puppet.info msg - @logs[msg] = 1 - end - end - - def add_handler(interface, handler) - @loadedhandlers << interface.prefix - super - end - - # Verify that our client has access. We allow untrusted access to - # puppetca methods but no others. - def authorize(request, method) - namespace = method.sub(/\..+/, '') - client = request.peeraddr[2] - if defined? @client and @client - client = @client - end - ip = request.peeraddr[3] - if request.client_cert - if @puppetserver.authconfig.exists? - allowed = @puppetserver.authconfig.allowed?(method, client, ip) - - if allowed - Puppet.debug "Allowing %s(%s) trusted access to %s" % - [client, ip, method] - return true - else - Puppet.debug "Denying %s(%s) trusted access to %s" % - [client, ip, method] - return false - end - else - # This is pretty hackish, but... - # This means we can't actually test this method at this point. - # The next release of Puppet will almost definitely require - # this file to exist or will default to denying all access. - if Puppet.name == "puppetmasterd" or defined? Test::Unit::TestCase - Puppet.debug "Allowing %s(%s) trusted access to %s" % - [client, ip, method] - return true - else - Puppet.debug "Denying %s(%s) trusted access to %s on %s" % - [client, ip, method, Puppet.name] - return false - end - end - else - if method =~ /^puppetca\./ - Puppet.notice "Allowing %s(%s) untrusted access to CA methods" % - [client, ip] - else - Puppet.err "Unauthenticated client %s(%s) cannot call %s" % - [client, ip, method] - return false - end - end - end - - def available?(method) - namespace = method.sub(/\..+/, '') - client = request.peeraddr[2] - ip = request.peeraddr[3] - if @loadedhandlers.include?(namespace) - return true - else - Puppet.warning "Client %s(%s) requested unavailable functionality %s" % - [client, ip, namespace] - return false - end - end - - def initialize(server, handlers) - @puppetserver = server - @notified = {} - # the servlet base class does not consume any arguments - # and its BasicServer base class only accepts a 'class_delim' - # option which won't change in Puppet at all - # thus, we don't need to pass any args to our base class, - # and we can consume them all ourselves - super() - - @loadedhandlers = [] - handlers.each { |handler| - #Puppet.debug "adding handler for %s" % handler.class - self.add_handler(handler.class.interface, handler) - } - - # Initialize these to nil, but they will get set to values - # by the 'service' method. These have to instance variables - # because I don't have a clear line from the service method to - # the service hook. - @request = nil - @client = nil - @clientip = nil - - self.set_service_hook { |obj, *args| - if @client and @clientip - args.push(@client, @clientip) - end - begin - obj.call(*args) - rescue XMLRPC::FaultException - raise - rescue Puppet::Server::AuthorizationError => detail - #Puppet.warning obj.inspect - #Puppet.warning args.inspect - Puppet.err "Permission denied: %s" % detail.to_s - raise XMLRPC::FaultException.new( - 1, detail.to_s - ) - rescue Puppet::Error => detail - #Puppet.warning obj.inspect - #Puppet.warning args.inspect - if Puppet[:trace] - puts detail.backtrace - end - Puppet.err detail.to_s - error = XMLRPC::FaultException.new( - 1, detail.to_s - ) - error.set_backtrace detail.backtrace - raise error - rescue => detail - #Puppet.warning obj.inspect - #Puppet.warning args.inspect - if Puppet[:trace] - puts detail.backtrace - end - Puppet.err "Could not call: %s" % detail.to_s - error = XMLRPC::FaultException.new(1, detail.to_s) - error.set_backtrace detail.backtrace - raise error - end - } - end - - # Handle the actual request. This does some basic collection of - # data, and then just calls the parent method. - def service(request, response) - @request = request - - # The only way that @client can be nil is if the request is local. - if peer = request.peeraddr - @client = peer[2] - @clientip = peer[3] - else - raise XMLRPC::FaultException.new( - ERR_UNCAUGHT_EXCEPTION, - "Could not retrieve client information" - ) - end - - # If they have a certificate (which will almost always be true) - # then we get the hostname from the cert, instead of via IP - # info - if cert = request.client_cert - nameary = cert.subject.to_a.find { |ary| - ary[0] == "CN" - } - - if nameary.nil? - Puppet.warning "Could not retrieve server name from cert" - else - unless @client == nameary[1] - Puppet.debug "Overriding %s with cert name %s" % - [@client, nameary[1]] - @client = nameary[1] - end - end - end - begin - super - rescue => detail - Puppet.err "Could not service request: %s: %s" % - [detail.class, detail] - end - @client = nil - @clientip = nil - @request = nil - end - - private - - # this is pretty much just a copy of the original method but with more - # feedback - # here's where we have our authorization hooks - def dispatch(methodname, *args) - - if defined? @request and @request - unless self.available?(methodname) - raise XMLRPC::FaultException.new( - ERR_UNAUTHORIZED, - "Functionality %s not available" % - methodname.sub(/\..+/, '') - ) - end - unless self.authorize(@request, methodname) - raise XMLRPC::FaultException.new( - ERR_UNAUTHORIZED, - "Host %s not authorized to call %s" % - [@request.host, methodname] - ) - end - else - raise Puppet::DevError, "Did not get request in dispatch" - end - - #Puppet.warning "dispatch on %s called with %s" % - # [methodname, args.inspect] - for name, obj in @handler - if obj.kind_of? Proc - unless methodname == name - #Puppet.debug "obj is proc but %s != %s" % - # [methodname, name] - next - end - else - unless methodname =~ /^#{name}(.+)$/ - #Puppet.debug "methodname did not match" - next - end - unless obj.respond_to? $1 - #Puppet.debug "methodname does not respond to %s" % $1 - next - end - obj = obj.method($1) - end - - if check_arity(obj, args.size) - if @service_hook.nil? - return obj.call(*args) - else - return @service_hook.call(obj, *args) - end - else - Puppet.debug "arity is incorrect" - end - end - - if @default_handler.nil? - raise XMLRPC::FaultException.new( - ERR_METHOD_MISSING, - "Method #{methodname} missing or wrong number of parameters!" - ) - else - @default_handler.call(methodname, *args) - end - end - end -end -end diff --git a/lib/puppet/type/pfile.rb b/lib/puppet/type/pfile.rb index 89e94d140..ad21c5c55 100644 --- a/lib/puppet/type/pfile.rb +++ b/lib/puppet/type/pfile.rb @@ -4,7 +4,7 @@ require 'etc' require 'uri' require 'fileutils' require 'puppet/type/property' -require 'puppet/server/fileserver' +require 'puppet/network/server/fileserver' module Puppet newtype(:file) do @@ -101,7 +101,7 @@ module Puppet @parent.bucket = value value end - when Puppet::Client::Dipper: value.name + when Puppet::Network::Client::Dipper: value.name else self.fail "Invalid backup type %s" % value.inspect @@ -300,7 +300,7 @@ module Puppet # This sets the @value on :backup, too self.bucket = obj elsif bucket == "puppet" - obj = Puppet::Client::Dipper.new( + obj = Puppet::Network::Client::Dipper.new( :Path => Puppet[:clientbucketdir] ) self.bucket = obj @@ -311,7 +311,7 @@ module Puppet else self.fail "Could not find filebucket %s" % bucket end - when Puppet::Client::Dipper: # things are hunky-dorey + when Puppet::Network::Client::Dipper: # things are hunky-dorey else self.fail "Invalid bucket type %s" % bucket.class end @@ -346,7 +346,7 @@ module Puppet else backup = self.bucket || self[:backup] case backup - when Puppet::Client::Dipper: + when Puppet::Network::Client::Dipper: notice "Recursively backing up to filebucket" require 'find' Find.find(self[:path]) do |f| @@ -385,7 +385,7 @@ module Puppet when "file": backup = self.bucket || self[:backup] case backup - when Puppet::Client::Dipper: + when Puppet::Network::Client::Dipper: sum = backup.backup(file) self.info "Filebucketed to %s with sum %s" % [backup.name, sum] @@ -957,7 +957,7 @@ module Puppet case uri.scheme when "file": unless defined? @@localfileserver - @@localfileserver = Puppet::Server::FileServer.new( + @@localfileserver = Puppet::Network::Server::FileServer.new( :Local => true, :Mount => { "/" => "localhost" }, :Config => false @@ -972,9 +972,9 @@ module Puppet args[:Port] = uri.port end # FIXME We should cache a copy of this server - #sourceobj.server = Puppet::NetworkClient.new(args) + #sourceobj.server = Puppet::Network::NetworkClient.new(args) unless @clients.include?(source) - @clients[source] = Puppet::Client::FileClient.new(args) + @clients[source] = Puppet::Network::Client::FileClient.new(args) end sourceobj.server = @clients[source] diff --git a/lib/puppet/type/pfile/source.rb b/lib/puppet/type/pfile/source.rb index d06366a79..8416107a2 100755 --- a/lib/puppet/type/pfile/source.rb +++ b/lib/puppet/type/pfile/source.rb @@ -1,4 +1,4 @@ -require 'puppet/server/fileserver' +require 'puppet/network/server/fileserver' module Puppet # Copy files from a local or remote source. This state *only* does any work @@ -7,7 +7,7 @@ module Puppet # this state, during retrieval, modifies the appropriate other states # so that things get taken care of appropriately. Puppet.type(:file).newproperty(:source) do - PINPARAMS = Puppet::Server::FileServer::CHECKPARAMS + PINPARAMS = Puppet::Network::Server::FileServer::CHECKPARAMS attr_accessor :source, :local desc "Copy a file over the current file. Uses ``checksum`` to @@ -86,7 +86,7 @@ module Puppet begin desc = server.describe(path, @parent[:links]) - rescue NetworkClientError => detail + rescue Puppet::Network::NetworkClientError => detail self.err "Could not describe %s: %s" % [path, detail] return nil @@ -231,7 +231,7 @@ module Puppet begin contents = sourceobj.server.retrieve(path, @parent[:links]) - rescue NetworkClientError => detail + rescue Puppet::Network::NetworkClientError => detail self.err "Could not retrieve %s: %s" % [path, detail] return nil diff --git a/lib/puppet/type/pfilebucket.rb b/lib/puppet/type/pfilebucket.rb index 6f9da3112..5ec7e790f 100755 --- a/lib/puppet/type/pfilebucket.rb +++ b/lib/puppet/type/pfilebucket.rb @@ -1,4 +1,4 @@ -require 'puppet/server/filebucket' +require 'puppet/network/server/filebucket' module Puppet newtype(:filebucket) do @@ -86,7 +86,7 @@ module Puppet def mkbucket if self[:server] begin - @bucket = Puppet::Client::Dipper.new( + @bucket = Puppet::Network::Client::Dipper.new( :Server => self[:server], :Port => self[:port] ) @@ -97,7 +97,7 @@ module Puppet end else begin - @bucket = Puppet::Client::Dipper.new( + @bucket = Puppet::Network::Client::Dipper.new( :Path => self[:path] ) rescue => detail diff --git a/lib/puppet/type/tidy.rb b/lib/puppet/type/tidy.rb index bc622f682..2827c1be3 100755 --- a/lib/puppet/type/tidy.rb +++ b/lib/puppet/type/tidy.rb @@ -270,7 +270,7 @@ module Puppet end # only allow backing up into filebuckets - unless self[:backup].is_a? Puppet::Client::Dipper + unless self[:backup].is_a? Puppet::Network::Client::Dipper self[:backup] = false end end diff --git a/lib/puppet/util/log.rb b/lib/puppet/util/log.rb index dd7544dae..5ee60b959 100644 --- a/lib/puppet/util/log.rb +++ b/lib/puppet/util/log.rb @@ -306,7 +306,7 @@ class Puppet::Util::Log @name = host - @driver = Puppet::Client::LogClient.new(args) + @driver = Puppet::Network::Client::LogClient.new(args) end def handle(msg) -- cgit