summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/puppet.rb219
-rw-r--r--lib/puppet/agent.rb134
-rw-r--r--lib/puppet/agent/locker.rb42
-rw-r--r--lib/puppet/agent/runner.rb65
-rw-r--r--lib/puppet/config_stores/rest.rb60
-rw-r--r--lib/puppet/configurer.rb166
-rw-r--r--lib/puppet/configurer/downloader.rb79
-rw-r--r--lib/puppet/configurer/fact_handler.rb55
-rw-r--r--lib/puppet/configurer/plugin_handler.rb25
-rwxr-xr-xlib/puppet/daemon.rb102
-rw-r--r--lib/puppet/defaults.rb205
-rw-r--r--lib/puppet/executables/client/certhandler.rb77
-rw-r--r--lib/puppet/file_serving/base.rb (renamed from lib/puppet/file_serving/file_base.rb)16
-rw-r--r--lib/puppet/file_serving/configuration.rb21
-rw-r--r--lib/puppet/file_serving/content.rb37
-rw-r--r--lib/puppet/file_serving/fileset.rb2
-rw-r--r--lib/puppet/file_serving/indirection_hooks.rb45
-rw-r--r--lib/puppet/file_serving/metadata.rb6
-rw-r--r--lib/puppet/file_serving/mount.rb24
-rw-r--r--lib/puppet/file_serving/terminus_helper.rb14
-rw-r--r--lib/puppet/indirector.rb2
-rw-r--r--lib/puppet/indirector/catalog/compiler.rb40
-rw-r--r--lib/puppet/indirector/catalog/rest.rb6
-rw-r--r--lib/puppet/indirector/catalog/yaml.rb6
-rw-r--r--lib/puppet/indirector/certificate/ca.rb9
-rw-r--r--lib/puppet/indirector/certificate/file.rb9
-rw-r--r--lib/puppet/indirector/certificate/rest.rb6
-rw-r--r--lib/puppet/indirector/certificate_request/ca.rb14
-rw-r--r--lib/puppet/indirector/certificate_request/file.rb8
-rw-r--r--lib/puppet/indirector/certificate_request/rest.rb6
-rw-r--r--lib/puppet/indirector/certificate_revocation_list/ca.rb8
-rw-r--r--lib/puppet/indirector/certificate_revocation_list/file.rb8
-rw-r--r--lib/puppet/indirector/certificate_revocation_list/rest.rb6
-rw-r--r--lib/puppet/indirector/direct_file_server.rb10
-rw-r--r--lib/puppet/indirector/facts/facter.rb48
-rw-r--r--lib/puppet/indirector/facts/rest.rb6
-rw-r--r--lib/puppet/indirector/file.rb1
-rw-r--r--lib/puppet/indirector/file_metadata/file.rb4
-rw-r--r--lib/puppet/indirector/file_metadata/modules.rb2
-rw-r--r--lib/puppet/indirector/file_server.rb3
-rw-r--r--lib/puppet/indirector/indirection.rb48
-rw-r--r--lib/puppet/indirector/key/ca.rb20
-rw-r--r--lib/puppet/indirector/key/file.rb42
-rw-r--r--lib/puppet/indirector/module_files.rb9
-rw-r--r--lib/puppet/indirector/report/rest.rb5
-rw-r--r--lib/puppet/indirector/request.rb79
-rw-r--r--lib/puppet/indirector/rest.rb105
-rw-r--r--lib/puppet/indirector/runner/rest.rb7
-rw-r--r--lib/puppet/indirector/ssl_file.rb172
-rw-r--r--lib/puppet/indirector/ssl_rsa.rb5
-rw-r--r--lib/puppet/indirector/ssl_rsa/file.rb33
-rw-r--r--lib/puppet/module.rb9
-rw-r--r--lib/puppet/network/client.rb2
-rw-r--r--lib/puppet/network/client/ca.rb2
-rw-r--r--lib/puppet/network/client/master.rb524
-rw-r--r--lib/puppet/network/format.rb79
-rw-r--r--lib/puppet/network/format_handler.rb119
-rw-r--r--lib/puppet/network/formats.rb77
-rwxr-xr-xlib/puppet/network/handler/fileserver.rb11
-rw-r--r--lib/puppet/network/handler/master.rb2
-rwxr-xr-xlib/puppet/network/handler/resource.rb18
-rwxr-xr-xlib/puppet/network/handler/runner.rb51
-rw-r--r--lib/puppet/network/http.rb2
-rw-r--r--lib/puppet/network/http/handler.rb128
-rw-r--r--lib/puppet/network/http/mongrel.rb11
-rw-r--r--lib/puppet/network/http/mongrel/rest.rb34
-rw-r--r--lib/puppet/network/http/webrick.rb83
-rw-r--r--lib/puppet/network/http/webrick/rest.rb21
-rw-r--r--lib/puppet/network/http_pool.rb49
-rw-r--r--lib/puppet/network/http_server/mongrel.rb6
-rw-r--r--lib/puppet/network/http_server/webrick.rb2
-rw-r--r--lib/puppet/network/server.rb140
-rw-r--r--lib/puppet/node.rb14
-rwxr-xr-xlib/puppet/node/facts.rb21
-rw-r--r--lib/puppet/parameter.rb481
-rw-r--r--lib/puppet/parser/ast/resource_defaults.rb2
-rw-r--r--lib/puppet/parser/collector.rb2
-rw-r--r--lib/puppet/parser/compiler.rb23
-rw-r--r--lib/puppet/parser/functions.rb2
-rw-r--r--lib/puppet/parser/functions/versioncmp.rb10
-rw-r--r--lib/puppet/parser/interpreter.rb6
-rw-r--r--lib/puppet/parser/resource.rb80
-rw-r--r--lib/puppet/parser/resource/reference.rb4
-rw-r--r--lib/puppet/parser/scope.rb6
-rw-r--r--lib/puppet/pgraph.rb121
-rw-r--r--lib/puppet/property.rb166
-rw-r--r--lib/puppet/provider.rb2
-rw-r--r--lib/puppet/provider/augeas/augeas.rb51
-rwxr-xr-xlib/puppet/provider/mailalias/aliases.rb8
-rw-r--r--lib/puppet/provider/package/rug.rb2
-rw-r--r--lib/puppet/provider/package/up2date.rb2
-rw-r--r--lib/puppet/provider/ssh_authorized_key/parsed.rb64
-rw-r--r--lib/puppet/reports/store.rb4
-rw-r--r--lib/puppet/resource.rb200
-rw-r--r--lib/puppet/resource/catalog.rb (renamed from lib/puppet/node/catalog.rb)200
-rw-r--r--lib/puppet/resource/reference.rb (renamed from lib/puppet/resource_reference.rb)64
-rw-r--r--lib/puppet/simple_graph.rb148
-rw-r--r--lib/puppet/ssl.rb7
-rw-r--r--lib/puppet/ssl/base.rb62
-rw-r--r--lib/puppet/ssl/certificate.rb34
-rw-r--r--lib/puppet/ssl/certificate_authority.rb289
-rw-r--r--lib/puppet/ssl/certificate_authority/interface.rb110
-rw-r--r--lib/puppet/ssl/certificate_factory.rb145
-rw-r--r--lib/puppet/ssl/certificate_request.rb51
-rw-r--r--lib/puppet/ssl/certificate_revocation_list.rb86
-rw-r--r--lib/puppet/ssl/host.rb258
-rw-r--r--lib/puppet/ssl/inventory.rb52
-rw-r--r--lib/puppet/ssl/key.rb56
-rw-r--r--lib/puppet/transaction.rb99
-rw-r--r--lib/puppet/transaction/report.rb8
-rw-r--r--lib/puppet/transportable.rb50
-rw-r--r--lib/puppet/type.rb758
-rw-r--r--lib/puppet/type/component.rb123
-rw-r--r--lib/puppet/type/file.rb650
-rwxr-xr-xlib/puppet/type/file/checksum.rb6
-rwxr-xr-xlib/puppet/type/file/content.rb58
-rwxr-xr-xlib/puppet/type/file/ensure.rb9
-rwxr-xr-xlib/puppet/type/file/group.rb2
-rwxr-xr-xlib/puppet/type/file/mode.rb2
-rwxr-xr-xlib/puppet/type/file/owner.rb2
-rw-r--r--lib/puppet/type/file/selcontext.rb8
-rwxr-xr-xlib/puppet/type/file/source.rb237
-rw-r--r--lib/puppet/type/file/target.rb2
-rwxr-xr-xlib/puppet/type/file/type.rb2
-rwxr-xr-xlib/puppet/type/filebucket.rb14
-rwxr-xr-xlib/puppet/type/maillist.rb2
-rw-r--r--lib/puppet/type/package.rb24
-rwxr-xr-xlib/puppet/type/schedule.rb20
-rwxr-xr-xlib/puppet/type/tidy.rb577
-rwxr-xr-xlib/puppet/type/user.rb7
-rw-r--r--lib/puppet/type/yumrepo.rb5
-rw-r--r--lib/puppet/type/zone.rb1
-rw-r--r--lib/puppet/util/cacher.rb104
-rw-r--r--lib/puppet/util/checksums.rb9
-rwxr-xr-xlib/puppet/util/filetype.rb6
-rw-r--r--lib/puppet/util/graph.rb4
-rw-r--r--lib/puppet/util/log.rb7
-rw-r--r--lib/puppet/util/package.rb2
-rwxr-xr-xlib/puppet/util/posix.rb39
-rw-r--r--lib/puppet/util/rdoc.rb1
-rw-r--r--lib/puppet/util/resource_template.rb2
-rw-r--r--lib/puppet/util/settings.rb435
142 files changed, 5384 insertions, 4215 deletions
diff --git a/lib/puppet.rb b/lib/puppet.rb
index 4b0091cd0..2cbf7fb29 100644
--- a/lib/puppet.rb
+++ b/lib/puppet.rb
@@ -8,7 +8,6 @@ end
require 'singleton'
require 'facter'
require 'puppet/error'
-require 'puppet/external/event-loop'
require 'puppet/util'
require 'puppet/util/log'
require 'puppet/util/autoload'
@@ -32,14 +31,7 @@ module Puppet
end
class << self
- # So we can monitor signals and such.
- include SignalObserver
-
include Puppet::Util
-
- # To keep a copy of arguments. Set within Config#addargs, because I'm
- # lazy.
- attr_accessor :args
attr_reader :features
attr_writer :name
end
@@ -117,7 +109,6 @@ module Puppet
# Load all of the configuration parameters.
require 'puppet/defaults'
-
def self.genmanifest
if Puppet[:genmanifest]
puts Puppet.settings.to_manifest
@@ -125,218 +116,14 @@ module Puppet
end
end
- # Run all threads to their ends
- def self.join
- defined? @threads and @threads.each do |t| t.join end
- end
-
- # Create a new service that we're supposed to run
- def self.newservice(service)
- @services ||= []
-
- @services << service
- end
-
- def self.newthread(&block)
- @threads ||= []
-
- @threads << Thread.new do
- yield
- end
- end
-
- def self.newtimer(hash, &block)
- timer = nil
- threadlock(:timers) do
- @timers ||= []
- timer = EventLoop::Timer.new(hash)
- @timers << timer
-
- if block_given?
- observe_signal(timer, :alarm, &block)
- end
- end
-
- # In case they need it for something else.
- timer
- end
-
# Parse the config file for this process.
- def self.parse_config(oldconfig = nil)
- # First look for the old configuration file.
- oldconfig ||= File.join(Puppet[:confdir], Puppet[:name].to_s + ".conf")
- if FileTest.exists?(oldconfig) and Puppet[:name] != "puppet"
- Puppet.warning "Individual config files are deprecated; remove %s and use puppet.conf" % oldconfig
- Puppet.settings.old_parse(oldconfig)
- return
- end
-
- # Now check for the normal config.
+ def self.parse_config
if Puppet[:config] and File.exists? Puppet[:config]
Puppet.debug "Parsing %s" % Puppet[:config]
Puppet.settings.parse(Puppet[:config])
end
end
- # Relaunch the executable.
- def self.restart
- command = $0 + " " + self.args.join(" ")
- Puppet.notice "Restarting with '%s'" % command
- Puppet.shutdown(false)
- Puppet::Util::Log.reopen
- exec(command)
- end
-
- # Trap a couple of the main signals. This should probably be handled
- # in a way that anyone else can register callbacks for traps, but, eh.
- def self.settraps
- [:INT, :TERM].each do |signal|
- trap(signal) do
- Puppet.notice "Caught #{signal}; shutting down"
- Puppet.debug "Signal caught here:"
- caller.each { |l| Puppet.debug l }
- Puppet.shutdown
- end
- end
-
- # Handle restarting.
- trap(:HUP) do
- if client = @services.find { |s| s.is_a? Puppet::Network::Client.master } and client.running?
- client.restart
- else
- Puppet.restart
- end
- end
-
- # Provide a hook for running clients where appropriate
- trap(:USR1) do
- done = 0
- Puppet.notice "Caught USR1; triggering client run"
- @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
- else
- done += 1
- begin
- client.runnow
- rescue => detail
- Puppet.err "Could not run client: %s" % detail
- end
- end
- else
- Puppet.info "Ignoring %s; cannot test whether it is running" %
- client.class
- end
- end
-
- unless done > 0
- Puppet.notice "No clients were run"
- end
- end
-
- trap(:USR2) do
- Puppet::Util::Log.reopen
- end
- end
-
- # Shutdown our server process, meaning stop all services and all threads.
- # Optionally, exit.
- def self.shutdown(leave = true)
- Puppet.notice "Shutting down"
- # Unmonitor our timers
- defined? @timers and @timers.each do |timer|
- EventLoop.current.ignore_timer timer
- end
-
- # This seems to exit the process, although I can't find where it does
- # so. Leaving it out doesn't seem to hurt anything.
- #if EventLoop.current.running?
- # EventLoop.current.quit
- #end
-
- # Stop our services
- defined? @services and @services.each do |svc|
- next unless svc.respond_to?(:shutdown)
- begin
- timeout(20) do
- svc.shutdown
- end
- rescue TimeoutError
- Puppet.err "%s could not shut down within 20 seconds" % svc.class
- end
- end
-
- # And wait for them all to die, giving a decent amount of time
- defined? @threads and @threads.each do |thr|
- begin
- timeout(20) do
- thr.join
- end
- rescue TimeoutError
- # Just ignore this, since we can't intelligently provide a warning
- end
- end
-
- if leave
- exit(0)
- end
- end
-
- # Start all of our services and optionally our event loop, which blocks,
- # waiting for someone, somewhere, to generate events of some kind.
- def self.start(block = true)
- # Starting everything in its own thread, fwiw
- defined? @services and @services.dup.each do |svc|
- newthread do
- begin
- svc.start
- rescue => detail
- if Puppet[:trace]
- puts detail.backtrace
- end
- @services.delete svc
- Puppet.err "Could not start %s: %s" % [svc.class, detail]
- end
- end
- end
-
- # We need to give the services a chance to register their timers before
- # we try to start monitoring them.
- sleep 0.5
-
- unless @services.length > 0
- Puppet.notice "No remaining services; exiting"
- exit(1)
- end
-
- if defined? @timers and ! @timers.empty?
- @timers.each do |timer|
- EventLoop.current.monitor_timer timer
- end
- end
-
- if block
- EventLoop.current.run
- end
- end
-
- # Create the timer that our different objects (uh, mostly the client)
- # check.
- def self.timer
- unless defined? @timer
- #Puppet.info "Interval is %s" % Puppet[:runinterval]
- #@timer = EventLoop::Timer.new(:interval => Puppet[:runinterval])
- @timer = EventLoop::Timer.new(
- :interval => Puppet[:runinterval],
- :tolerance => 1,
- :start? => true
- )
- EventLoop.current.monitor_timer @timer
- end
- @timer
- end
-
# XXX this should all be done using puppet objects, not using
# normal mkdir
def self.recmkdir(dir,mode = 0755)
@@ -375,15 +162,19 @@ module Puppet
# Retrieve a type by name. Just proxy to the Type class.
def self.type(name)
+ # LAK:DEP Deprecation notice added 12/17/2008
+ Puppet.warning "Puppet.type is deprecated; use Puppet::Type.type"
Puppet::Type.type(name)
end
end
require 'puppet/type'
require 'puppet/network'
+require 'puppet/ssl'
require 'puppet/module'
require 'puppet/util/storage'
require 'puppet/parser/interpreter'
+
if Puppet[:storeconfigs]
require 'puppet/rails'
end
diff --git a/lib/puppet/agent.rb b/lib/puppet/agent.rb
new file mode 100644
index 000000000..3add43ec1
--- /dev/null
+++ b/lib/puppet/agent.rb
@@ -0,0 +1,134 @@
+require 'sync'
+require 'puppet/external/event-loop'
+
+# A general class for triggering a run of another
+# class.
+class Puppet::Agent
+ require 'puppet/agent/locker'
+ include Puppet::Agent::Locker
+
+ require 'puppet/agent/runner'
+
+ attr_reader :client_class, :client, :needing_restart, :splayed
+ attr_accessor :stopping
+
+ def configure_delayed_restart
+ @needing_restart = true
+ end
+
+ # Just so we can specify that we are "the" instance.
+ def initialize(client_class)
+ @splayed = false
+
+ @client_class = client_class
+ end
+
+ def lockfile_path
+ client_class.lockfile_path
+ end
+
+ def needing_restart?
+ @needing_restart
+ end
+
+ def restart
+ configure_delayed_restart and return if running?
+ Process.kill(:HUP, $$)
+ @needing_restart = false
+ end
+
+ # Perform a run with our client.
+ def run
+ if running?
+ Puppet.notice "Run of %s already in progress; skipping" % client_class
+ return
+ end
+ if stopping?
+ Puppet.notice "In shutdown progress; skipping run"
+ return
+ end
+ splay
+ with_client do |client|
+ begin
+ sync.synchronize { lock { client.run } }
+ rescue => detail
+ puts detail.backtrace if Puppet[:trace]
+ Puppet.err "Could not run %s: %s" % [client_class, detail]
+ end
+ end
+ end
+
+ def stop
+ if self.stopping?
+ Puppet.notice "Already in shutdown"
+ return
+ end
+ self.stopping = true
+ if client and client.respond_to?(:stop)
+ begin
+ client.stop
+ rescue
+ puts detail.backtrace if Puppet[:trace]
+ Puppet.err "Could not stop %s: %s" % [client_class, detail]
+ end
+ end
+ ensure
+ self.stopping = false
+ end
+
+ def stopping?
+ stopping
+ end
+
+ # Have we splayed already?
+ def splayed?
+ splayed
+ end
+
+ # Sleep when splay is enabled; else just return.
+ def splay
+ return unless Puppet[:splay]
+ return if splayed?
+
+ time = rand(Integer(Puppet[:splaylimit]) + 1)
+ Puppet.info "Sleeping for %s seconds (splay is enabled)" % time
+ sleep(time)
+ @splayed = true
+ end
+
+ # Start listening for events. We're pretty much just listening for
+ # timer events here.
+ def start
+ # Create our timer. Puppet will handle observing it and such.
+ timer = EventLoop::Timer.new(:interval => Puppet[:runinterval], :tolerance => 1, :start? => true) do
+ run()
+ end
+
+ # Run once before we start following the timer
+ timer.sound_alarm
+ end
+
+ def sync
+ unless defined?(@sync) and @sync
+ @sync = Sync.new
+ end
+ @sync
+ end
+
+ private
+
+ # Create and yield a client instance, keeping a reference
+ # to it during the yield.
+ def with_client
+ begin
+ @client = client_class.new
+ rescue => details
+ puts detail.backtrace if Puppet[:trace]
+ Puppet.err "Could not create instance of %s: %s" % [client_class, details]
+ return
+ end
+ yield @client
+ ensure
+ @client = nil
+ end
+end
diff --git a/lib/puppet/agent/locker.rb b/lib/puppet/agent/locker.rb
new file mode 100644
index 000000000..eaf19177a
--- /dev/null
+++ b/lib/puppet/agent/locker.rb
@@ -0,0 +1,42 @@
+require 'puppet/util/pidlock'
+
+# Break out the code related to locking the agent. This module is just
+# included into the agent, but having it here makes it easier to test.
+module Puppet::Agent::Locker
+ # Let the daemon run again, freely in the filesystem.
+ def enable
+ lockfile.unlock(:anonymous => true)
+ end
+
+ # Stop the daemon from making any catalog runs.
+ def disable
+ lockfile.lock(:anonymous => true)
+ end
+
+ # Yield if we get a lock, else do nothing. Return
+ # true/false depending on whether we get the lock.
+ def lock
+ if lockfile.lock
+ begin
+ yield
+ ensure
+ lockfile.unlock
+ end
+ return true
+ else
+ return false
+ end
+ end
+
+ def lockfile
+ unless defined?(@lockfile)
+ @lockfile = Puppet::Util::Pidlock.new(lockfile_path)
+ end
+
+ @lockfile
+ end
+
+ def running?
+ lockfile.locked?
+ end
+end
diff --git a/lib/puppet/agent/runner.rb b/lib/puppet/agent/runner.rb
new file mode 100644
index 000000000..705b6c269
--- /dev/null
+++ b/lib/puppet/agent/runner.rb
@@ -0,0 +1,65 @@
+require 'puppet/agent'
+require 'puppet/configurer'
+require 'puppet/indirector'
+
+# A basic class for running the agent. Used by
+# puppetrun to kick off agents remotely.
+class Puppet::Agent::Runner
+ extend Puppet::Indirector
+ indirects :runner, :terminus_class => :rest
+
+ attr_reader :status, :background, :options
+
+ def agent
+ Puppet::Agent.new(Puppet::Configurer)
+ end
+
+ def background?
+ background
+ end
+
+ def initialize(options = {})
+ if options.include?(:background)
+ @background = options[:background]
+ options.delete(:background)
+ end
+
+ valid_options = [:tags, :ignoreschedules]
+ options.each do |key, value|
+ raise ArgumentError, "Runner does not accept %s" % key unless valid_options.include?(key)
+ end
+
+ @options = options
+ end
+
+ def log_run
+ msg = ""
+ msg += "triggered run" %
+ if options[:tags]
+ msg += " with tags %s" % options[:tags]
+ end
+
+ if options[:ignoreschedules]
+ msg += " ignoring schedules"
+ end
+
+ Puppet.notice msg
+ end
+
+ def run
+ if agent.running?
+ @status = "running"
+ return
+ end
+
+ log_run()
+
+ if background?
+ Thread.new { agent.run(options) }
+ else
+ agent.run(options)
+ end
+
+ @status = "success"
+ end
+end
diff --git a/lib/puppet/config_stores/rest.rb b/lib/puppet/config_stores/rest.rb
deleted file mode 100644
index bb3d937ac..000000000
--- a/lib/puppet/config_stores/rest.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-Puppet::Util::SettingsStore.newstore(:rest) do
- desc "Store client configurations via a REST web service."
-
- require 'net/http'
-
- # Get a client's config. (called in collector?)
- def get(client, config)
- # Assuming this comes in as Puppet::Parser objects
- # we may need way to choose which transport data type we use.
-
- # hmm.. is this even useful for stored configs? I suppose there could
- # be scenarios where it'd be cool, like ralsh or something.
- end
-
- def initialize
- @host = Puppet[:puppetstorehost]
- @port = Puppet[:puppetstoreport]
-
- # Not sure if this is bad idea to share.
- @http = Net::HTTP.new(@host, @port)
- end
-
- # Store config to the web service. (called in getconfig?)
- def store(client, config)
- # Probably store as yaml...
- puppetstore = Thread.new do
- benchmark(:notice, "Stored configuration for %s" % client) do
- begin
- # config should come from elsewhere; probably in getconfig I assume.
- # should probably allow a config option for the serialization type.
- yaml = YAML.dump(config)
- url = "/collector/create"
- @http.post(url, yaml, { 'Content-Type' => 'text/yaml' })
- rescue => detail
- Puppet.err("ERROR: storeconfig failed: ", detail.to_s)
- end
- end
- end
- puppetstore.run
- end
-
- # Rough first try... assuming the calling method handles the data type conversion
- # Can we use a thread here? Probably needs to be the caller's thread.
- def collect_exported(client, conditions)
- begin
- # Gotta be a better way... seems goofy to me.
- # maybe using a nested rails rest route...
-
- # filterhost so we don't get exported resources for the current client
- url = "/resources?restype=exported&filterhost=#{client}"
- conditions.each_pair {|k,v| url << "&#{k}=#{v}"}
- res = @http.get(url)
- rescue => detail
- Puppet.err("ERROR: collect_exported failed: ", detail.to_s)
- end
-
- return res.body unless !res.is_a?(Net::HTTPOK)
- end
-
-end
diff --git a/lib/puppet/configurer.rb b/lib/puppet/configurer.rb
new file mode 100644
index 000000000..b29082d5a
--- /dev/null
+++ b/lib/puppet/configurer.rb
@@ -0,0 +1,166 @@
+# The client for interacting with the puppetmaster config server.
+require 'sync'
+require 'timeout'
+require 'puppet/network/http_pool'
+require 'puppet/util'
+
+class Puppet::Configurer
+ require 'puppet/configurer/fact_handler'
+ require 'puppet/configurer/plugin_handler'
+
+ include Puppet::Configurer::FactHandler
+ include Puppet::Configurer::PluginHandler
+
+ # For benchmarking
+ include Puppet::Util
+
+ attr_accessor :catalog
+ attr_reader :compile_time
+
+ # Provide more helpful strings to the logging that the Agent does
+ def self.to_s
+ "Puppet configuration client"
+ end
+
+ class << self
+ # Puppetd should only have one instance running, and we need a way
+ # to retrieve it.
+ attr_accessor :instance
+ include Puppet::Util
+ end
+
+ # How to lock instances of this class.
+ def self.lockfile_path
+ Puppet[:puppetdlockfile]
+ end
+
+ def clear
+ @catalog.clear(true) if @catalog
+ @catalog = nil
+ 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
+
+ # Just so we can specify that we are "the" instance.
+ def initialize
+ Puppet.settings.use(:main, :ssl, :puppetd)
+
+ self.class.instance = self
+ @running = false
+ @splayed = false
+ end
+
+ # Prepare for catalog retrieval. Downloads everything necessary, etc.
+ def prepare
+ dostorage()
+
+ download_plugins()
+
+ download_fact_plugins()
+
+ upload_facts()
+ end
+
+ # Get the remote catalog, yo. Returns nil if no catalog can be found.
+ def retrieve_catalog
+ name = Facter.value("hostname")
+ catalog_class = Puppet::Resource::Catalog
+
+ # First try it with no cache, then with the cache.
+ result = nil
+ begin
+ duration = thinmark do
+ result = catalog_class.find(name, :ignore_cache => true)
+ end
+ rescue => detail
+ puts detail.backtrace if Puppet[:trace]
+ Puppet.err "Could not retrieve catalog from remote server: %s" % detail
+ end
+
+ unless result
+ begin
+ duration = thinmark do
+ result = catalog_class.find(name, :ignore_terminus => true)
+ end
+ rescue => detail
+ puts detail.backtrace if Puppet[:trace]
+ Puppet.err "Could not retrieve catalog from cache: %s" % detail
+ end
+ end
+
+ return nil unless result
+
+ convert_catalog(result, duration)
+ end
+
+ # Convert a plain resource catalog into our full host catalog.
+ def convert_catalog(result, duration)
+ catalog = result.to_ral
+ catalog.retrieval_duration = duration
+ catalog.host_config = true
+ catalog.write_class_file
+ return catalog
+ end
+
+ # The code that actually runs the catalog.
+ # This just passes any options on to the catalog,
+ # which accepts :tags and :ignoreschedules.
+ def run(options = {})
+ prepare()
+
+ unless catalog = retrieve_catalog
+ Puppet.err "Could not retrieve catalog; skipping run"
+ return
+ end
+
+ begin
+ benchmark(:notice, "Finished catalog run") do
+ catalog.apply(options)
+ end
+ rescue => detail
+ puts detail.backtrace if Puppet[:trace]
+ Puppet.err "Failed to apply catalog: %s" % detail
+ end
+
+ # Now close all of our existing http connections, since there's no
+ # reason to leave them lying open.
+ Puppet::Network::HttpPool.clear_http_instances
+ end
+
+ private
+
+ 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
+
+ return timeout
+ end
+end
diff --git a/lib/puppet/configurer/downloader.rb b/lib/puppet/configurer/downloader.rb
new file mode 100644
index 000000000..8a2eb0b82
--- /dev/null
+++ b/lib/puppet/configurer/downloader.rb
@@ -0,0 +1,79 @@
+require 'puppet/configurer'
+require 'puppet/resource/catalog'
+
+class Puppet::Configurer::Downloader
+ attr_reader :name, :path, :source, :ignore
+
+ # Determine the timeout value to use.
+ 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
+
+ return timeout
+ end
+
+ # Evaluate our download, returning the list of changed values.
+ def evaluate
+ Puppet.info "Retrieving #{name}"
+
+ files = []
+ begin
+ Timeout.timeout(self.class.timeout) do
+ catalog.apply do |trans|
+ trans.changed?.find_all do |resource|
+ yield resource if block_given?
+ files << resource[:path]
+ end
+ end
+ end
+ rescue Puppet::Error, Timeout::Error => detail
+ puts detail.backtrace if Puppet[:debug]
+ Puppet.err "Could not retrieve #{name}: %s" % detail
+ end
+
+ return files
+ end
+
+ def initialize(name, path, source, ignore = nil)
+ @name, @path, @source, @ignore = name, path, source, ignore
+ end
+
+ def catalog
+ catalog = Puppet::Resource::Catalog.new
+ catalog.add_resource(file)
+ catalog
+ end
+
+ def file
+ args = default_arguments.merge(:path => path, :source => source)
+ args[:ignore] = ignore if ignore
+ Puppet::Type.type(:file).new(args)
+ end
+
+ private
+
+ def default_arguments
+ {
+ :path => path,
+ :recurse => true,
+ :source => source,
+ :tag => name,
+ :owner => Process.uid,
+ :group => Process.gid,
+ :purge => true,
+ :force => true,
+ :backup => false,
+ :noop => false
+ }
+ end
+end
diff --git a/lib/puppet/configurer/fact_handler.rb b/lib/puppet/configurer/fact_handler.rb
new file mode 100644
index 000000000..8a6de5e9f
--- /dev/null
+++ b/lib/puppet/configurer/fact_handler.rb
@@ -0,0 +1,55 @@
+require 'puppet/indirector/facts/facter'
+
+require 'puppet/configurer/downloader'
+
+# Break out the code related to facts. This module is
+# just included into the agent, but having it here makes it
+# easier to test.
+module Puppet::Configurer::FactHandler
+ def download_fact_plugins?
+ Puppet[:factsync]
+ end
+
+ def upload_facts
+ # XXX down = Puppet[:downcasefacts]
+
+ reload_facter()
+
+ # This works because puppetd configures Facts to use 'facter' for
+ # finding facts and the 'rest' terminus for caching them. Thus, we'll
+ # compile them and then "cache" them on the server.
+ begin
+ Puppet::Node::Facts.find(Puppet[:certname])
+ rescue => detail
+ puts detail.backtrace if Puppet[:trace]
+ Puppet.err("Could not retrieve local facts: %s" % detail)
+ end
+ end
+
+ # Retrieve facts from the central server.
+ def download_fact_plugins
+ return unless download_fact_plugins?
+
+ Puppet::Configurer::Downloader.new("fact", Puppet[:factdest], Puppet[:factsource], Puppet[:factsignore]).evaluate
+ end
+
+ # Clear out all of the loaded facts and reload them from disk.
+ # NOTE: This is clumsy and shouldn't be required for later (1.5.x) versions
+ # of Facter.
+ def reload_facter
+ Facter.clear
+
+ # Reload everything.
+ if Facter.respond_to? :loadfacts
+ Facter.loadfacts
+ elsif Facter.respond_to? :load
+ Facter.load
+ else
+ Puppet.warning "You should upgrade your version of Facter to at least 1.3.8"
+ 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.
+ Puppet::Node::Facts::Facter.load_fact_plugins()
+ end
+end
diff --git a/lib/puppet/configurer/plugin_handler.rb b/lib/puppet/configurer/plugin_handler.rb
new file mode 100644
index 000000000..def6a1707
--- /dev/null
+++ b/lib/puppet/configurer/plugin_handler.rb
@@ -0,0 +1,25 @@
+# Break out the code related to plugins. This module is
+# just included into the agent, but having it here makes it
+# easier to test.
+module Puppet::Configurer::PluginHandler
+ def download_plugins?
+ Puppet[:pluginsync]
+ end
+
+ # Retrieve facts from the central server.
+ def download_plugins
+ return nil unless download_plugins?
+ Puppet::Configurer::Downloader.new("plugin", Puppet[:plugindest], Puppet[:pluginsource], Puppet[:pluginsignore]).evaluate.each { |file| load_plugin(file) }
+ end
+
+ def load_plugin(file)
+ return if FileTest.directory?(file)
+
+ begin
+ Puppet.info "Loading downloaded plugin %s" % file
+ load file
+ rescue Exception => detail
+ Puppet.err "Could not load downloaded file %s: %s" % [file, detail]
+ end
+ end
+end
diff --git a/lib/puppet/daemon.rb b/lib/puppet/daemon.rb
index 24d743764..5f811d897 100755
--- a/lib/puppet/daemon.rb
+++ b/lib/puppet/daemon.rb
@@ -1,10 +1,11 @@
require 'puppet'
require 'puppet/util/pidlock'
+require 'puppet/external/event-loop'
# A module that handles operations common to all daemons. This is included
# into the Server and Client base classes.
-module Puppet::Daemon
- include Puppet::Util
+class Puppet::Daemon
+ attr_accessor :agent, :server, :argv
def daemonname
Puppet[:name]
@@ -17,7 +18,7 @@ module Puppet::Daemon
exit(0)
end
- setpidfile()
+ create_pidfile()
# Get rid of console logging
Puppet::Util::Log.close(:console)
@@ -38,18 +39,42 @@ module Puppet::Daemon
end
end
- # The path to the pid file for this server
+ # Create a pidfile for our daemon, so we can be stopped and others
+ # don't try to start.
+ def create_pidfile
+ Puppet::Util.sync(Puppet[:name]).synchronize(Sync::EX) do
+ unless Puppet::Util::Pidlock.new(pidfile).lock
+ raise "Could not create PID file: %s" % [pidfile]
+ end
+ end
+ end
+
+ # Provide the path to our pidfile.
def pidfile
- if Puppet[:pidfile] != ""
- Puppet[:pidfile]
- else
- File.join(Puppet[:rundir], daemonname() + ".pid")
+ Puppet[:pidfile]
+ end
+
+ def reexec
+ raise Puppet::DevError, "Cannot reexec unless ARGV arguments are set" unless argv
+ command = $0 + " " + argv.join(" ")
+ Puppet.notice "Restarting with '%s'" % command
+ stop(:exit => false)
+ exec(command)
+ end
+
+ def reload
+ return unless agent
+ if agent.running?
+ Puppet.notice "Not triggering already-running agent"
+ return
end
+
+ agent.run
end
- # Remove the pid file
- def rmpidfile
- threadlock(:pidfile) do
+ # Remove the pid file for our daemon.
+ def remove_pidfile
+ Puppet::Util.sync(Puppet[:name]).synchronize(Sync::EX) do
locker = Puppet::Util::Pidlock.new(pidfile)
if locker.locked?
locker.unlock or Puppet.err "Could not remove PID file %s" % [pidfile]
@@ -57,27 +82,52 @@ module Puppet::Daemon
end
end
- # Create the pid file.
- def setpidfile
- threadlock(:pidfile) do
- unless Puppet::Util::Pidlock.new(pidfile).lock
- Puppet.err("Could not create PID file: %s" % [pidfile])
- exit(74)
- end
+ def restart
+ if agent and agent.running?
+ agent.configure_delayed_restart
+ else
+ reexec
end
end
- # Shut down our server
- def shutdown
- # Remove our pid file
- rmpidfile()
+ def reopen_logs
+ Puppet::Util::Log.reopen
+ end
- # And close all logs except the console.
- Puppet::Util::Log.destinations.reject { |d| d == :console }.each do |dest|
- Puppet::Util::Log.close(dest)
+ # Trap a couple of the main signals. This should probably be handled
+ # in a way that anyone else can register callbacks for traps, but, eh.
+ def set_signal_traps
+ {:INT => :stop, :TERM => :stop, :HUP => :restart, :USR1 => :reload, :USR2 => :reopen_logs}.each do |signal, method|
+ trap(signal) do
+ Puppet.notice "Caught #{signal}; calling #{method}"
+ send(method)
+ end
end
+ end
+
+ # Stop everything
+ def stop(args = {:exit => true})
+ server.stop if server
+
+ agent.stop if agent
+
+ remove_pidfile()
+
+ Puppet::Util::Log.close_all
+
+ exit if args[:exit]
+ end
+
+ def start
+ set_signal_traps
+
+ create_pidfile
+
+ raise Puppet::DevError, "Daemons must have an agent, server, or both" unless agent or server
+ agent.start if agent
+ server.start if server
- super
+ EventLoop.current.run
end
end
diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb
index e1b6dc423..ef170011f 100644
--- a/lib/puppet/defaults.rb
+++ b/lib/puppet/defaults.rb
@@ -60,12 +60,6 @@ module Puppet
this directory can be removed without causing harm (although it
might result in spurious service restarts)."
},
- :ssldir => {
- :default => "$confdir/ssl",
- :mode => 0771,
- :owner => "root",
- :desc => "Where SSL certificates are kept."
- },
:rundir => {
:default => rundir,
:mode => 01777,
@@ -141,7 +135,25 @@ module Puppet
but then ship with tools that do not know how to handle signed ints, so the UIDs show up as
huge numbers that can then not be fed back into the system. This is a hackish way to fail in a
slightly more useful way when that happens."],
- :node_terminus => ["plain", "Where to find information about nodes."]
+ :node_terminus => ["plain", "Where to find information about nodes."],
+ :httplog => { :default => "$logdir/http.log",
+ :owner => "root",
+ :mode => 0640,
+ :desc => "Where the puppetd web server logs."
+ },
+ :http_proxy_host => ["none",
+ "The HTTP proxy host to use for outgoing connections. Note: You
+ may need to use a FQDN for the server hostname when using a proxy."],
+ :http_proxy_port => [3128,
+ "The HTTP proxy port to use for outgoing connections"],
+ :http_enable_post_connection_check => [true,
+ "Boolean; wheter or not puppetd should validate the server
+ SSL certificate against the request hostname."],
+ :filetimeout => [ 15,
+ "The minimum time to wait (in seconds) between checking for updates in
+ configuration files. This timeout determines how quickly Puppet checks whether
+ a file (such as manifests or templates) has changed on disk."
+ ]
)
hostname = Facter["hostname"].value
@@ -152,7 +164,7 @@ module Puppet
fqdn = hostname
end
- Puppet.setdefaults(:ssl,
+ Puppet.setdefaults(:main,
# We have to downcase the fqdn, because the current ssl stuff (as oppsed to in master) doesn't have good facilities for
# manipulating naming.
:certname => {:default => fqdn.downcase, :desc => "The name to use when handling certificates. Defaults
@@ -163,7 +175,14 @@ module Puppet
If it's anything other than an empty string, it will be used as an alias in the created
certificate. By default, only the server gets an alias set up, and only for 'puppet'."],
:certdir => ["$ssldir/certs", "The certificate directory."],
+ :ssldir => {
+ :default => "$confdir/ssl",
+ :mode => 0771,
+ :owner => "root",
+ :desc => "Where SSL certificates are kept."
+ },
:publickeydir => ["$ssldir/public_keys", "The public key directory."],
+ :requestdir => ["$ssldir/certificate_requests", "Where host certificate requests are stored."],
:privatekeydir => { :default => "$ssldir/private_keys",
:mode => 0750,
:desc => "The private key directory."
@@ -179,7 +198,7 @@ module Puppet
},
:hostcsr => { :default => "$ssldir/csr_$certname.pem",
:mode => 0644,
- :desc => "Where individual hosts store and look for their certificates."
+ :desc => "Where individual hosts store and look for their certificate requests."
},
:hostcert => { :default => "$certdir/$certname.pem",
:mode => 0644,
@@ -196,6 +215,11 @@ module Puppet
:localcacert => { :default => "$certdir/ca.pem",
:mode => 0644,
:desc => "Where each client stores the CA certificate."
+ },
+ :hostcrl => { :default => "$ssldir/crl.pem",
+ :mode => 0644,
+ :desc => "Where the host's certificate revocation list can be found.
+ This is distinct from the certificate authority's CRL."
}
)
@@ -227,7 +251,12 @@ module Puppet
:owner => "$user",
:group => "$group",
:mode => 0664,
- :desc => "The certificate revocation list (CRL) for the CA. Set this to 'false' if you do not want to use a CRL."
+ :desc => "The certificate revocation list (CRL) for the CA. Will be used if present but otherwise ignored.",
+ :hook => proc do |value|
+ if value == 'false'
+ Puppet.warning "Setting the :cacrl to 'false' is deprecated; Puppet will just ignore the crl if yours is missing"
+ end
+ end
},
:caprivatedir => { :default => "$cadir/private",
:owner => "$user",
@@ -255,7 +284,7 @@ module Puppet
:serial => { :default => "$cadir/serial",
:owner => "$user",
:group => "$group",
- :mode => 0600,
+ :mode => 0644,
:desc => "Where the serial number for certificates is stored."
},
:autosign => { :default => "$confdir/autosign.conf",
@@ -288,13 +317,16 @@ module Puppet
self.setdefaults(self.settings[:name],
:config => ["$confdir/puppet.conf",
"The configuration file for #{Puppet[:name]}."],
- :pidfile => ["", "The pid file"],
- :bindaddress => ["", "The address to bind to. Mongrel servers
+ :pidfile => ["$rundir/$name.pid", "The pid file"],
+ :bindaddress => ["", "The address a listening server should bind to. Mongrel servers
default to 127.0.0.1 and WEBrick defaults to 0.0.0.0."],
- :servertype => ["webrick", "The type of server to use. Currently supported
+ :servertype => {:default => "webrick", :desc => "The type of server to use. Currently supported
options are webrick and mongrel. If you use mongrel, you will need
a proxy in front of the process or processes, since Mongrel cannot
- speak SSL."]
+ speak SSL.",
+ :call_on_define => true, # Call our hook with the default value, so we always get the correct bind address set.
+ :hook => proc { |value| value == "webrick" ? Puppet.settings[:bindaddress] = "0.0.0.0" : Puppet.settings[:bindaddress] = "127.0.0.1" if Puppet.settings[:bindaddress] == "" }
+ }
)
self.setdefaults(:puppetmasterd,
@@ -337,9 +369,9 @@ module Puppet
:desc => "Where FileBucket files are stored."
},
:ca => [true, "Wether the master should function as a certificate authority."],
- :modulepath => [ "$confdir/modules:/usr/share/puppet/modules",
- "The search path for modules as a colon-separated list of
- directories." ],
+ :modulepath => {:default => "$confdir/modules:/usr/share/puppet/modules",
+ :desc => "The search path for modules as a colon-separated list of
+ directories.", :type => :element }, # We don't want this to be considered a file, since it's multiple files.
:ssl_client_header => ["HTTP_X_CLIENT_DN", "The header containing an authenticated
client's SSL DN. Only used with Mongrel. This header must be set by the proxy
to the authenticated client's SSL DN (e.g., ``/CN=puppet.reductivelabs.com``).
@@ -353,7 +385,31 @@ module Puppet
:yamldir => {:default => "$vardir/yaml", :owner => "$user", :group => "$user", :mode => "750",
:desc => "The directory in which YAML data is stored, usually in a subdirectory."},
:clientyamldir => {:default => "$vardir/client_yaml", :mode => "750",
- :desc => "The directory in which client-side YAML data is stored."}
+ :desc => "The directory in which client-side YAML data is stored."},
+ :reports => ["store",
+ "The list of reports to generate. All reports are looked for
+ in puppet/reports/<name>.rb, and multiple report names should be
+ comma-separated (whitespace is okay)."
+ ],
+ :reportdir => {:default => "$vardir/reports",
+ :mode => 0750,
+ :owner => "$user",
+ :group => "$group",
+ :desc => "The directory in which to store reports
+ received from the client. Each client gets a separate
+ subdirectory."},
+ :fileserverconfig => ["$confdir/fileserver.conf",
+ "Where the fileserver configuration is stored."],
+ :rrddir => {:default => "$vardir/rrd",
+ :owner => "$user",
+ :group => "$group",
+ :desc => "The directory where RRD database files are stored.
+ Directories for each reporting host will be created under
+ this directory."
+ },
+ :rrdgraph => [false, "Whether RRD information should be graphed."],
+ :rrdinterval => ["$runinterval", "How often RRD should expect data.
+ This should match how often the hosts report back to the server."]
)
self.setdefaults(:puppetd,
@@ -381,19 +437,6 @@ module Puppet
:mode => 0640,
:desc => "The log file for puppetd. This is generally not used."
},
- :httplog => { :default => "$logdir/http.log",
- :owner => "root",
- :mode => 0640,
- :desc => "Where the puppetd web server logs."
- },
- :http_proxy_host => ["none",
- "The HTTP proxy host to use for outgoing connections. Note: You
- may need to use a FQDN for the server hostname when using a proxy."],
- :http_proxy_port => [3128,
- "The HTTP proxy port to use for outgoing connections"],
- :http_enable_post_connection_check => [true,
- "Boolean; wheter or not puppetd should validate the server
- SSL certificate against the request hostname."],
:server => ["puppet",
"The server to which server puppetd should connect"],
:ignoreschedules => [false,
@@ -414,35 +457,7 @@ module Puppet
:ca_port => ["$masterport", "The port to use for the certificate authority."],
:catalog_format => ["yaml", "What format to use to dump the catalog. Only supports
'marshal' and 'yaml'. Only matters on the client, since it asks the server
- for a specific format."]
- )
-
- self.setdefaults(:filebucket,
- :clientbucketdir => {
- :default => "$vardir/clientbucket",
- :mode => 0750,
- :desc => "Where FileBucket files are stored locally."
- }
- )
- self.setdefaults(:fileserver,
- :fileserverconfig => ["$confdir/fileserver.conf",
- "Where the fileserver configuration is stored."]
- )
- self.setdefaults(:reporting,
- :reports => ["store",
- "The list of reports to generate. All reports are looked for
- in puppet/reports/<name>.rb, and multiple report names should be
- comma-separated (whitespace is okay)."
- ],
- :reportdir => {:default => "$vardir/reports",
- :mode => 0750,
- :owner => "$user",
- :group => "$group",
- :desc => "The directory in which to store reports
- received from the client. Each client gets a separate
- subdirectory."}
- )
- self.setdefaults(:puppetd,
+ for a specific format."],
:puppetdlockfile => [ "$statedir/puppetdlock",
"A lock file to temporarily stop puppetd from doing anything."],
:usecacheonfailure => [true,
@@ -468,10 +483,12 @@ module Puppet
run interval."],
:splay => [false,
"Whether to sleep for a pseudo-random (but consistent) amount of time before
- a run."]
- )
-
- self.setdefaults(:puppetd,
+ a run."],
+ :clientbucketdir => {
+ :default => "$vardir/clientbucket",
+ :mode => 0750,
+ :desc => "Where FileBucket files are stored locally."
+ },
:configtimeout => [120,
"How long the client should wait for the configuration to be retrieved
before considering it a failure. This can help reduce flapping if too
@@ -482,16 +499,24 @@ module Puppet
],
:report => [false,
"Whether to send reports after every transaction."
- ]
+ ],
+ :graph => [false, "Whether to create dot graph files for the different
+ configuration graphs. These dot files can be interpreted by tools
+ like OmniGraffle or dot (which is part of ImageMagick)."],
+ :graphdir => ["$statedir/graphs", "Where to store dot-outputted graphs."],
+ :storeconfigs => [false,
+ "Whether to store each client's configuration. This
+ requires ActiveRecord from Ruby on Rails."]
)
# Plugin information.
self.setdefaults(:main,
- :pluginpath => ["$vardir/plugins",
- "Where Puppet should look for plugins. Multiple directories should
+ :pluginpath => {:default => "$vardir/plugins/",
+ :desc => "Where Puppet should look for plugins. Multiple directories should
be colon-separated, like normal PATH variables. As of 0.23.1, this
option is deprecated; download your custom libraries to the $libdir
- instead."],
+ instead.",
+ :type => :element}, # Don't consider this a file, since it's a colon-separated list.
:plugindest => ["$libdir",
"Where Puppet should store plugins that it pulls down from the central
server."],
@@ -507,15 +532,16 @@ module Puppet
# Central fact information.
self.setdefaults(:main,
- :factpath => {:default => "$vardir/facts",
+ :factpath => {:default => "$vardir/facts/",
:desc => "Where Puppet should look for facts. Multiple directories should
be colon-separated, like normal PATH variables.",
:call_on_define => true, # Call our hook with the default value, so we always get the value added to facter.
+ :type => :element, # Don't consider it a file, because it could be multiple colon-separated files
:hook => proc { |value| Facter.search(value) if Facter.respond_to?(:search) }},
- :factdest => ["$vardir/facts",
+ :factdest => ["$vardir/facts/",
"Where Puppet should store facts that it pulls down from the central
server."],
- :factsource => ["puppet://$server/facts",
+ :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."],
@@ -566,13 +592,6 @@ module Puppet
and other environments normally use ``debug``."]
)
- setdefaults(:graphing,
- :graph => [false, "Whether to create dot graph files for the different
- configuration graphs. These dot files can be interpreted by tools
- like OmniGraffle or dot (which is part of ImageMagick)."],
- :graphdir => ["$statedir/graphs", "Where to store dot-outputted graphs."]
- )
-
setdefaults(:transaction,
:tags => ["", "Tags to use to find resources. If this is set, then
only resources tagged with the specified tags will be applied.
@@ -649,12 +668,6 @@ module Puppet
branch under your main directory."]
)
- setdefaults(:puppetmasterd,
- :storeconfigs => [false,
- "Whether to store each client's configuration. This
- requires ActiveRecord from Ruby on Rails."]
- )
-
# This doesn't actually work right now.
setdefaults(:parser,
:lexical => [false, "Whether to use lexical scoping (vs. dynamic)."],
@@ -663,26 +676,4 @@ module Puppet
directories."
]
)
-
- setdefaults(:main,
- :filetimeout => [ 15,
- "The minimum time to wait (in seconds) between checking for updates in
- configuration files. This timeout determines how quickly Puppet checks whether
- a file (such as manifests or templates) has changed on disk."
- ]
- )
-
- setdefaults(:metrics,
- :rrddir => {:default => "$vardir/rrd",
- :owner => "$user",
- :group => "$group",
- :desc => "The directory where RRD database files are stored.
- Directories for each reporting host will be created under
- this directory."
- },
- :rrdgraph => [false, "Whether RRD information should be graphed."],
- :rrdinterval => ["$runinterval", "How often RRD should expect data.
- This should match how often the hosts report back to the server."]
- )
end
-
diff --git a/lib/puppet/executables/client/certhandler.rb b/lib/puppet/executables/client/certhandler.rb
deleted file mode 100644
index b041397ae..000000000
--- a/lib/puppet/executables/client/certhandler.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-
-module Puppet
- module Executables
- module Client
- class CertHandler
- attr_writer :wait_for_cert, :one_time
- attr_reader :new_cert
-
- def initialize(wait_time, is_one_time)
- @wait_for_cert = wait_time
- @one_time = is_one_time
- @new_cert = false
- end
-
- # Did we just read a cert?
- def new_cert?
- new_cert
- end
-
- # Read, or retrieve if necessary, our certificate. Returns true if we retrieved
- # a new cert, false if the cert already exists.
- def read_retrieve
- #NOTE: ACS this is checking that a file exists, maybe next time just do that?
- unless read_cert
- # If we don't already have the certificate, then create a client to
- # request one. Use the special ca stuff, don't use the normal server and port.
- retrieve_cert
- end
-
- ! new_cert?
- end
-
- def retrieve_cert
- caclient = Puppet::Network::Client.ca.new()
-
- while true do
- begin
- if caclient.request_cert
- break if read_new_cert
- else
- Puppet.notice "Did not receive certificate"
- if @one_time
- Puppet.notice "Set to run 'one time'; exiting with no certificate"
- exit(1)
- end
- end
- rescue StandardError => detail
- Puppet.err "Could not request certificate: %s" % detail.to_s
- exit(23) if @one_time
- end
-
- sleep @wait_for_cert
- end
- end
-
- def read_cert
- Puppet::Network::HttpPool.read_cert
- end
-
- def read_new_cert
- if Puppet::Network::HttpPool.read_cert
- # If we read it in, then we need to get rid of our existing http connection.
- # The @new_cert flag will help us do that, in that it provides a way
- # to notify that the cert status has changed.
- @new_cert = true
- Puppet.notice "Got signed certificate"
- else
- Puppet.err "Could not read certificates after retrieving them"
- exit(34) if @one_time
- end
-
- return @new_cert
- end
- end
- end
- end
-end
diff --git a/lib/puppet/file_serving/file_base.rb b/lib/puppet/file_serving/base.rb
index e87d683aa..2a0199dee 100644
--- a/lib/puppet/file_serving/file_base.rb
+++ b/lib/puppet/file_serving/base.rb
@@ -6,8 +6,10 @@ require 'puppet/file_serving'
# The base class for Content and Metadata; provides common
# functionality like the behaviour around links.
-class Puppet::FileServing::FileBase
- attr_accessor :key
+class Puppet::FileServing::Base
+ # This is for external consumers to store the source that was used
+ # to retrieve the metadata.
+ attr_accessor :source
# Does our file exist?
def exist?
@@ -21,17 +23,15 @@ class Puppet::FileServing::FileBase
# Return the full path to our file. Fails if there's no path set.
def full_path
- raise(ArgumentError, "You must set a path to get a file's path") unless self.path
-
- if relative_path.nil? or relative_path == ""
+ (if relative_path.nil? or relative_path == "" or relative_path == "."
path
else
File.join(path, relative_path)
- end
+ end).gsub(%r{/+}, "/")
end
- def initialize(key, options = {})
- @key = key
+ def initialize(path, options = {})
+ self.path = path
@links = :manage
options.each do |param, value|
diff --git a/lib/puppet/file_serving/configuration.rb b/lib/puppet/file_serving/configuration.rb
index ccf0957d1..907186ac3 100644
--- a/lib/puppet/file_serving/configuration.rb
+++ b/lib/puppet/file_serving/configuration.rb
@@ -5,25 +5,23 @@
require 'puppet'
require 'puppet/file_serving'
require 'puppet/file_serving/mount'
+require 'puppet/util/cacher'
class Puppet::FileServing::Configuration
require 'puppet/file_serving/configuration/parser'
+ class << self
+ include Puppet::Util::Cacher
+ cached_attr(:configuration) { new() }
+ end
+
@config_fileuration = nil
Mount = Puppet::FileServing::Mount
- # Remove our singleton instance.
- def self.clear_cache
- @config_fileuration = nil
- end
-
# Create our singleton configuration.
def self.create
- unless @config_fileuration
- @config_fileuration = new()
- end
- @config_fileuration
+ configuration
end
private_class_method :new
@@ -103,13 +101,14 @@ class Puppet::FileServing::Configuration
# Reparse the configuration if necessary.
readconfig
- raise(ArgumentError, "Cannot find file: Invalid path '%s'" % uri) unless uri =~ %r{/([-\w]+)/?}
+ raise(ArgumentError, "Cannot find file: Invalid path '%s'" % uri) unless uri =~ %r{^([-\w]+)(/|$)}
# the dir is based on one of the mounts
# so first retrieve the mount path
mount = path = nil
+
# Strip off the mount name.
- mount_name, path = uri.sub(%r{^/}, '').split(File::Separator, 2)
+ mount_name, path = uri.split(File::Separator, 2)
return nil unless mount = @mounts[mount_name]
diff --git a/lib/puppet/file_serving/content.rb b/lib/puppet/file_serving/content.rb
index 9398513e7..c1ecff749 100644
--- a/lib/puppet/file_serving/content.rb
+++ b/lib/puppet/file_serving/content.rb
@@ -4,31 +4,46 @@
require 'puppet/indirector'
require 'puppet/file_serving'
-require 'puppet/file_serving/file_base'
+require 'puppet/file_serving/base'
require 'puppet/file_serving/indirection_hooks'
# A class that handles retrieving file contents.
# It only reads the file when its content is specifically
# asked for.
-class Puppet::FileServing::Content < Puppet::FileServing::FileBase
+class Puppet::FileServing::Content < Puppet::FileServing::Base
extend Puppet::Indirector
indirects :file_content, :extend => Puppet::FileServing::IndirectionHooks
- attr_reader :path
+ attr_writer :content
+
+ def self.supported_formats
+ [:raw]
+ end
+
+ def self.from_raw(content)
+ instance = new("/this/is/a/fake/path")
+ instance.content = content
+ instance
+ end
+
+ # Collect our data.
+ def collect
+ return if stat.ftype == "directory"
+ content
+ end
# Read the content of our file in.
def content
- # This stat can raise an exception, too.
- raise(ArgumentError, "Cannot read the contents of links unless following links") if stat().ftype == "symlink"
+ unless defined?(@content) and @content
+ # This stat can raise an exception, too.
+ raise(ArgumentError, "Cannot read the contents of links unless following links") if stat().ftype == "symlink"
- ::File.read(full_path())
+ @content = ::File.read(full_path())
+ end
+ @content
end
- # Just return the file contents as the yaml. This allows us to
- # avoid escaping or any such thing. LAK:NOTE Not really sure how
- # this will behave if the file contains yaml... I think the far
- # side needs to understand that it's a plain string.
- def to_yaml
+ def to_raw
content
end
end
diff --git a/lib/puppet/file_serving/fileset.rb b/lib/puppet/file_serving/fileset.rb
index 3cb76317d..b28fb2d7e 100644
--- a/lib/puppet/file_serving/fileset.rb
+++ b/lib/puppet/file_serving/fileset.rb
@@ -30,6 +30,8 @@ class Puppet::FileServing::Fileset
# Should we ignore this path?
def ignore?(path)
+ return false if @ignore == [nil]
+
# 'detect' normally returns the found result, whereas we just want true/false.
! @ignore.detect { |pattern| File.fnmatch?(pattern, path) }.nil?
end
diff --git a/lib/puppet/file_serving/indirection_hooks.rb b/lib/puppet/file_serving/indirection_hooks.rb
index 66ed169dc..15564cf3d 100644
--- a/lib/puppet/file_serving/indirection_hooks.rb
+++ b/lib/puppet/file_serving/indirection_hooks.rb
@@ -9,36 +9,37 @@ require 'puppet/file_serving'
# in file-serving indirections. This is necessary because
# the terminus varies based on the URI asked for.
module Puppet::FileServing::IndirectionHooks
- PROTOCOL_MAP = {"puppet" => :rest, "file" => :file, "puppetmounts" => :file_server}
+ PROTOCOL_MAP = {"puppet" => :rest, "file" => :file}
# Pick an appropriate terminus based on the protocol.
def select_terminus(request)
- full_uri = request.key
- # Short-circuit to :file if it's a fully-qualified path.
- return PROTOCOL_MAP["file"] if full_uri =~ /^#{::File::SEPARATOR}/
- begin
- uri = URI.parse(URI.escape(full_uri))
- rescue => detail
- raise ArgumentError, "Could not understand URI %s: %s" % [full_uri, detail.to_s]
- end
+ # We rely on the request's parsing of the URI.
- terminus = PROTOCOL_MAP[uri.scheme] || raise(ArgumentError, "URI protocol '%s' is not supported for file serving" % uri.scheme)
+ # Short-circuit to :file if it's a fully-qualified path or specifies a 'file' protocol.
+ return PROTOCOL_MAP["file"] if request.key =~ /^#{::File::SEPARATOR}/
+ return PROTOCOL_MAP["file"] if request.protocol == "file"
- # This provides a convenient mechanism for people to write configurations work
- # well in both a networked and local setting.
- if uri.host.nil? and uri.scheme == "puppet" and Puppet.settings[:name] == "puppet"
- terminus = :file_server
+ # We're heading over the wire the protocol is 'puppet' and we've got a server name or we're not named 'puppet'
+ if request.protocol == "puppet" and (request.server or Puppet.settings[:name] != "puppet")
+ return PROTOCOL_MAP["puppet"]
+ end
+
+ if request.protocol and PROTOCOL_MAP[request.protocol].nil?
+ raise(ArgumentError, "URI protocol '%s' is not currently supported for file serving" % request.protocol)
end
+ # If we're still here, we're using the file_server or modules.
+
# This is the backward-compatible module terminus.
- if terminus == :file_server and uri.path =~ %r{^/([^/]+)\b}
- modname = $1
- if modname == "modules"
- terminus = :modules
- elsif terminus(:modules).find_module(modname, request.options[:node])
- Puppet.warning "DEPRECATION NOTICE: Found file '%s' in module without using the 'modules' mount; please prefix path with '/modules'" % uri.path
- terminus = :modules
- end
+ modname = request.key.split("/")[0]
+
+ if modname == "modules"
+ terminus = :modules
+ elsif terminus(:modules).find_module(modname, request.options[:node])
+ Puppet.warning "DEPRECATION NOTICE: Found file '%s' in module without using the 'modules' mount; please prefix path with 'modules/'" % request.key
+ terminus = :modules
+ else
+ terminus = :file_server
end
return terminus
diff --git a/lib/puppet/file_serving/metadata.rb b/lib/puppet/file_serving/metadata.rb
index b277955ac..1fc2b40ab 100644
--- a/lib/puppet/file_serving/metadata.rb
+++ b/lib/puppet/file_serving/metadata.rb
@@ -5,12 +5,12 @@
require 'puppet'
require 'puppet/indirector'
require 'puppet/file_serving'
-require 'puppet/file_serving/file_base'
+require 'puppet/file_serving/base'
require 'puppet/util/checksums'
require 'puppet/file_serving/indirection_hooks'
# A class that handles retrieving file metadata.
-class Puppet::FileServing::Metadata < Puppet::FileServing::FileBase
+class Puppet::FileServing::Metadata < Puppet::FileServing::Base
include Puppet::Util::Checksums
@@ -47,7 +47,7 @@ class Puppet::FileServing::Metadata < Puppet::FileServing::FileBase
# Retrieve the attributes for this file, relative to a base directory.
# Note that File.stat raises Errno::ENOENT if the file is absent and this
# method does not catch that exception.
- def collect_attributes
+ def collect
real_path = full_path()
stat = stat()
@owner = stat.uid
diff --git a/lib/puppet/file_serving/mount.rb b/lib/puppet/file_serving/mount.rb
index 8e5bd03e8..552cf33f2 100644
--- a/lib/puppet/file_serving/mount.rb
+++ b/lib/puppet/file_serving/mount.rb
@@ -4,6 +4,7 @@
require 'puppet/network/authstore'
require 'puppet/util/logging'
+require 'puppet/util/cacher'
require 'puppet/file_serving'
require 'puppet/file_serving/metadata'
require 'puppet/file_serving/content'
@@ -13,11 +14,16 @@ require 'puppet/file_serving/content'
class Puppet::FileServing::Mount < Puppet::Network::AuthStore
include Puppet::Util::Logging
- @@localmap = nil
+ class << self
+ include Puppet::Util::Cacher
- # Clear the cache. This is only ever used for testing.
- def self.clear_cache
- @@localmap = nil
+ cached_attr(:localmap) do
+ { "h" => Facter.value("hostname"),
+ "H" => [Facter.value("hostname"),
+ Facter.value("domain")].join("."),
+ "d" => Facter.value("domain")
+ }
+ end
end
attr_reader :name
@@ -173,14 +179,6 @@ class Puppet::FileServing::Mount < Puppet::Network::AuthStore
# Cache this manufactured map, since if it's used it's likely
# to get used a lot.
def localmap
- unless @@localmap
- @@localmap = {
- "h" => Facter.value("hostname"),
- "H" => [Facter.value("hostname"),
- Facter.value("domain")].join("."),
- "d" => Facter.value("domain")
- }
- end
- @@localmap
+ self.class.localmap
end
end
diff --git a/lib/puppet/file_serving/terminus_helper.rb b/lib/puppet/file_serving/terminus_helper.rb
index e5da0e29f..b51e27297 100644
--- a/lib/puppet/file_serving/terminus_helper.rb
+++ b/lib/puppet/file_serving/terminus_helper.rb
@@ -9,10 +9,20 @@ require 'puppet/file_serving/fileset'
module Puppet::FileServing::TerminusHelper
# Create model instances for all files in a fileset.
def path2instances(request, path)
- args = [:links, :ignore, :recurse].inject({}) { |hash, param| hash[param] = request.options[param] if request.options[param]; hash }
+ args = [:links, :ignore, :recurse].inject({}) do |hash, param|
+ if request.options.include?(param) # use 'include?' so the values can be false
+ hash[param] = request.options[param]
+ elsif request.options.include?(param.to_s)
+ hash[param] = request.options[param.to_s]
+ end
+ hash[param] = true if hash[param] == "true"
+ hash[param] = false if hash[param] == "false"
+ hash
+ end
Puppet::FileServing::Fileset.new(path, args).files.collect do |file|
- inst = model.new(File.join(request.key, file), :path => path, :relative_path => file)
+ inst = model.new(path, :relative_path => file)
inst.links = request.options[:links] if request.options[:links]
+ inst.collect
inst
end
end
diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb
index 2402b9cbe..1beb68ec0 100644
--- a/lib/puppet/indirector.rb
+++ b/lib/puppet/indirector.rb
@@ -10,6 +10,7 @@ module Puppet::Indirector
require 'puppet/indirector/indirection'
require 'puppet/indirector/terminus'
require 'puppet/indirector/envelope'
+ require 'puppet/network/format_handler'
# Declare that the including class indirects its methods to
# this terminus. The terminus name must be the name of a Puppet
@@ -22,6 +23,7 @@ module Puppet::Indirector
extend ClassMethods
include InstanceMethods
include Puppet::Indirector::Envelope
+ extend Puppet::Network::FormatHandler
# instantiate the actual Terminus for that type and this name (:ldap, w/ args :node)
# & hook the instantiated Terminus into this class (Node: @indirection = terminus)
diff --git a/lib/puppet/indirector/catalog/compiler.rb b/lib/puppet/indirector/catalog/compiler.rb
index a6a812817..47635d88c 100644
--- a/lib/puppet/indirector/catalog/compiler.rb
+++ b/lib/puppet/indirector/catalog/compiler.rb
@@ -1,10 +1,10 @@
require 'puppet/node'
-require 'puppet/node/catalog'
+require 'puppet/resource/catalog'
require 'puppet/indirector/code'
require 'puppet/parser/interpreter'
require 'yaml'
-class Puppet::Node::Catalog::Compiler < Puppet::Indirector::Code
+class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code
desc "Puppet's catalog compilation interface, and its back-end is
Puppet's compiler"
@@ -14,8 +14,14 @@ class Puppet::Node::Catalog::Compiler < Puppet::Indirector::Code
# Compile a node's catalog.
def find(request)
- unless node = request.options[:use_node] || find_node(request.key)
- raise ArgumentError, "Could not find node '%s'; cannot compile" % request.key
+ unless node = request.options[:use_node]
+ # If the request is authenticated, then the 'node' info will
+ # be available; if not, then we use the passed-in key. We rely
+ # on our authorization system to determine whether this is allowed.
+ name = request.node || request.key
+ unless node = find_node(name)
+ raise ArgumentError, "Could not find node '%s'; cannot compile" % name
+ end
end
if catalog = compile(node)
@@ -81,15 +87,14 @@ class Puppet::Node::Catalog::Compiler < Puppet::Indirector::Code
end
# Turn our host name into a node object.
- def find_node(key)
- # If we want to use the cert name as our key
- # LAK:FIXME This needs to be figured out somehow, but it requires the routing.
- # This should be able to use the request, yay.
- #if Puppet[:node_name] == 'cert' and client
- # key = client
- #end
+ def find_node(name)
+ begin
+ return nil unless node = Puppet::Node.find(name)
+ rescue => detail
+ puts detail.backtrace if Puppet[:trace]
+ raise Puppet::Error, "Failed when searching for node %s: %s" % [name, detail]
+ end
- return nil unless node = Puppet::Node.find(key)
# Add any external data to the node.
add_node_data(node)
@@ -126,17 +131,6 @@ class Puppet::Node::Catalog::Compiler < Puppet::Indirector::Code
end
end
- # Translate our catalog appropriately for sending back to a client.
- # LAK:FIXME This method should probably be part of the protocol, but it
- # shouldn't be here.
- def translate(config)
- unless networked?
- config
- else
- CGI.escape(config.to_yaml(:UseBlock => true))
- end
- end
-
# Mark that the node has checked in. LAK:FIXME this needs to be moved into
# the Node class, or somewhere that's got abstract backends.
def update_node_check(node)
diff --git a/lib/puppet/indirector/catalog/rest.rb b/lib/puppet/indirector/catalog/rest.rb
new file mode 100644
index 000000000..b70775be2
--- /dev/null
+++ b/lib/puppet/indirector/catalog/rest.rb
@@ -0,0 +1,6 @@
+require 'puppet/resource/catalog'
+require 'puppet/indirector/rest'
+
+class Puppet::Resource::Catalog::Rest < Puppet::Indirector::REST
+ desc "Find resource catalogs over HTTP via REST."
+end
diff --git a/lib/puppet/indirector/catalog/yaml.rb b/lib/puppet/indirector/catalog/yaml.rb
index 00241d852..d6fc9b81d 100644
--- a/lib/puppet/indirector/catalog/yaml.rb
+++ b/lib/puppet/indirector/catalog/yaml.rb
@@ -1,7 +1,7 @@
-require 'puppet/node/catalog'
+require 'puppet/resource/catalog'
require 'puppet/indirector/yaml'
-class Puppet::Node::Catalog::Yaml < Puppet::Indirector::Yaml
+class Puppet::Resource::Catalog::Yaml < Puppet::Indirector::Yaml
desc "Store catalogs as flat files, serialized using YAML."
private
@@ -10,8 +10,6 @@ class Puppet::Node::Catalog::Yaml < Puppet::Indirector::Yaml
# objects. This is hackish, but eh.
def from_yaml(text)
if config = YAML.load(text)
- # We can't yaml-dump classes.
- #config.edgelist_class = Puppet::Relationship
return config
end
end
diff --git a/lib/puppet/indirector/certificate/ca.rb b/lib/puppet/indirector/certificate/ca.rb
new file mode 100644
index 000000000..b64080e16
--- /dev/null
+++ b/lib/puppet/indirector/certificate/ca.rb
@@ -0,0 +1,9 @@
+require 'puppet/indirector/ssl_file'
+require 'puppet/ssl/certificate'
+
+class Puppet::SSL::Certificate::Ca < Puppet::Indirector::SslFile
+ desc "Manage the CA collection of signed SSL certificates on disk."
+
+ store_in :signeddir
+ store_ca_at :cacert
+end
diff --git a/lib/puppet/indirector/certificate/file.rb b/lib/puppet/indirector/certificate/file.rb
new file mode 100644
index 000000000..c19d001f4
--- /dev/null
+++ b/lib/puppet/indirector/certificate/file.rb
@@ -0,0 +1,9 @@
+require 'puppet/indirector/ssl_file'
+require 'puppet/ssl/certificate'
+
+class Puppet::SSL::Certificate::File < Puppet::Indirector::SslFile
+ desc "Manage SSL certificates on disk."
+
+ store_in :certdir
+ store_ca_at :localcacert
+end
diff --git a/lib/puppet/indirector/certificate/rest.rb b/lib/puppet/indirector/certificate/rest.rb
new file mode 100644
index 000000000..f88d60d40
--- /dev/null
+++ b/lib/puppet/indirector/certificate/rest.rb
@@ -0,0 +1,6 @@
+require 'puppet/ssl/certificate'
+require 'puppet/indirector/rest'
+
+class Puppet::SSL::Certificate::Rest < Puppet::Indirector::REST
+ desc "Find and save certificates over HTTP via REST."
+end
diff --git a/lib/puppet/indirector/certificate_request/ca.rb b/lib/puppet/indirector/certificate_request/ca.rb
new file mode 100644
index 000000000..e90f43a03
--- /dev/null
+++ b/lib/puppet/indirector/certificate_request/ca.rb
@@ -0,0 +1,14 @@
+require 'puppet/indirector/ssl_file'
+require 'puppet/ssl/certificate_request'
+
+class Puppet::SSL::CertificateRequest::Ca < Puppet::Indirector::SslFile
+ desc "Manage the CA collection of certificate requests on disk."
+
+ store_in :csrdir
+
+ def save(request)
+ result = super
+ Puppet.notice "%s has a waiting certificate request" % request.key
+ result
+ end
+end
diff --git a/lib/puppet/indirector/certificate_request/file.rb b/lib/puppet/indirector/certificate_request/file.rb
new file mode 100644
index 000000000..274311e2c
--- /dev/null
+++ b/lib/puppet/indirector/certificate_request/file.rb
@@ -0,0 +1,8 @@
+require 'puppet/indirector/ssl_file'
+require 'puppet/ssl/certificate_request'
+
+class Puppet::SSL::CertificateRequest::File < Puppet::Indirector::SslFile
+ desc "Manage the collection of certificate requests on disk."
+
+ store_in :requestdir
+end
diff --git a/lib/puppet/indirector/certificate_request/rest.rb b/lib/puppet/indirector/certificate_request/rest.rb
new file mode 100644
index 000000000..6df014583
--- /dev/null
+++ b/lib/puppet/indirector/certificate_request/rest.rb
@@ -0,0 +1,6 @@
+require 'puppet/ssl/certificate_request'
+require 'puppet/indirector/rest'
+
+class Puppet::SSL::CertificateRequest::Rest < Puppet::Indirector::REST
+ desc "Find and save certificate requests over HTTP via REST."
+end
diff --git a/lib/puppet/indirector/certificate_revocation_list/ca.rb b/lib/puppet/indirector/certificate_revocation_list/ca.rb
new file mode 100644
index 000000000..66cc23e50
--- /dev/null
+++ b/lib/puppet/indirector/certificate_revocation_list/ca.rb
@@ -0,0 +1,8 @@
+require 'puppet/indirector/ssl_file'
+require 'puppet/ssl/certificate_revocation_list'
+
+class Puppet::SSL::CertificateRevocationList::Ca < Puppet::Indirector::SslFile
+ desc "Manage the CA collection of certificate requests on disk."
+
+ store_at :cacrl
+end
diff --git a/lib/puppet/indirector/certificate_revocation_list/file.rb b/lib/puppet/indirector/certificate_revocation_list/file.rb
new file mode 100644
index 000000000..037aa6b8c
--- /dev/null
+++ b/lib/puppet/indirector/certificate_revocation_list/file.rb
@@ -0,0 +1,8 @@
+require 'puppet/indirector/ssl_file'
+require 'puppet/ssl/certificate_revocation_list'
+
+class Puppet::SSL::CertificateRevocationList::File < Puppet::Indirector::SslFile
+ desc "Manage the global certificate revocation list."
+
+ store_at :hostcrl
+end
diff --git a/lib/puppet/indirector/certificate_revocation_list/rest.rb b/lib/puppet/indirector/certificate_revocation_list/rest.rb
new file mode 100644
index 000000000..13cc95c87
--- /dev/null
+++ b/lib/puppet/indirector/certificate_revocation_list/rest.rb
@@ -0,0 +1,6 @@
+require 'puppet/ssl/certificate_revocation_list'
+require 'puppet/indirector/rest'
+
+class Puppet::SSL::CertificateRevocationList::Rest < Puppet::Indirector::REST
+ desc "Find and save certificate revocation lists over HTTP via REST."
+end
diff --git a/lib/puppet/indirector/direct_file_server.rb b/lib/puppet/indirector/direct_file_server.rb
index b3b4886f3..bcda92366 100644
--- a/lib/puppet/indirector/direct_file_server.rb
+++ b/lib/puppet/indirector/direct_file_server.rb
@@ -12,16 +12,14 @@ class Puppet::Indirector::DirectFileServer < Puppet::Indirector::Terminus
include Puppet::FileServing::TerminusHelper
def find(request)
- uri = key2uri(request.key)
- return nil unless FileTest.exists?(uri.path)
- instance = model.new(request.key, :path => uri.path)
+ return nil unless FileTest.exists?(request.key)
+ instance = model.new(request.key)
instance.links = request.options[:links] if request.options[:links]
return instance
end
def search(request)
- uri = key2uri(request.key)
- return nil unless FileTest.exists?(uri.path)
- path2instances(request, uri.path)
+ return nil unless FileTest.exists?(request.key)
+ path2instances(request, request.key)
end
end
diff --git a/lib/puppet/indirector/facts/facter.rb b/lib/puppet/indirector/facts/facter.rb
index a026dfe60..e664e17c7 100644
--- a/lib/puppet/indirector/facts/facter.rb
+++ b/lib/puppet/indirector/facts/facter.rb
@@ -6,31 +6,33 @@ class Puppet::Node::Facts::Facter < Puppet::Indirector::Code
between Puppet and Facter. It's only `somewhat` abstract because it always
returns the local host's facts, regardless of what you attempt to find."
- 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 %s %s" %
- [type, ::File.basename(file.sub(".rb",''))]
- Timeout::timeout(self.timeout) do
- load fqfile
- end
- rescue => detail
- Puppet.warning "Could not load %s %s: %s" % [type, fqfile, detail]
- end
- end
- end
- def self.loadfacts
+ def self.load_fact_plugins
# Add any per-module fact directories to the factpath
module_fact_dirs = Puppet[:modulepath].split(":").collect do |d|
Dir.glob("%s/*/plugins/facter" % d)
end.flatten
dirs = module_fact_dirs + Puppet[:factpath].split(":")
x = dirs.each do |dir|
- loaddir(dir, "fact")
+ load_facts_in_dir(dir)
+ end
+ end
+
+ def self.load_facts_in_dir(dir)
+ return unless FileTest.directory?(dir)
+
+ Dir.chdir(dir) do
+ Dir.glob("*.rb").each do |file|
+ fqfile = ::File.join(dir, file)
+ begin
+ Puppet.info "Loading facts in %s" % [::File.basename(file.sub(".rb",''))]
+ Timeout::timeout(self.timeout) do
+ load file
+ end
+ rescue => detail
+ Puppet.warning "Could not load fact file %s: %s" % [fqfile, detail]
+ end
+ end
end
end
@@ -53,7 +55,7 @@ class Puppet::Node::Facts::Facter < Puppet::Indirector::Code
def initialize(*args)
super
- self.class.loadfacts
+ self.class.load_fact_plugins
end
def destroy(facts)
@@ -62,7 +64,13 @@ class Puppet::Node::Facts::Facter < Puppet::Indirector::Code
# Look a host's facts up in Facter.
def find(request)
- Puppet::Node::Facts.new(request.key, Facter.to_hash)
+ result = Puppet::Node::Facts.new(request.key, Facter.to_hash)
+
+ result.add_local_facts
+ result.stringify
+ result.downcase_if_necessary
+
+ result
end
def save(facts)
diff --git a/lib/puppet/indirector/facts/rest.rb b/lib/puppet/indirector/facts/rest.rb
new file mode 100644
index 000000000..0f1c5f0d7
--- /dev/null
+++ b/lib/puppet/indirector/facts/rest.rb
@@ -0,0 +1,6 @@
+require 'puppet/node/facts'
+require 'puppet/indirector/rest'
+
+class Puppet::Node::Facts::Rest < Puppet::Indirector::REST
+ desc "Find and save certificates over HTTP via REST."
+end
diff --git a/lib/puppet/indirector/file.rb b/lib/puppet/indirector/file.rb
index e5382155f..99d95ecb2 100644
--- a/lib/puppet/indirector/file.rb
+++ b/lib/puppet/indirector/file.rb
@@ -11,6 +11,7 @@ class Puppet::Indirector::File < Puppet::Indirector::Terminus
end
raise Puppet::Error.new("File %s does not exist; cannot destroy" % [request.key]) unless File.exist?(path)
+ Puppet.notice "Removing file %s %s at '%s'" % [model, request.key, path]
begin
File.unlink(path)
rescue => detail
diff --git a/lib/puppet/indirector/file_metadata/file.rb b/lib/puppet/indirector/file_metadata/file.rb
index c46015c38..bb586489d 100644
--- a/lib/puppet/indirector/file_metadata/file.rb
+++ b/lib/puppet/indirector/file_metadata/file.rb
@@ -11,7 +11,7 @@ class Puppet::Indirector::FileMetadata::File < Puppet::Indirector::DirectFileSer
def find(request)
return unless data = super
- data.collect_attributes
+ data.collect
return data
end
@@ -19,7 +19,7 @@ class Puppet::Indirector::FileMetadata::File < Puppet::Indirector::DirectFileSer
def search(request)
return unless result = super
- result.each { |instance| instance.collect_attributes }
+ result.each { |instance| instance.collect }
return result
end
diff --git a/lib/puppet/indirector/file_metadata/modules.rb b/lib/puppet/indirector/file_metadata/modules.rb
index 5ed7a8a45..4598c2175 100644
--- a/lib/puppet/indirector/file_metadata/modules.rb
+++ b/lib/puppet/indirector/file_metadata/modules.rb
@@ -11,7 +11,7 @@ class Puppet::Indirector::FileMetadata::Modules < Puppet::Indirector::ModuleFile
def find(*args)
return unless instance = super
- instance.collect_attributes
+ instance.collect
instance
end
end
diff --git a/lib/puppet/indirector/file_server.rb b/lib/puppet/indirector/file_server.rb
index b0df7ff5d..46a590f9c 100644
--- a/lib/puppet/indirector/file_server.rb
+++ b/lib/puppet/indirector/file_server.rb
@@ -25,8 +25,9 @@ class Puppet::Indirector::FileServer < Puppet::Indirector::Terminus
# Find our key using the fileserver.
def find(request)
return nil unless path = find_path(request)
- result = model.new(request.key, :path => path)
+ result = model.new(path)
result.links = request.options[:links] if request.options[:links]
+ result.collect
return result
end
diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb
index 4841ec532..5d8cfe9b5 100644
--- a/lib/puppet/indirector/indirection.rb
+++ b/lib/puppet/indirector/indirection.rb
@@ -1,20 +1,17 @@
require 'puppet/util/docs'
require 'puppet/indirector/envelope'
require 'puppet/indirector/request'
+require 'puppet/util/cacher'
# The class that connects functional classes with their different collection
# back-ends. Each indirection has a set of associated terminus classes,
# each of which is a subclass of Puppet::Indirector::Terminus.
class Puppet::Indirector::Indirection
+ include Puppet::Util::Cacher
include Puppet::Util::Docs
@@indirections = []
- # Clear all cached termini from all indirections.
- def self.clear_cache
- @@indirections.each { |ind| ind.clear_cache }
- end
-
# Find an indirection by name. This is provided so that Terminus classes
# can specifically hook up with the indirections they are associated with.
def self.instance(name)
@@ -50,17 +47,10 @@ class Puppet::Indirector::Indirection
attr_reader :cache_class
# Define a terminus class to be used for caching.
def cache_class=(class_name)
- validate_terminus_class(class_name)
+ validate_terminus_class(class_name) if class_name
@cache_class = class_name
end
- # Clear our cached list of termini, and reset the cache name
- # so it's looked up again.
- # This is only used for testing.
- def clear_cache
- @termini.clear
- end
-
# This is only used for testing.
def delete
@@indirections.delete(self) if @@indirections.include?(self)
@@ -104,7 +94,6 @@ class Puppet::Indirector::Indirection
@model = model
@name = name
- @termini = {}
@cache_class = nil
@terminus_class = nil
@@ -138,7 +127,7 @@ class Puppet::Indirector::Indirection
raise Puppet::DevError, "No terminus specified for %s; cannot redirect" % self.name
end
- return @termini[terminus_name] ||= make_terminus(terminus_name)
+ return termini[terminus_name] ||= make_terminus(terminus_name)
end
# This can be used to select the terminus class.
@@ -197,7 +186,7 @@ class Puppet::Indirector::Indirection
terminus = prepare(request)
# See if our instance is in the cache and up to date.
- if cache? and cached = cache.find(request)
+ if cache? and ! request.ignore_cache? and cached = cache.find(request)
if cached.expired?
Puppet.info "Not using expired %s for %s from cache; expired at %s" % [self.name, request.key, cached.expiration]
else
@@ -207,9 +196,9 @@ class Puppet::Indirector::Indirection
end
# Otherwise, return the result from the terminus, caching if appropriate.
- if result = terminus.find(request)
+ if ! request.ignore_terminus? and result = terminus.find(request)
result.expiration ||= self.expiration
- if cache?
+ if cache? and request.use_cache?
Puppet.info "Caching %s for %s" % [self.name, request.key]
cache.save request(:save, result, *args)
end
@@ -255,9 +244,12 @@ class Puppet::Indirector::Indirection
request = request(:save, instance, *args)
terminus = prepare(request)
+ result = terminus.save(request)
+
# If caching is enabled, save our document there
cache.save(request) if cache?
- terminus.save(request)
+
+ result
end
private
@@ -273,7 +265,11 @@ class Puppet::Indirector::Indirection
return unless terminus.respond_to?(:authorized?)
unless terminus.authorized?(request)
- raise ArgumentError, "Not authorized to call %s on %s with %s" % [request.method, request.key, request.options.inspect]
+ msg = "Not authorized to call %s on %s" % [request.method, request.key]
+ unless request.options.empty?
+ msg += " with %s" % request.options.inspect
+ end
+ raise ArgumentError, msg
end
end
@@ -281,14 +277,17 @@ class Puppet::Indirector::Indirection
def prepare(request)
# Pick our terminus.
if respond_to?(:select_terminus)
- terminus_name = select_terminus(request)
+ unless terminus_name = select_terminus(request)
+ raise ArgumentError, "Could not determine appropriate terminus for %s" % request
+ end
else
terminus_name = terminus_class
end
- check_authorization(request, terminus(terminus_name))
+ dest_terminus = terminus(terminus_name)
+ check_authorization(request, dest_terminus)
- return terminus(terminus_name)
+ return dest_terminus
end
# Create a new terminus instance.
@@ -299,4 +298,7 @@ class Puppet::Indirector::Indirection
end
return klass.new
end
+
+ # Cache our terminus instances indefinitely, but make it easy to clean them up.
+ cached_attr(:termini) { Hash.new }
end
diff --git a/lib/puppet/indirector/key/ca.rb b/lib/puppet/indirector/key/ca.rb
new file mode 100644
index 000000000..3604de22b
--- /dev/null
+++ b/lib/puppet/indirector/key/ca.rb
@@ -0,0 +1,20 @@
+require 'puppet/indirector/ssl_file'
+require 'puppet/ssl/key'
+
+class Puppet::SSL::Key::Ca < Puppet::Indirector::SslFile
+ desc "Manage the CA's private on disk. This terminus *only* works
+ with the CA key, because that's the only key that the CA ever interacts
+ with."
+
+ # This is just to pass the validation in the base class. Eh.
+ store_at :cakey
+
+ store_ca_at :cakey
+
+ def path(name)
+ unless ca?(name)
+ raise ArgumentError, "The :ca terminus can only handle the CA private key"
+ end
+ super
+ end
+end
diff --git a/lib/puppet/indirector/key/file.rb b/lib/puppet/indirector/key/file.rb
new file mode 100644
index 000000000..4536f8aa7
--- /dev/null
+++ b/lib/puppet/indirector/key/file.rb
@@ -0,0 +1,42 @@
+require 'puppet/indirector/ssl_file'
+require 'puppet/ssl/key'
+
+class Puppet::SSL::Key::File < Puppet::Indirector::SslFile
+ desc "Manage SSL private and public keys on disk."
+
+ store_in :privatekeydir
+ store_ca_at :cakey
+
+ # Where should we store the public key?
+ def public_key_path(name)
+ if ca?(name)
+ Puppet[:capub]
+ else
+ File.join(Puppet[:publickeydir], name.to_s + ".pem")
+ end
+ end
+
+ # Remove the public key, in addition to the private key
+ def destroy(request)
+ super
+
+ return unless FileTest.exist?(public_key_path(request.key))
+
+ begin
+ File.unlink(public_key_path(request.key))
+ rescue => detail
+ raise Puppet::Error, "Could not remove %s public key: %s" % [request.key, detail]
+ end
+ end
+
+ # Save the public key, in addition to the private key.
+ def save(request)
+ super
+
+ begin
+ File.open(public_key_path(request.key), "w") { |f| f.print request.instance.content.public_key.to_pem }
+ rescue => detail
+ raise Puppet::Error, "Could not write %s: %s" % [key, detail]
+ end
+ end
+end
diff --git a/lib/puppet/indirector/module_files.rb b/lib/puppet/indirector/module_files.rb
index cf5c29cab..7c5cf278f 100644
--- a/lib/puppet/indirector/module_files.rb
+++ b/lib/puppet/indirector/module_files.rb
@@ -21,7 +21,7 @@ class Puppet::Indirector::ModuleFiles < Puppet::Indirector::Terminus
# Make sure our file path starts with /modules, so that we authorize
# against the 'modules' mount.
- path = uri.path =~ /^\/modules/ ? uri.path : "/modules" + uri.path
+ path = uri.path =~ /^modules\// ? uri.path : "modules/" + uri.path
configuration.authorized?(path, :node => request.node, :ipaddress => request.ip)
end
@@ -30,7 +30,7 @@ class Puppet::Indirector::ModuleFiles < Puppet::Indirector::Terminus
def find(request)
return nil unless path = find_path(request)
- result = model.new(request.key, :path => path)
+ result = model.new(path)
result.links = request.options[:links] if request.options[:links]
return result
end
@@ -66,9 +66,8 @@ class Puppet::Indirector::ModuleFiles < Puppet::Indirector::Terminus
def find_path(request)
uri = key2uri(request.key)
- # Strip off /modules if it's there -- that's how requests get routed to this terminus.
- # Also, strip off the leading slash if present.
- module_name, relative_path = uri.path.sub(/^\/modules\b/, '').sub(%r{^/}, '').split(File::Separator, 2)
+ # Strip off modules/ if it's there -- that's how requests get routed to this terminus.
+ module_name, relative_path = uri.path.sub(/^modules\//, '').sub(%r{^/}, '').split(File::Separator, 2)
# And use the environment to look up the module.
return nil unless mod = find_module(module_name, request.node)
diff --git a/lib/puppet/indirector/report/rest.rb b/lib/puppet/indirector/report/rest.rb
new file mode 100644
index 000000000..905b71a5d
--- /dev/null
+++ b/lib/puppet/indirector/report/rest.rb
@@ -0,0 +1,5 @@
+require 'puppet/indirector/rest'
+
+class Puppet::Transaction::Report::Rest < Puppet::Indirector::REST
+ desc "Get server report over HTTP via REST."
+end
diff --git a/lib/puppet/indirector/request.rb b/lib/puppet/indirector/request.rb
index 98fa38885..8227db174 100644
--- a/lib/puppet/indirector/request.rb
+++ b/lib/puppet/indirector/request.rb
@@ -1,9 +1,12 @@
require 'puppet/indirector'
-# Provide any attributes or functionality needed for indirected
-# instances.
+# This class encapsulates all of the information you need to make an
+# Indirection call, and as a a result also handles REST calls. It's somewhat
+# analogous to an HTTP Request object, except tuned for our Indirector.
class Puppet::Indirector::Request
- attr_accessor :indirection_name, :key, :method, :options, :instance, :node, :ip, :authenticated
+ attr_accessor :indirection_name, :key, :method, :options, :instance, :node, :ip, :authenticated, :ignore_cache, :ignore_terminus
+
+ attr_accessor :server, :port, :uri, :protocol
# Is this an authenticated request?
def authenticated?
@@ -11,6 +14,19 @@ class Puppet::Indirector::Request
! ! authenticated
end
+ # LAK:NOTE This is a messy interface to the cache, and it's only
+ # used by the Configurer class. I decided it was better to implement
+ # it now and refactor later, when we have a better design, than
+ # to spend another month coming up with a design now that might
+ # not be any better.
+ def ignore_cache?
+ ignore_cache
+ end
+
+ def ignore_terminus?
+ ignore_terminus
+ end
+
def initialize(indirection_name, method, key, options = {})
options ||= {}
raise ArgumentError, "Request options must be a hash, not %s" % options.class unless options.is_a?(Hash)
@@ -28,7 +44,15 @@ class Puppet::Indirector::Request
end
if key.is_a?(String) or key.is_a?(Symbol)
- @key = key
+ # If the request key is a URI, then we need to treat it specially,
+ # because it rewrites the key. We could otherwise strip server/port/etc
+ # info out in the REST class, but it seemed bad design for the REST
+ # class to rewrite the key.
+ if key.to_s =~ /^\w+:\/\// # it's a URI
+ set_uri_key(key)
+ else
+ @key = key
+ end
else
@instance = key
@key = @instance.name
@@ -37,6 +61,51 @@ class Puppet::Indirector::Request
# Look up the indirection based on the name provided.
def indirection
- Puppet::Indirector::Indirection.instance(@indirection_name)
+ Puppet::Indirector::Indirection.instance(indirection_name)
+ end
+
+ # Should we allow use of the cached object?
+ def use_cache?
+ if defined?(@use_cache)
+ ! ! use_cache
+ else
+ true
+ end
+ end
+
+ # Are we trying to interact with multiple resources, or just one?
+ def plural?
+ method == :search
+ end
+
+ private
+
+ # Parse the key as a URI, setting attributes appropriately.
+ def set_uri_key(key)
+ @uri = key
+ begin
+ uri = URI.parse(URI.escape(key))
+ rescue => detail
+ raise ArgumentError, "Could not understand URL %s: %s" % [source, detail.to_s]
+ end
+
+ # Just short-circuit these to full paths
+ if uri.scheme == "file"
+ @key = uri.path
+ return
+ end
+
+ @server = uri.host if uri.host
+
+ # If the URI class can look up the scheme, it will provide a port,
+ # otherwise it will default to '0'.
+ if uri.port.to_i == 0 and uri.scheme == "puppet"
+ @port = Puppet.settings[:masterport].to_i
+ else
+ @port = uri.port.to_i
+ end
+
+ @protocol = uri.scheme
+ @key = uri.path.sub(/^\//, '')
end
end
diff --git a/lib/puppet/indirector/rest.rb b/lib/puppet/indirector/rest.rb
index d33150fc2..5ac25f02d 100644
--- a/lib/puppet/indirector/rest.rb
+++ b/lib/puppet/indirector/rest.rb
@@ -1,56 +1,97 @@
require 'net/http'
require 'uri'
+require 'puppet/network/http_pool'
+
# Access objects via REST
class Puppet::Indirector::REST < Puppet::Indirector::Terminus
- def rest_connection_details
- { :host => Puppet[:server], :port => Puppet[:masterport].to_i }
+ class << self
+ attr_reader :server_setting, :port_setting
end
- def network_fetch(path)
- network {|conn| conn.get("/#{path}").body }
+ # Specify the setting that we should use to get the server name.
+ def self.use_server_setting(setting)
+ @server_setting = setting
end
-
- def network_delete(path)
- network {|conn| conn.delete("/#{path}").body }
+
+ def self.server
+ return Puppet.settings[server_setting || :server]
end
-
- def network_put(path, data)
- network {|conn| conn.put("/#{path}", data).body }
+
+ # Specify the setting that we should use to get the port.
+ def self.use_port_setting(setting)
+ @port_setting = setting
end
-
+
+ def self.port
+ return Puppet.settings[port_setting || :masterport].to_i
+ end
+
+ # Figure out the content type, turn that into a format, and use the format
+ # to extract the body of the response.
+ def deserialize(response, multiple = false)
+ case response.code
+ when "404"
+ return nil
+ when /^2/
+ unless response['content-type']
+ raise "No content type in http response; cannot parse"
+ end
+
+ # Convert the response to a deserialized object.
+ if multiple
+ model.convert_from_multiple(response['content-type'], response.body)
+ else
+ model.convert_from(response['content-type'], response.body)
+ end
+ else
+ # Raise the http error if we didn't get a 'success' of some kind.
+ message = "Server returned %s: %s" % [response.code, response.message]
+ raise Net::HTTPError.new(message, response)
+ end
+ end
+
+ # Provide appropriate headers.
+ def headers
+ {"Accept" => model.supported_formats.join(", ")}
+ end
+
+ def network(request)
+ Puppet::Network::HttpPool.http_instance(request.server || self.class.server, request.port || self.class.port)
+ end
+
def find(request)
- network_result = network_fetch("#{indirection.name}/#{request.key}")
- raise YAML.load(network_result) if exception?(network_result)
- indirection.model.from_yaml(network_result)
+ deserialize network(request).get("/#{indirection.name}/#{request.key}#{query_string(request)}", headers)
end
def search(request)
- network_results = network_fetch("#{indirection.name}s/#{request.key}")
- raise YAML.load(network_results) if exception?(network_results)
- YAML.load(network_results.to_s).collect {|result| indirection.model.from_yaml(result) }
+ if request.key
+ path = "/#{indirection.name}s/#{request.key}#{query_string(request)}"
+ else
+ path = "/#{indirection.name}s#{query_string(request)}"
+ end
+ unless result = deserialize(network(request).get(path, headers), true)
+ return []
+ end
+ return result
end
def destroy(request)
- network_result = network_delete("#{indirection.name}/#{request.key}")
- raise YAML.load(network_result) if exception?(network_result)
- YAML.load(network_result.to_s)
+ raise ArgumentError, "DELETE does not accept options" unless request.options.empty?
+ deserialize network(request).delete("/#{indirection.name}/#{request.key}", headers)
end
def save(request)
- network_result = network_put("#{indirection.name}/", request.instance.to_yaml)
- raise YAML.load(network_result) if exception?(network_result)
- indirection.model.from_yaml(network_result)
+ raise ArgumentError, "PUT does not accept options" unless request.options.empty?
+ deserialize network(request).put("/#{indirection.name}/", request.instance.render, headers)
end
-
- private
-
- def network(&block)
- Net::HTTP.start(rest_connection_details[:host], rest_connection_details[:port]) {|conn| yield(conn) }
- end
-
- def exception?(yaml_string)
- yaml_string =~ %r{--- !ruby/exception}
+
+ private
+
+ # Create the query string, if options are present.
+ def query_string(request)
+ return "" unless request.options and ! request.options.empty?
+ "?" + request.options.collect { |key, value| "%s=%s" % [key, value] }.join("&")
end
end
diff --git a/lib/puppet/indirector/runner/rest.rb b/lib/puppet/indirector/runner/rest.rb
new file mode 100644
index 000000000..25d3def3f
--- /dev/null
+++ b/lib/puppet/indirector/runner/rest.rb
@@ -0,0 +1,7 @@
+require 'puppet/agent'
+require 'puppet/agent/runner'
+require 'puppet/indirector/rest'
+
+class Puppet::Agent::Runner::Rest < Puppet::Indirector::REST
+ desc "Trigger Agent runs via REST."
+end
diff --git a/lib/puppet/indirector/ssl_file.rb b/lib/puppet/indirector/ssl_file.rb
new file mode 100644
index 000000000..4119a656f
--- /dev/null
+++ b/lib/puppet/indirector/ssl_file.rb
@@ -0,0 +1,172 @@
+require 'puppet/ssl'
+
+class Puppet::Indirector::SslFile < Puppet::Indirector::Terminus
+ # Specify the directory in which multiple files are stored.
+ def self.store_in(setting)
+ @directory_setting = setting
+ end
+
+ # Specify a single file location for storing just one file.
+ # This is used for things like the CRL.
+ def self.store_at(setting)
+ @file_setting = setting
+ end
+
+ # Specify where a specific ca file should be stored.
+ def self.store_ca_at(setting)
+ @ca_setting = setting
+ end
+
+ class << self
+ attr_reader :directory_setting, :file_setting, :ca_setting
+ end
+
+ # The full path to where we should store our files.
+ def self.collection_directory
+ return nil unless directory_setting
+ Puppet.settings[directory_setting]
+ end
+
+ # The full path to an individual file we would be managing.
+ def self.file_location
+ return nil unless file_setting
+ Puppet.settings[file_setting]
+ end
+
+ # The full path to a ca file we would be managing.
+ def self.ca_location
+ return nil unless ca_setting
+ Puppet.settings[ca_setting]
+ end
+
+ # We assume that all files named 'ca' are pointing to individual ca files,
+ # rather than normal host files. It's a bit hackish, but all the other
+ # solutions seemed even more hackish.
+ def ca?(name)
+ name == Puppet::SSL::Host.ca_name
+ end
+
+ def initialize
+ Puppet.settings.use(:main, :ssl)
+
+ (collection_directory || file_location) or raise Puppet::DevError, "No file or directory setting provided; terminus %s cannot function" % self.class.name
+ end
+
+ # Use a setting to determine our path.
+ def path(name)
+ if ca?(name) and ca_location
+ ca_location
+ elsif collection_directory
+ File.join(collection_directory, name.to_s + ".pem")
+ else
+ file_location
+ end
+ end
+
+ # Remove our file.
+ def destroy(request)
+ path = path(request.key)
+ return false unless FileTest.exist?(path)
+
+ Puppet.notice "Removing file %s %s at '%s'" % [model, request.key, path]
+ begin
+ File.unlink(path)
+ rescue => detail
+ raise Puppet::Error, "Could not remove %s: %s" % [request.key, detail]
+ end
+ end
+
+ # Find the file on disk, returning an instance of the model.
+ def find(request)
+ path = path(request.key)
+
+ return nil unless FileTest.exist?(path) or rename_files_with_uppercase(path)
+
+ result = model.new(request.key)
+ result.read(path)
+ result
+ end
+
+ # Save our file to disk.
+ def save(request)
+ path = path(request.key)
+ dir = File.dirname(path)
+
+ raise Puppet::Error.new("Cannot save %s; parent directory %s does not exist" % [request.key, dir]) unless FileTest.directory?(dir)
+ raise Puppet::Error.new("Cannot save %s; parent directory %s is not writable" % [request.key, dir]) unless FileTest.writable?(dir)
+
+ write(request.key, path) { |f| f.print request.instance.to_s }
+ end
+
+ # Search for more than one file. At this point, it just returns
+ # an instance for every file in the directory.
+ def search(request)
+ dir = collection_directory
+ Dir.entries(dir).reject { |file| file !~ /\.pem$/ }.collect do |file|
+ name = file.sub(/\.pem$/, '')
+ result = model.new(name)
+ result.read(File.join(dir, file))
+ result
+ end
+ end
+
+ private
+
+ # Demeterish pointers to class info.
+ def collection_directory
+ self.class.collection_directory
+ end
+
+ def file_location
+ self.class.file_location
+ end
+
+ def ca_location
+ self.class.ca_location
+ end
+
+ # A hack method to deal with files that exist with a different case.
+ # Just renames it; doesn't read it in or anything.
+ # LAK:NOTE This is a copy of the method in sslcertificates/support.rb,
+ # which we'll be EOL'ing at some point. This method was added at 20080702
+ # and should be removed at some point.
+ def rename_files_with_uppercase(file)
+ dir, short = File.split(file)
+ return nil unless FileTest.exist?(dir)
+
+ raise ArgumentError, "Tried to fix SSL files to a file containing uppercase" unless short.downcase == short
+ real_file = Dir.entries(dir).reject { |f| f =~ /^\./ }.find do |other|
+ other.downcase == short
+ end
+
+ return nil unless real_file
+
+ full_file = File.join(dir, real_file)
+
+ Puppet.notice "Fixing case in %s; renaming to %s" % [full_file, file]
+ File.rename(full_file, file)
+
+ return true
+ end
+
+ # Yield a filehandle set up appropriately, either with our settings doing
+ # the work or opening a filehandle manually.
+ def write(name, path)
+ if ca?(name) and ca_location
+ Puppet.settings.write(self.class.ca_setting) { |f| yield f }
+ elsif file_location
+ Puppet.settings.write(self.class.file_setting) { |f| yield f }
+ else
+ begin
+ File.open(path, "w") { |f| yield f }
+ rescue => detail
+ raise Puppet::Error, "Could not write %s: %s" % [path, detail]
+ end
+ end
+ end
+end
+
+# LAK:NOTE This has to be at the end, because classes like SSL::Key use this
+# class, and this require statement loads those, which results in a load loop
+# and lots of failures.
+require 'puppet/ssl/host'
diff --git a/lib/puppet/indirector/ssl_rsa.rb b/lib/puppet/indirector/ssl_rsa.rb
deleted file mode 100644
index 162d8200a..000000000
--- a/lib/puppet/indirector/ssl_rsa.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-# This is a stub class
-
-class Puppet::Indirector::SslRsa #:nodoc:
-end
-
diff --git a/lib/puppet/indirector/ssl_rsa/file.rb b/lib/puppet/indirector/ssl_rsa/file.rb
deleted file mode 100644
index 435aa8f86..000000000
--- a/lib/puppet/indirector/ssl_rsa/file.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'puppet/indirector/file'
-require 'puppet/indirector/ssl_rsa'
-
-class Puppet::Indirector::SslRsa::File < Puppet::Indirector::File
- desc "Store SSL keys on disk."
-
- def initialize
- Puppet.settings.use(:ssl)
- end
-
- def path(name)
- if name == :ca
- File.join Puppet.settings[:cadir], "ca_key.pem"
- else
- File.join Puppet.settings[:publickeydir], name.to_s + ".pem"
- end
- end
-
- def save(key)
- File.open(path(key.name), "w") { |f| f.print key.to_pem }
- end
-
- def find(name)
- return nil unless FileTest.exists?(path(name))
- OpenSSL::PKey::RSA.new(File.read(path(name)))
- end
-
- def destroy(name)
- return nil unless FileTest.exists?(path(name))
- File.unlink(path(name)) and true
- end
-
-end
diff --git a/lib/puppet/module.rb b/lib/puppet/module.rb
index 7bf35ac18..ab1bc75bd 100644
--- a/lib/puppet/module.rb
+++ b/lib/puppet/module.rb
@@ -12,9 +12,14 @@ class Puppet::Module
dirs = Puppet.settings.value(:modulepath, environment).split(":")
if ENV["PUPPETLIB"]
dirs = ENV["PUPPETLIB"].split(":") + dirs
- else
end
- dirs.select do |p|
+ dirs.collect do |dir|
+ if dir !~ /^#{File::SEPARATOR}/
+ File.join(Dir.getwd, dir)
+ else
+ dir
+ end
+ end.select do |p|
p =~ /^#{File::SEPARATOR}/ && File::directory?(p)
end
end
diff --git a/lib/puppet/network/client.rb b/lib/puppet/network/client.rb
index 478883959..429e2563f 100644
--- a/lib/puppet/network/client.rb
+++ b/lib/puppet/network/client.rb
@@ -1,7 +1,6 @@
# the available clients
require 'puppet'
-require 'puppet/daemon'
require 'puppet/network/xmlrpc/client'
require 'puppet/util/subclass_loader'
require 'puppet/util/methodhelper'
@@ -34,7 +33,6 @@ end
# provide a different interface.
class Puppet::Network::Client
Client = self
- include Puppet::Daemon
include Puppet::Util
extend Puppet::Util::SubclassLoader
include Puppet::Util::MethodHelper
diff --git a/lib/puppet/network/client/ca.rb b/lib/puppet/network/client/ca.rb
index a2704e451..5fbdfe9e3 100644
--- a/lib/puppet/network/client/ca.rb
+++ b/lib/puppet/network/client/ca.rb
@@ -45,7 +45,7 @@ class Puppet::Network::Client::CA < Puppet::Network::Client
end
unless @cert.check_private_key(key)
- raise InvalidCertificate, "Certificate does not match private key. Try 'puppetca --clean %s' on the server." % Facter.value(:fqdn)
+ raise InvalidCertificate, "Certificate does not match private key. Try 'puppetca --clean %s' on the server." % Puppet[:certname]
end
# Only write the cert out if it passes validating.
diff --git a/lib/puppet/network/client/master.rb b/lib/puppet/network/client/master.rb
deleted file mode 100644
index 4b0cfdae3..000000000
--- a/lib/puppet/network/client/master.rb
+++ /dev/null
@@ -1,524 +0,0 @@
-# The client for interacting with the puppetmaster config server.
-require 'sync'
-require 'timeout'
-require 'puppet/network/http_pool'
-
-class Puppet::Network::Client::Master < Puppet::Network::Client
- unless defined? @@sync
- @@sync = Sync.new
- end
-
- attr_accessor :catalog
- 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]
-
- Facter.clear
-
- # Reload everything.
- if Facter.respond_to? :loadfacts
- Facter.loadfacts
- elsif Facter.respond_to? :load
- Facter.load
- else
- Puppet.warning "You should upgrade your version of Facter to at least 1.3.8"
- 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()
- facts = Facter.to_hash.inject({}) do |newhash, array|
- name, fact = array
- if down
- newhash[name] = fact.to_s.downcase
- else
- newhash[name] = fact.to_s
- end
- newhash
- end
-
- # Add our client version to the list of facts, so people can use it
- # in their manifests
- facts["clientversion"] = Puppet.version.to_s
-
- # And add our environment as a fact.
- unless facts.include?("environment")
- facts["environment"] = Puppet[:environment]
- end
-
- facts
- end
-
- # Return the list of dynamic facts as an array of symbols
- # NOTE:LAK(2008/04/10): This code is currently unused, since we now always
- # recompile.
- def self.dynamic_facts
- # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com]
- x = Puppet.settings[:dynamicfacts].split(/\s*,\s*/).collect { |fact| fact.downcase }
- end
-
- # Cache the config
- def cache(text)
- Puppet.info "Caching catalog 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
- @catalog.clear(true) if @catalog
- Puppet::Type.allclear
- @catalog = nil
- 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
-
- # Let the daemon run again, freely in the filesystem. Frolick, little
- # daemon!
- def enable
- lockfile.unlock(:anonymous => true)
- end
-
- # Stop the daemon from making any catalog runs.
- def disable
- lockfile.lock(:anonymous => true)
- end
-
- # Retrieve the config from a remote server. If this fails, then
- # use the cached copy.
- def getconfig
- dostorage()
-
- # Retrieve the plugins.
- getplugins() if Puppet[:pluginsync]
-
- facts = nil
- Puppet::Util.benchmark(:debug, "Retrieved facts") do
- facts = self.class.facts
- end
-
- raise Puppet::Network::ClientError.new("Could not retrieve any facts") unless facts.length > 0
-
- Puppet.debug("Retrieving catalog")
-
- # If we can't retrieve the catalog, just return, which will either
- # fail, or use the in-memory catalog.
- unless marshalled_objects = get_actual_config(facts)
- use_cached_config(true)
- return
- end
-
- begin
- case Puppet[:catalog_format]
- when "marshal": objects = Marshal.load(marshalled_objects)
- when "yaml": objects = YAML.load(marshalled_objects)
- else
- raise "Invalid catalog format '%s'" % Puppet[:catalog_format]
- end
- rescue => detail
- msg = "Configuration could not be translated from %s" % Puppet[:catalog_format]
- msg += "; using cached catalog" if use_cached_config(true)
- Puppet.warning msg
- return
- end
-
- self.setclasses(objects.classes)
-
- # Clear all existing objects, so we can recreate our stack.
- clear() if self.catalog
-
- # Now convert the objects to a puppet catalog graph.
- begin
- @catalog = objects.to_catalog
- rescue => detail
- clear()
- puts detail.backtrace if Puppet[:trace]
- msg = "Configuration could not be instantiated: %s" % detail
- msg += "; using cached catalog" if use_cached_config(true)
- Puppet.warning msg
- return
- end
-
- if ! @catalog.from_cache
- self.cache(marshalled_objects)
- end
-
- # Keep the state database up to date.
- @catalog.host_config = true
- 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.settings.use(:main, :ssl, :puppetd)
- super
-
- self.class.instance = self
- @running = false
- @splayed = false
- 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 nil
- end
- end
-
- # The code that actually runs the catalog.
- # This just passes any options on to the catalog,
- # which accepts :tags and :ignoreschedules.
- def run(options = {})
- got_lock = false
- splay
- Puppet::Util.sync(:puppetrun).synchronize(Sync::EX) do
- if !lockfile.lock
- Puppet.notice "Lock file %s exists; skipping catalog run" %
- lockfile.lockfile
- else
- got_lock = true
- begin
- duration = thinmark do
- self.getconfig
- end
- rescue => detail
- puts detail.backtrace if Puppet[:trace]
- Puppet.err "Could not retrieve catalog: %s" % detail
- end
-
- if self.catalog
- @catalog.retrieval_duration = duration
- Puppet.notice "Starting catalog run" unless @local
- benchmark(:notice, "Finished catalog run") do
- @catalog.apply(options)
- end
- end
-
- # Now close all of our existing http connections, since there's no
- # reason to leave them lying open.
- Puppet::Network::HttpPool.clear_http_instances
- 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
- ensure
- # Just make sure we remove the lock file if we set it.
- lockfile.unlock if got_lock and lockfile.locked?
- clear()
- end
-
- def running?
- lockfile.locked?
- 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)
- hash = {
- :path => args[:dest],
- :recurse => true,
- :source => args[:source],
- :tag => "#{args[:name]}s",
- :owner => Process.uid,
- :group => Process.gid,
- :purge => true,
- :force => true,
- :backup => false,
- :noop => false
- }
-
- if args[:ignore]
- hash[:ignore] = args[:ignore].split(/\s+/)
- end
- downconfig = Puppet::Node::Catalog.new("downloading")
- downconfig.add_resource Puppet::Type.type(:file).create(hash)
-
- Puppet.info "Retrieving #{args[:name]}s"
-
- files = []
- begin
- Timeout::timeout(self.timeout) do
- downconfig.apply do |trans|
- trans.changed?.find_all do |resource|
- yield resource if block_given?
- files << resource[:path]
- end
- end
- end
- rescue Puppet::Error, Timeout::Error => detail
- if Puppet[:debug]
- puts detail.backtrace
- end
- Puppet.err "Could not retrieve %ss: %s" % [args[:name], detail]
- end
-
- # Now clean up after ourselves
- downconfig.clear
-
- return files
- end
-
- # Retrieve facts from the central server.
- def self.getfacts
- # Download the new facts
- path = Puppet[:factpath].split(":")
- files = []
- download(:dest => Puppet[:factdest], :source => Puppet[:factsource],
- :ignore => Puppet[:factsignore], :name => "fact") do |resource|
-
- next unless path.include?(::File.dirname(resource[:path]))
-
- files << resource[:path]
- end
- 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
- download(:dest => Puppet[:plugindest], :source => Puppet[:pluginsource],
- :ignore => Puppet[:pluginsignore], :name => "plugin") do |resource|
-
- next if FileTest.directory?(resource[:path])
- path = resource[:path].sub(Puppet[:plugindest], '').sub(/^\/+/, '')
- unless Puppet::Util::Autoload.loaded?(path)
- next
- end
-
- begin
- Puppet.info "Reloading downloaded file %s" % path
- load resource[:path]
- rescue => detail
- Puppet.warning "Could not reload downloaded file %s: %s" %
- [resource[: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 %s %s" %
- [type, ::File.basename(file.sub(".rb",''))]
- Timeout::timeout(self.timeout) do
- load fqfile
- end
- rescue => detail
- Puppet.warning "Could not load %s %s: %s" % [type, fqfile, detail]
- end
- end
- end
-
- def self.loadfacts
- # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com]
- x = 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
-
- return timeout
- end
-
- loadfacts()
-
- # Actually retrieve the catalog, either from the server or from a
- # local master.
- def get_actual_config(facts)
- 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
-
- # Retrieve a config from a remote master.
- def get_remote_config(facts)
- textobjects = ""
-
- textfacts = CGI.escape(YAML.dump(facts))
-
- benchmark(:debug, "Retrieved catalog") do
- # error handling for this is done in the network client
- begin
- textobjects = @driver.getconfig(textfacts, Puppet[:catalog_format])
- begin
- textobjects = CGI.unescape(textobjects)
- rescue => detail
- raise Puppet::Error, "Could not CGI.unescape catalog"
- end
-
- rescue => detail
- Puppet.err "Could not retrieve catalog: %s" % detail
- return nil
- end
- end
-
- return nil if textobjects == ""
-
- @compile_time = Time.now
- Puppet::Util::Storage.cache(:configuration)[:facts] = facts
- Puppet::Util::Storage.cache(:configuration)[:compile_time] = @compile_time
-
- return textobjects
- end
-
- def lockfile
- unless defined?(@lockfile)
- @lockfile = Puppet::Util::Pidlock.new(Puppet[:puppetdlockfile])
- end
-
- @lockfile
- end
-
- def splayed?
- @splayed
- end
-
- # Sleep when splay is enabled; else just return.
- def splay
- return unless Puppet[:splay]
- return if splayed?
-
- time = rand(Integer(Puppet[:splaylimit]) + 1)
- Puppet.info "Sleeping for %s seconds (splay is enabled)" % time
- sleep(time)
- @splayed = true
- end
-
- private
-
- # Use our cached config, optionally specifying whether this is
- # necessary because of a failure.
- def use_cached_config(because_of_failure = false)
- return true if self.catalog
-
- if because_of_failure and ! Puppet[:usecacheonfailure]
- @catalog = nil
- Puppet.warning "Not using cache on failed catalog"
- return false
- end
-
- return false unless oldtext = self.retrievecache
-
- begin
- @catalog = YAML.load(oldtext).to_catalog
- @catalog.from_cache = true
- @catalog.host_config = true
- rescue => detail
- puts detail.backtrace if Puppet[:trace]
- Puppet.warning "Could not load cached catalog: %s" % detail
- clear
- return false
- end
- return true
- end
-end
diff --git a/lib/puppet/network/format.rb b/lib/puppet/network/format.rb
new file mode 100644
index 000000000..21aead7cc
--- /dev/null
+++ b/lib/puppet/network/format.rb
@@ -0,0 +1,79 @@
+require 'puppet/provider/confiner'
+
+# A simple class for modeling encoding formats for moving
+# instances around the network.
+class Puppet::Network::Format
+ include Puppet::Provider::Confiner
+
+ attr_reader :name, :mime, :weight
+
+ def initialize(name, options = {}, &block)
+ @name = name.to_s.downcase.intern
+
+ if mime = options[:mime]
+ self.mime = mime
+ options.delete(:mime)
+ else
+ self.mime = "text/%s" % name
+ end
+
+ if weight = options[:weight]
+ @weight = weight
+ options.delete(:weight)
+ else
+ @weight = 5
+ end
+
+ unless options.empty?
+ raise ArgumentError, "Unsupported option(s) %s" % options.keys
+ end
+
+ instance_eval(&block) if block_given?
+
+ @intern_method = "from_%s" % name
+ @render_method = "to_%s" % name
+ @intern_multiple_method = "from_multiple_%s" % name
+ @render_multiple_method = "to_multiple_%s" % name
+ end
+
+ def intern(klass, text)
+ return klass.send(intern_method, text) if klass.respond_to?(intern_method)
+ raise NotImplementedError, "%s can not intern instances from %s" % [klass, mime]
+ end
+
+ def intern_multiple(klass, text)
+ return klass.send(intern_multiple_method, text) if klass.respond_to?(intern_multiple_method)
+ raise NotImplementedError, "%s can not intern multiple instances from %s" % [klass, mime]
+ end
+
+ def mime=(mime)
+ @mime = mime.to_s.downcase
+ end
+
+ def render(instance)
+ return instance.send(render_method) if instance.respond_to?(render_method)
+ raise NotImplementedError, "%s can not render instances to %s" % [instance.class, mime]
+ end
+
+ def render_multiple(instances)
+ # This method implicitly assumes that all instances are of the same type.
+ return instances[0].class.send(render_multiple_method, instances) if instances[0].class.respond_to?(render_multiple_method)
+ raise NotImplementedError, "%s can not intern multiple instances to %s" % [instances[0].class, mime]
+ end
+
+ def supported?(klass)
+ suitable? and
+ klass.respond_to?(intern_method) and
+ klass.respond_to?(intern_multiple_method) and
+ klass.respond_to?(render_multiple_method) and
+ klass.instance_methods.include?(render_method)
+ end
+
+ def to_s
+ "Puppet::Network::Format[%s]" % name
+ end
+
+ private
+
+ attr_reader :intern_method, :render_method, :intern_multiple_method, :render_multiple_method
+end
diff --git a/lib/puppet/network/format_handler.rb b/lib/puppet/network/format_handler.rb
new file mode 100644
index 000000000..efeea79e3
--- /dev/null
+++ b/lib/puppet/network/format_handler.rb
@@ -0,0 +1,119 @@
+require 'yaml'
+require 'puppet/network'
+require 'puppet/network/format'
+
+module Puppet::Network::FormatHandler
+ class FormatError < Puppet::Error; end
+
+ class FormatProtector
+ attr_reader :format
+
+ def protect(method, args)
+ begin
+ Puppet::Network::FormatHandler.format(format).send(method, *args)
+ rescue => details
+ direction = method.to_s.include?("intern") ? "from" : "to"
+ error = FormatError.new("Could not %s %s %s: %s" % [method, direction, format, details])
+ error.set_backtrace(details.backtrace)
+ raise error
+ end
+ end
+
+ def initialize(format)
+ @format = format
+ end
+
+ [:intern, :intern_multiple, :render, :render_multiple].each do |method|
+ define_method(method) do |*args|
+ protect(method, args)
+ end
+ end
+ end
+
+ @formats = {}
+ def self.create(*args, &block)
+ instance = Puppet::Network::Format.new(*args)
+ instance.instance_eval(&block) if block_given?
+
+ @formats[instance.name] = instance
+ instance
+ end
+
+ def self.extended(klass)
+ klass.extend(ClassMethods)
+
+ # LAK:NOTE This won't work in 1.9 ('send' won't be able to send
+ # private methods, but I don't know how else to do it.
+ klass.send(:include, InstanceMethods)
+ end
+
+ def self.format(name)
+ @formats[name.to_s.downcase.intern]
+ end
+
+ # Provide a list of all formats.
+ def self.formats
+ @formats.keys
+ end
+
+ # Return a format capable of handling the provided mime type.
+ def self.mime(mimetype)
+ mimetype = mimetype.to_s.downcase
+ @formats.values.find { |format| format.mime == mimetype }
+ end
+
+ # Use a delegator to make sure any exceptions generated by our formats are
+ # handled intelligently.
+ def self.protected_format(name)
+ @format_protectors ||= {}
+ @format_protectors[name] ||= FormatProtector.new(name)
+ @format_protectors[name]
+ end
+
+ module ClassMethods
+ def format_handler
+ Puppet::Network::FormatHandler
+ end
+
+ def convert_from(format, data)
+ format_handler.protected_format(format).intern(self, data)
+ end
+
+ def convert_from_multiple(format, data)
+ format_handler.protected_format(format).intern_multiple(self, data)
+ end
+
+ def render_multiple(format, instances)
+ format_handler.protected_format(format).render_multiple(instances)
+ end
+
+ def default_format
+ supported_formats[0]
+ end
+
+ def support_format?(name)
+ Puppet::Network::FormatHandler.format(name).supported?(self)
+ end
+
+ def supported_formats
+ format_handler.formats.collect { |f| format_handler.format(f) }.find_all { |f| f.supported?(self) }.collect { |f| f.name }.sort do |a, b|
+ # It's an inverse sort -- higher weight formats go first.
+ format_handler.format(b).weight <=> format_handler.format(a).weight
+ end
+ end
+ end
+
+ module InstanceMethods
+ def render(format = nil)
+ format ||= self.class.default_format
+
+ Puppet::Network::FormatHandler.protected_format(format).render(self)
+ end
+
+ def support_format?(name)
+ self.class.support_format?(name)
+ end
+ end
+end
+
+require 'puppet/network/formats'
diff --git a/lib/puppet/network/formats.rb b/lib/puppet/network/formats.rb
new file mode 100644
index 000000000..85e8ce6f8
--- /dev/null
+++ b/lib/puppet/network/formats.rb
@@ -0,0 +1,77 @@
+require 'puppet/network/format_handler'
+
+Puppet::Network::FormatHandler.create(:yaml, :mime => "text/yaml") do
+ # Yaml doesn't need the class name; it's serialized.
+ def intern(klass, text)
+ YAML.load(text)
+ end
+
+ # Yaml doesn't need the class name; it's serialized.
+ def intern_multiple(klass, text)
+ YAML.load(text)
+ end
+
+ def render(instance)
+ instance.to_yaml
+ end
+
+ # Yaml monkey-patches Array, so this works.
+ def render_multiple(instances)
+ instances.to_yaml
+ end
+
+ # Everything's supported
+ def supported?(klass)
+ true
+ end
+end
+
+
+Puppet::Network::FormatHandler.create(:marshal, :mime => "text/marshal") do
+ # Marshal doesn't need the class name; it's serialized.
+ def intern(klass, text)
+ Marshal.load(text)
+ end
+
+ # Marshal doesn't need the class name; it's serialized.
+ def intern_multiple(klass, text)
+ Marshal.load(text)
+ end
+
+ def render(instance)
+ Marshal.dump(instance)
+ end
+
+ # Marshal monkey-patches Array, so this works.
+ def render_multiple(instances)
+ Marshal.dump(instances)
+ end
+
+ # Everything's supported
+ def supported?(klass)
+ true
+ end
+end
+
+Puppet::Network::FormatHandler.create(:s, :mime => "text/plain")
+
+# A very low-weight format so it'll never get chosen automatically.
+Puppet::Network::FormatHandler.create(:raw, :mime => "application/x-raw", :weight => 1) do
+ def intern_multiple(klass, text)
+ raise NotImplementedError
+ end
+
+ def render_multiple(instances)
+ raise NotImplementedError
+ end
+
+ # LAK:NOTE The format system isn't currently flexible enough to handle
+ # what I need to support raw formats just for individual instances (rather
+ # than both individual and collections), but we don't yet have enough data
+ # to make a "correct" design.
+ # So, we hack it so it works for singular but fail if someone tries it
+ # on plurals.
+ def supported?(klass)
+ true
+ end
+end
diff --git a/lib/puppet/network/handler/fileserver.rb b/lib/puppet/network/handler/fileserver.rb
index 815d0ba82..4e00b605f 100755
--- a/lib/puppet/network/handler/fileserver.rb
+++ b/lib/puppet/network/handler/fileserver.rb
@@ -77,7 +77,7 @@ class Puppet::Network::Handler
return "" unless metadata.exist?
begin
- metadata.collect_attributes
+ metadata.collect
rescue => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err detail
@@ -498,22 +498,25 @@ class Puppet::Network::Handler
@path = nil
end
+ @files = {}
+
super()
end
def fileobj(path, links, client)
obj = nil
- if obj = Puppet.type(:file)[file_path(path, client)]
+ if obj = @files[file_path(path, client)]
# 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(
+ obj = Puppet::Type.type(:file).new(
:name => file_path(path, client),
:check => CHECKPARAMS
)
+ @files[file_path(path, client)] = obj
end
if links == :manage
@@ -530,7 +533,7 @@ class Puppet::Network::Handler
# Read the contents of the file at the relative path given.
def read_file(relpath, client)
- File.read(file_path(relpath, client))
+ File.read(file_path(relpath, client))
end
# Cache this manufactured map, since if it's used it's likely
diff --git a/lib/puppet/network/handler/master.rb b/lib/puppet/network/handler/master.rb
index 71b633a09..7bde0af73 100644
--- a/lib/puppet/network/handler/master.rb
+++ b/lib/puppet/network/handler/master.rb
@@ -62,7 +62,7 @@ class Puppet::Network::Handler
# Pass the facts to the fact handler
Puppet::Node::Facts.new(client, facts).save unless local?
- catalog = Puppet::Node::Catalog.find(client)
+ catalog = Puppet::Resource::Catalog.find(client)
case format
when "yaml":
diff --git a/lib/puppet/network/handler/resource.rb b/lib/puppet/network/handler/resource.rb
index f2a339751..e7ecbbdf2 100755
--- a/lib/puppet/network/handler/resource.rb
+++ b/lib/puppet/network/handler/resource.rb
@@ -59,7 +59,7 @@ class Puppet::Network::Handler
Puppet.info "Describing %s[%s]" % [type.to_s.capitalize, name]
@local = true unless client
typeklass = nil
- unless typeklass = Puppet.type(type)
+ unless typeklass = Puppet::Type.type(type)
raise Puppet::Error, "Puppet type %s is unsupported" % type
end
@@ -68,15 +68,11 @@ class Puppet::Network::Handler
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
+ 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
unless obj
@@ -128,7 +124,7 @@ class Puppet::Network::Handler
def list(type, ignore = [], base = nil, format = "yaml", client = nil, clientip = nil)
@local = true unless client
typeklass = nil
- unless typeklass = Puppet.type(type)
+ unless typeklass = Puppet::Type.type(type)
raise Puppet::Error, "Puppet type %s is unsupported" % type
end
diff --git a/lib/puppet/network/handler/runner.rb b/lib/puppet/network/handler/runner.rb
index c97e4791a..070cae114 100755
--- a/lib/puppet/network/handler/runner.rb
+++ b/lib/puppet/network/handler/runner.rb
@@ -1,3 +1,5 @@
+require 'puppet/agent/runner'
+
class Puppet::Network::Handler
class MissingMasterError < RuntimeError; end # Cannot find the master client
# A simple server for triggering a new run on a Puppet client.
@@ -13,51 +15,16 @@ class Puppet::Network::Handler
# 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.client(:Master).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 += " ignoring schedules"
- end
+ options = {}
+ options[:tags] = tags if tags
+ options[:ignoreschedules] = ignoreschedules if ignoreschedules
+ options[:background] = !fg
- Puppet.notice msg
+ runner = Puppet::Agent::Runner.new(options)
- # And then we need to tell it to run, with this extra info.
- if fg
- master.run(:tags => tags, :ignoreschedules => ignoreschedules)
- else
- Puppet.newthread do
- master.run(:tags => tags, :ignoreschedules => ignoreschedules)
- end
- end
+ runner.run
- return "success"
+ return runner.status
end
end
end
diff --git a/lib/puppet/network/http.rb b/lib/puppet/network/http.rb
index c219859b6..3b81d38b5 100644
--- a/lib/puppet/network/http.rb
+++ b/lib/puppet/network/http.rb
@@ -1,4 +1,4 @@
-class Puppet::Network::HTTP
+module Puppet::Network::HTTP
def self.server_class_by_type(kind)
case kind.to_sym
when :webrick:
diff --git a/lib/puppet/network/http/handler.rb b/lib/puppet/network/http/handler.rb
index 3c14c8a40..7c7abccf5 100644
--- a/lib/puppet/network/http/handler.rb
+++ b/lib/puppet/network/http/handler.rb
@@ -1,4 +1,30 @@
+module Puppet::Network::HTTP
+end
+
module Puppet::Network::HTTP::Handler
+ attr_reader :model, :server, :handler
+
+ # Retrieve the accept header from the http request.
+ def accept_header(request)
+ raise NotImplementedError
+ end
+
+ # Which format to use when serializing our response. Just picks
+ # the first value in the accept header, at this point.
+ def format_to_use(request)
+ unless header = accept_header(request)
+ raise ArgumentError, "An Accept header must be provided to pick the right format"
+ end
+
+ format = nil
+ header.split(/,\s*/).each do |name|
+ next unless format = Puppet::Network::FormatHandler.format(name)
+ next unless format.suitable?
+ return name
+ end
+
+ raise "No specified acceptable formats (%s) are functional on this machine" % header
+ end
def initialize_for_puppet(args = {})
raise ArgumentError unless @server = args[:server]
@@ -14,54 +40,108 @@ module Puppet::Network::HTTP::Handler
return do_save(request, response) if put?(request) and singular?(request)
raise ArgumentError, "Did not understand HTTP #{http_method(request)} request for '#{path(request)}'"
rescue Exception => e
- return do_exception(request, response, e)
+ return do_exception(response, e)
end
- private
+ # Are we interacting with a singular instance?
+ def singular?(request)
+ %r{/#{handler.to_s}$}.match(path(request))
+ end
- def model
- @model
+ # Are we interacting with multiple instances?
+ def plural?(request)
+ %r{/#{handler.to_s}s$}.match(path(request))
+ end
+
+ # Set the response up, with the body and status.
+ def set_response(response, body, status = 200)
+ raise NotImplementedError
+ end
+
+ # Set the specified format as the content type of the response.
+ def set_content_type(response, format)
+ raise NotImplementedError
+ end
+
+ def do_exception(response, exception, status=400)
+ if exception.is_a?(Exception)
+ puts exception.backtrace if Puppet[:trace]
+ puts exception if Puppet[:trace]
+ end
+ set_content_type(response, "text/plain")
+ set_response(response, exception.to_s, status)
end
+ # Execute our find.
def do_find(request, response)
key = request_key(request) || raise(ArgumentError, "Could not locate lookup key in request path [#{path(request)}]")
args = params(request)
- result = model.find(key, args).to_yaml
- encode_result(request, response, result)
+ unless result = model.find(key, args)
+ return do_exception(response, "Could not find %s %s" % [model.name, key], 404)
+ end
+
+ # The encoding of the result must include the format to use,
+ # and it needs to be used for both the rendering and as
+ # the content type.
+ format = format_to_use(request)
+ set_content_type(response, format)
+
+ set_response(response, result.render(format))
end
+ # Execute our search.
def do_search(request, response)
args = params(request)
- result = model.search(args).collect {|result| result.to_yaml }.to_yaml
- encode_result(request, response, result)
+ if key = request_key(request)
+ result = model.search(key, args)
+ else
+ result = model.search(args)
+ end
+ if result.nil? or (result.is_a?(Array) and result.empty?)
+ return do_exception(response, "Could not find instances in %s with '%s'" % [model.name, args.inspect], 404)
+ end
+
+ format = format_to_use(request)
+ set_content_type(response, format)
+
+ set_response(response, model.render_multiple(format, result))
end
+ # Execute our destroy.
def do_destroy(request, response)
key = request_key(request) || raise(ArgumentError, "Could not locate lookup key in request path [#{path(request)}]")
args = params(request)
result = model.destroy(key, args)
- encode_result(request, response, YAML.dump(result))
+
+ set_content_type(response, "yaml")
+
+ set_response(response, result.to_yaml)
end
+ # Execute our save.
def do_save(request, response)
data = body(request).to_s
raise ArgumentError, "No data to save" if !data or data.empty?
args = params(request)
- obj = model.from_yaml(data)
- result = save_object(obj, args).to_yaml
- encode_result(request, response, result)
+
+ format = format_to_use(request)
+
+ obj = model.convert_from(format_to_use(request), data)
+ result = save_object(obj, args)
+
+ set_content_type(response, "yaml")
+
+ set_response(response, result.to_yaml)
end
+ private
+
# LAK:NOTE This has to be here for testing; it's a stub-point so
# we keep infinite recursion from happening.
def save_object(object, args)
object.save(args)
end
- def do_exception(request, response, exception, status=404)
- encode_result(request, response, exception.to_yaml, status)
- end
-
def find_model_for_handler(handler)
Puppet::Indirector::Indirection.model(handler) ||
raise(ArgumentError, "Cannot locate indirection [#{handler}].")
@@ -79,20 +159,8 @@ module Puppet::Network::HTTP::Handler
http_method(request) == 'DELETE'
end
- def singular?(request)
- %r{/#{@handler.to_s}$}.match(path(request))
- end
-
- def plural?(request)
- %r{/#{@handler.to_s}s$}.match(path(request))
- end
-
# methods to be overridden by the including web server class
- def register_handler
- raise NotImplementedError
- end
-
def http_method(request)
raise NotImplementedError
end
@@ -112,8 +180,4 @@ module Puppet::Network::HTTP::Handler
def params(request)
raise NotImplementedError
end
-
- def encode_result(request, response, result, status = 200)
- raise NotImplementedError
- end
end
diff --git a/lib/puppet/network/http/mongrel.rb b/lib/puppet/network/http/mongrel.rb
index 9a4531c7a..847781cf2 100644
--- a/lib/puppet/network/http/mongrel.rb
+++ b/lib/puppet/network/http/mongrel.rb
@@ -16,6 +16,7 @@ class Puppet::Network::HTTP::Mongrel
@protocols = args[:protocols]
@handlers = args[:handlers]
+ @xmlrpc_handlers = args[:xmlrpc_handlers]
@server = Mongrel::HttpServer.new(args[:address], args[:port])
setup_handlers
@@ -38,12 +39,22 @@ class Puppet::Network::HTTP::Mongrel
def setup_handlers
@protocols.each do |protocol|
+ next if protocol == :xmlrpc
klass = class_for_protocol(protocol)
@handlers.each do |handler|
@server.register('/' + handler.to_s, klass.new(:server => @server, :handler => handler))
@server.register('/' + handler.to_s + 's', klass.new(:server => @server, :handler => handler))
end
end
+
+ if @protocols.include?(:xmlrpc) and ! @xmlrpc_handlers.empty?
+ setup_xmlrpc_handlers
+ end
+ end
+
+ # Use our existing code to provide the xmlrpc backward compatibility.
+ def setup_xmlrpc_handlers
+ @server.register('/RPC2', Puppet::Network::HTTPServer::Mongrel.new(@xmlrpc_handlers))
end
def class_for_protocol(protocol)
diff --git a/lib/puppet/network/http/mongrel/rest.rb b/lib/puppet/network/http/mongrel/rest.rb
index 520ad67f0..45d21ea62 100644
--- a/lib/puppet/network/http/mongrel/rest.rb
+++ b/lib/puppet/network/http/mongrel/rest.rb
@@ -4,24 +4,28 @@ class Puppet::Network::HTTP::MongrelREST < Mongrel::HttpHandler
include Puppet::Network::HTTP::Handler
+ ACCEPT_HEADER = "HTTP_ACCEPT".freeze # yay, zed's a crazy-man
+
def initialize(args={})
super()
initialize_for_puppet(args)
end
- # Return the query params for this request. We had to expose this method for
- # testing purposes.
- def params(request)
- Mongrel::HttpRequest.query_parse(request.params["QUERY_STRING"]).merge(client_info(request))
+ def accept_header(request)
+ request.params[ACCEPT_HEADER]
end
- private
-
# which HTTP verb was used in this request
def http_method(request)
request.params[Mongrel::Const::REQUEST_METHOD]
end
+ # Return the query params for this request. We had to expose this method for
+ # testing purposes.
+ def params(request)
+ Mongrel::HttpRequest.query_parse(request.params["QUERY_STRING"]).merge(client_info(request))
+ end
+
# what path was requested?
def path(request)
# LAK:NOTE See http://snurl.com/21zf8 [groups_google_com]
@@ -31,7 +35,7 @@ class Puppet::Network::HTTP::MongrelREST < Mongrel::HttpHandler
# return the key included in the request path
def request_key(request)
# LAK:NOTE See http://snurl.com/21zf8 [groups_google_com]
- x = request.params[Mongrel::Const::REQUEST_PATH].split('/')[2]
+ x = request.params[Mongrel::Const::REQUEST_PATH].split('/', 3)[2]
end
# return the request body
@@ -39,9 +43,21 @@ class Puppet::Network::HTTP::MongrelREST < Mongrel::HttpHandler
request.body
end
+ def set_content_type(response, format)
+ response.header['Content-Type'] = format
+ end
+
# produce the body of the response
- def encode_result(request, response, result, status = 200)
- response.start(status) do |head, body|
+ def set_response(response, result, status = 200)
+ args = [status]
+
+ # Set the 'reason' (or 'message', as it's called in Webrick), when
+ # we have a failure.
+ if status >= 300
+ args << false << result
+ end
+
+ response.start(*args) do |head, body|
body.write(result)
end
end
diff --git a/lib/puppet/network/http/webrick.rb b/lib/puppet/network/http/webrick.rb
index 3a37e2071..972ebc2e2 100644
--- a/lib/puppet/network/http/webrick.rb
+++ b/lib/puppet/network/http/webrick.rb
@@ -1,8 +1,12 @@
require 'webrick'
require 'webrick/https'
require 'puppet/network/http/webrick/rest'
+require 'puppet/network/xmlrpc/webrick_servlet'
require 'thread'
+require 'puppet/ssl/certificate'
+require 'puppet/ssl/certificate_revocation_list'
+
class Puppet::Network::HTTP::WEBrick
def initialize(args = {})
@listening = false
@@ -22,7 +26,14 @@ class Puppet::Network::HTTP::WEBrick
@protocols = args[:protocols]
@handlers = args[:handlers]
- @server = WEBrick::HTTPServer.new(:BindAddress => args[:address], :Port => args[:port])
+ @xmlrpc_handlers = args[:xmlrpc_handlers]
+
+ arguments = {:BindAddress => args[:address], :Port => args[:port]}
+ arguments.merge!(setup_logger)
+ arguments.merge!(setup_ssl)
+
+ @server = WEBrick::HTTPServer.new(arguments)
+
setup_handlers
@mutex.synchronize do
@@ -48,15 +59,85 @@ class Puppet::Network::HTTP::WEBrick
end
end
+ # Configure our http log file.
+ def setup_logger
+ # Make sure the settings are all ready for us.
+ Puppet.settings.use(:main, :ssl, Puppet[:name])
+
+ if Puppet[:name] == "puppetmasterd"
+ file = Puppet[:masterhttplog]
+ else
+ file = Puppet[:httplog]
+ end
+
+ # open the log manually to prevent file descriptor leak
+ file_io = ::File.open(file, "a+")
+ file_io.sync
+ file_io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
+
+ args = [file_io]
+ args << WEBrick::Log::DEBUG if Puppet::Util::Log.level == :debug
+
+ logger = WEBrick::Log.new(*args)
+ return :Logger => logger, :AccessLog => [
+ [logger, WEBrick::AccessLog::COMMON_LOG_FORMAT ],
+ [logger, WEBrick::AccessLog::REFERER_LOG_FORMAT ]
+ ]
+ end
+
+ # Add all of the ssl cert information.
+ def setup_ssl
+ results = {}
+
+ # Get the cached copy. We know it's been generated, too.
+ host = Puppet::SSL::Host.localhost
+
+ raise Puppet::Error, "Could not retrieve certificate for %s and not running on a valid certificate authority" % host.name unless host.certificate
+
+ results[:SSLPrivateKey] = host.key.content
+ results[:SSLCertificate] = host.certificate.content
+ results[:SSLStartImmediately] = true
+ results[:SSLEnable] = true
+
+ unless Puppet::SSL::Certificate.find("ca")
+ raise Puppet::Error, "Could not find CA certificate"
+ end
+
+ results[:SSLCACertificateFile] = Puppet[:localcacert]
+ results[:SSLVerifyClient] = OpenSSL::SSL::VERIFY_PEER
+
+ results[:SSLCertificateStore] = host.ssl_store
+
+ results
+ end
+
private
def setup_handlers
+ # Set up the new-style protocols.
@protocols.each do |protocol|
+ next if protocol == :xmlrpc
klass = self.class.class_for_protocol(protocol)
@handlers.each do |handler|
@server.mount('/' + handler.to_s, klass, handler)
@server.mount('/' + handler.to_s + 's', klass, handler)
end
end
+
+ # And then set up xmlrpc, if configured.
+ if @protocols.include?(:xmlrpc) and ! @xmlrpc_handlers.empty?
+ @server.mount("/RPC2", xmlrpc_servlet)
+ end
+ end
+
+ # Create our xmlrpc servlet, which provides backward compatibility.
+ def xmlrpc_servlet
+ handlers = @xmlrpc_handlers.collect { |handler|
+ unless hclass = Puppet::Network::Handler.handler(handler)
+ raise "Invalid xmlrpc handler %s" % handler
+ end
+ hclass.new({})
+ }
+ Puppet::Network::XMLRPC::WEBrickServlet.new handlers
end
end
diff --git a/lib/puppet/network/http/webrick/rest.rb b/lib/puppet/network/http/webrick/rest.rb
index a235fb4f3..f06914365 100644
--- a/lib/puppet/network/http/webrick/rest.rb
+++ b/lib/puppet/network/http/webrick/rest.rb
@@ -10,7 +10,7 @@ class Puppet::Network::HTTP::WEBrickREST < WEBrick::HTTPServlet::AbstractServlet
initialize_for_puppet(:server => server, :handler => handler)
end
- # We had to expose this method for testing purposes.
+ # Retrieve the request parameters, including authentication information.
def params(request)
result = request.query
result.merge(client_information(request))
@@ -21,7 +21,9 @@ class Puppet::Network::HTTP::WEBrickREST < WEBrick::HTTPServlet::AbstractServlet
process(request, response)
end
- private
+ def accept_header(request)
+ request["accept"]
+ end
def http_method(request)
request.request_method
@@ -34,16 +36,25 @@ class Puppet::Network::HTTP::WEBrickREST < WEBrick::HTTPServlet::AbstractServlet
def request_key(request)
# LAK:NOTE See http://snurl.com/21zf8 [groups_google_com]
- x = request.path.split('/')[2]
+ x = request.path.split('/', 3)[2]
end
def body(request)
request.body
end
- def encode_result(request, response, result, status = 200)
+ # Set the specified format as the content type of the response.
+ def set_content_type(response, format)
+ response["content-type"] = format
+ end
+
+ def set_response(response, result, status = 200)
response.status = status
- response.body = result
+ if status >= 200 and status < 300
+ response.body = result
+ else
+ response.reason_phrase = result
+ end
end
# Retrieve node/cert/ip information from the request object.
diff --git a/lib/puppet/network/http_pool.rb b/lib/puppet/network/http_pool.rb
index 1227f78dc..9430457bb 100644
--- a/lib/puppet/network/http_pool.rb
+++ b/lib/puppet/network/http_pool.rb
@@ -1,11 +1,24 @@
-require 'puppet/sslcertificates/support'
+require 'puppet/ssl/host'
require 'net/https'
+require 'puppet/util/cacher'
-module Puppet::Network
-end
+module Puppet::Network; end
# Manage Net::HTTP instances for keep-alive.
module Puppet::Network::HttpPool
+ class << self
+ include Puppet::Util::Cacher
+
+ private
+
+ cached_attr(:http_cache) { Hash.new }
+ end
+
+ # Use the global localhost instance.
+ def self.ssl_host
+ Puppet::SSL::Host.localhost
+ end
+
# 2008/03/23
# LAK:WARNING: Enabling this has a high propability of
# causing corrupt files and who knows what else. See #1010.
@@ -15,18 +28,12 @@ module Puppet::Network::HttpPool
HTTP_KEEP_ALIVE
end
- # This handles reading in the key and such-like.
- extend Puppet::SSLCertificates::Support
- @http_cache = {}
-
# Clear our http cache, closing all connections.
def self.clear_http_instances
- @http_cache.each do |name, connection|
+ http_cache.each do |name, connection|
connection.finish if connection.started?
end
- @http_cache.clear
- @cert = nil
- @key = nil
+ Puppet::Util::Cacher.expire
end
# Make sure we set the driver up when we read the cert in.
@@ -44,17 +51,13 @@ module Puppet::Network::HttpPool
# Use cert information from a Puppet client to set up the http object.
def self.cert_setup(http)
# Just no-op if we don't have certs.
- return false unless (defined?(@cert) and @cert) or self.read_cert
-
- store = OpenSSL::X509::Store.new
- store.add_file Puppet[:localcacert]
- store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
+ return false unless FileTest.exist?(Puppet[:hostcert]) # ssl_host.certificate
- http.cert_store = store
+ http.cert_store = ssl_host.ssl_store
http.ca_file = Puppet[:localcacert]
- http.cert = self.cert
+ http.cert = ssl_host.certificate.content
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
- http.key = self.key
+ http.key = ssl_host.key.content
end
# Retrieve a cached http instance of caching is enabled, else return
@@ -66,11 +69,11 @@ module Puppet::Network::HttpPool
# Return our cached instance if we've got a cache, as long as we're not
# resetting the instance.
if keep_alive?
- return @http_cache[key] if ! reset and @http_cache[key]
+ return http_cache[key] if ! reset and http_cache[key]
# Clean up old connections if we have them.
- if http = @http_cache[key]
- @http_cache.delete(key)
+ if http = http_cache[key]
+ http_cache.delete(key)
http.finish if http.started?
end
end
@@ -100,7 +103,7 @@ module Puppet::Network::HttpPool
cert_setup(http)
- @http_cache[key] = http if keep_alive?
+ http_cache[key] = http if keep_alive?
return http
end
diff --git a/lib/puppet/network/http_server/mongrel.rb b/lib/puppet/network/http_server/mongrel.rb
index 6b2325d29..924c11728 100644
--- a/lib/puppet/network/http_server/mongrel.rb
+++ b/lib/puppet/network/http_server/mongrel.rb
@@ -34,7 +34,6 @@ require 'puppet/network/xmlrpc/server'
require 'puppet/network/http_server'
require 'puppet/network/client_request'
require 'puppet/network/handler'
-require 'puppet/daemon'
require 'resolv'
@@ -51,7 +50,6 @@ require 'resolv'
# </pre>
module Puppet::Network
class HTTPServer::Mongrel < ::Mongrel::HttpHandler
- include Puppet::Daemon
attr_reader :xmlrpc_server
def initialize(handlers)
@@ -64,11 +62,11 @@ module Puppet::Network
# behaviour and we have to subclass Mongrel::HttpHandler so our handler
# works for Mongrel.
@xmlrpc_server = Puppet::Network::XMLRPCServer.new
- handlers.each do |name, args|
+ handlers.each do |name|
unless handler = Puppet::Network::Handler.handler(name)
raise ArgumentError, "Invalid handler %s" % name
end
- @xmlrpc_server.add_handler(handler.interface, handler.new(args))
+ @xmlrpc_server.add_handler(handler.interface, handler.new({}))
end
end
diff --git a/lib/puppet/network/http_server/webrick.rb b/lib/puppet/network/http_server/webrick.rb
index 568b4e798..0e835d057 100644
--- a/lib/puppet/network/http_server/webrick.rb
+++ b/lib/puppet/network/http_server/webrick.rb
@@ -1,5 +1,4 @@
require 'puppet'
-require 'puppet/daemon'
require 'webrick'
require 'webrick/https'
require 'fcntl'
@@ -16,7 +15,6 @@ module Puppet
# The old-school, pure ruby webrick server, which is the default serving
# mechanism.
class HTTPServer::WEBrick < WEBrick::HTTPServer
- include Puppet::Daemon
include Puppet::SSLCertificates::Support
# Read the CA cert and CRL and populate an OpenSSL::X509::Store
diff --git a/lib/puppet/network/server.rb b/lib/puppet/network/server.rb
index cab14519b..de32db02f 100644
--- a/lib/puppet/network/server.rb
+++ b/lib/puppet/network/server.rb
@@ -1,59 +1,153 @@
require 'puppet/network/http'
+require 'puppet/util/pidlock'
class Puppet::Network::Server
- attr_reader :server_type, :protocols, :address, :port
+ attr_reader :server_type, :protocols, :address, :port
+
+ # Put the daemon into the background.
+ def daemonize
+ if pid = fork()
+ Process.detach(pid)
+ exit(0)
+ end
+
+ # Get rid of console logging
+ Puppet::Util::Log.close(:console)
+
+ Process.setsid
+ Dir.chdir("/")
+ begin
+ $stdin.reopen "/dev/null"
+ $stdout.reopen "/dev/null", "a"
+ $stderr.reopen $stdout
+ Puppet::Util::Log.reopen
+ rescue => detail
+ File.open("/tmp/daemonout", "w") { |f|
+ f.puts "Could not start %s: %s" % [Puppet[:name], detail]
+ }
+ raise "Could not start %s: %s" % [Puppet[:name], detail]
+ end
+ end
+
+ # Create a pidfile for our daemon, so we can be stopped and others
+ # don't try to start.
+ def create_pidfile
+ Puppet::Util.sync(Puppet[:name]).synchronize(Sync::EX) do
+ unless Puppet::Util::Pidlock.new(pidfile).lock
+ raise "Could not create PID file: %s" % [pidfile]
+ end
+ end
+ end
+
+ # Remove the pid file for our daemon.
+ def remove_pidfile
+ Puppet::Util.sync(Puppet[:name]).synchronize(Sync::EX) do
+ locker = Puppet::Util::Pidlock.new(pidfile)
+ if locker.locked?
+ locker.unlock or Puppet.err "Could not remove PID file %s" % [pidfile]
+ end
+ end
+ end
+
+ # Provide the path to our pidfile.
+ def pidfile
+ Puppet[:pidfile]
+ end
def initialize(args = {})
@server_type = Puppet[:servertype] or raise "No servertype configuration found." # e.g., WEBrick, Mongrel, etc.
http_server_class || raise(ArgumentError, "Could not determine HTTP Server class for server type [#{@server_type}]")
- @address = args[:address] || Puppet[:bindaddress] ||
- raise(ArgumentError, "Must specify :address or configure Puppet :bindaddress.")
- @port = args[:port] || Puppet[:masterport] ||
- raise(ArgumentError, "Must specify :port or configure Puppet :masterport")
- @protocols = [ :rest ]
+
+ @address = args[:address] || Puppet[:bindaddress] || raise(ArgumentError, "Must specify :address or configure Puppet :bindaddress.")
+ @port = args[:port] || Puppet[:masterport] || raise(ArgumentError, "Must specify :port or configure Puppet :masterport")
+
+ @protocols = [ :rest, :xmlrpc ]
@listening = false
@routes = {}
+ @xmlrpc_routes = {}
self.register(args[:handlers]) if args[:handlers]
+ self.register_xmlrpc(args[:xmlrpc_handlers]) if args[:xmlrpc_handlers]
+
+ # Make sure we have all of the directories we need to function.
+ Puppet.settings.use(:main, :ssl, Puppet[:name])
end
+ # Register handlers for REST networking, based on the Indirector.
def register(*indirections)
- raise ArgumentError, "Indirection names are required." if indirections.empty?
- indirections.flatten.each { |i| @routes[i.to_sym] = true }
+ raise ArgumentError, "Indirection names are required." if indirections.empty?
+ indirections.flatten.each do |name|
+ Puppet::Indirector::Indirection.model(name) || raise(ArgumentError, "Cannot locate indirection '#{name}'.")
+ @routes[name.to_sym] = true
+ end
end
+ # Unregister Indirector handlers.
def unregister(*indirections)
- raise "Cannot unregister indirections while server is listening." if listening?
- indirections = @routes.keys if indirections.empty?
+ raise "Cannot unregister indirections while server is listening." if listening?
+ indirections = @routes.keys if indirections.empty?
+
+ indirections.flatten.each do |i|
+ raise(ArgumentError, "Indirection [%s] is unknown." % i) unless @routes[i.to_sym]
+ end
+
+ indirections.flatten.each do |i|
+ @routes.delete(i.to_sym)
+ end
+ end
+
+ # Register xmlrpc handlers for backward compatibility.
+ def register_xmlrpc(*namespaces)
+ raise ArgumentError, "XMLRPC namespaces are required." if namespaces.empty?
+ namespaces.flatten.each do |name|
+ Puppet::Network::Handler.handler(name) || raise(ArgumentError, "Cannot locate XMLRPC handler for namespace '#{name}'.")
+ @xmlrpc_routes[name.to_sym] = true
+ end
+ end
+
+ # Unregister xmlrpc handlers.
+ def unregister_xmlrpc(*namespaces)
+ raise "Cannot unregister xmlrpc handlers while server is listening." if listening?
+ namespaces = @xmlrpc_routes.keys if namespaces.empty?
- indirections.flatten.each do |i|
- raise(ArgumentError, "Indirection [%s] is unknown." % i) unless @routes[i.to_sym]
- end
+ namespaces.flatten.each do |i|
+ raise(ArgumentError, "XMLRPC handler '%s' is unknown." % i) unless @xmlrpc_routes[i.to_sym]
+ end
- indirections.flatten.each do |i|
- @routes.delete(i.to_sym)
- end
+ namespaces.flatten.each do |i|
+ @xmlrpc_routes.delete(i.to_sym)
+ end
end
def listening?
- @listening
+ @listening
end
def listen
- raise "Cannot listen -- already listening." if listening?
- @listening = true
- http_server.listen(:address => address, :port => port, :handlers => @routes.keys, :protocols => protocols)
+ raise "Cannot listen -- already listening." if listening?
+ @listening = true
+ http_server.listen(:address => address, :port => port, :handlers => @routes.keys, :xmlrpc_handlers => @xmlrpc_routes.keys, :protocols => protocols)
end
def unlisten
- raise "Cannot unlisten -- not currently listening." unless listening?
- http_server.unlisten
- @listening = false
+ raise "Cannot unlisten -- not currently listening." unless listening?
+ http_server.unlisten
+ @listening = false
end
def http_server_class
http_server_class_by_type(@server_type)
end
+ def start
+ create_pidfile
+ listen
+ end
+
+ def stop
+ unlisten
+ remove_pidfile
+ end
+
private
def http_server
diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb
index e10299d87..74bf8902d 100644
--- a/lib/puppet/node.rb
+++ b/lib/puppet/node.rb
@@ -67,10 +67,14 @@ class Puppet::Node
# Merge the node facts with parameters from the node source.
def fact_merge
- if facts = Puppet::Node::Facts.find(name)
- merge(facts.values)
- else
- Puppet.warning "Could not find facts for %s; you probably have a discrepancy between the node and fact names" % name
+ begin
+ if facts = Puppet::Node::Facts.find(name)
+ merge(facts.values)
+ end
+ rescue => detail
+ error = Puppet::Error.new("Could not retrieve facts for %s: %s" % [name, detail])
+ error.set_backtrace(detail.backtrace)
+ raise error
end
end
@@ -79,6 +83,8 @@ class Puppet::Node
params.each do |name, value|
@parameters[name] = value unless @parameters.include?(name)
end
+
+ @parameters["environment"] ||= self.environment if self.environment
end
# Calculate the list of names we might use for looking
diff --git a/lib/puppet/node/facts.rb b/lib/puppet/node/facts.rb
index 8ee90b4ac..dca435c7d 100755
--- a/lib/puppet/node/facts.rb
+++ b/lib/puppet/node/facts.rb
@@ -21,6 +21,11 @@ class Puppet::Node::Facts
attr_accessor :name, :values
+ def add_local_facts
+ values["clientversion"] = Puppet.version.to_s
+ values["environment"] ||= Puppet.settings[:environment]
+ end
+
def initialize(name, values = {})
@name = name
@values = values
@@ -28,6 +33,22 @@ class Puppet::Node::Facts
add_internal
end
+ def downcase_if_necessary
+ return unless Puppet.settings[:downcasefacts]
+
+ Puppet.warning "DEPRECATION NOTICE: Fact downcasing is deprecated; please disable (20080122)"
+ values.each do |fact, value|
+ values[fact] = value.downcase if value.is_a?(String)
+ end
+ end
+
+ # Convert all fact values into strings.
+ def stringify
+ values.each do |fact, value|
+ values[fact] = value.to_s
+ end
+ end
+
private
# Add internal data to the facts for storage.
diff --git a/lib/puppet/parameter.rb b/lib/puppet/parameter.rb
index 06dfe5b91..04b3aec30 100644
--- a/lib/puppet/parameter.rb
+++ b/lib/puppet/parameter.rb
@@ -2,6 +2,7 @@ require 'puppet/util/methodhelper'
require 'puppet/util/log_paths'
require 'puppet/util/logging'
require 'puppet/util/docs'
+require 'puppet/util/cacher'
class Puppet::Parameter
include Puppet::Util
@@ -9,10 +10,223 @@ class Puppet::Parameter
include Puppet::Util::LogPaths
include Puppet::Util::Logging
include Puppet::Util::MethodHelper
+ include Puppet::Util::Cacher
+
+ # A collection of values and regexes, used for specifying
+ # what values are allowed in a given parameter.
+ class ValueCollection
+ class Value
+ attr_reader :name, :options, :event
+ attr_accessor :block, :call, :method, :required_features
+
+ # Add an alias for this value.
+ def alias(name)
+ @aliases << convert(name)
+ end
+
+ # Return all aliases.
+ def aliases
+ @aliases.dup
+ end
+
+ # Store the event that our value generates, if it does so.
+ def event=(value)
+ @event = convert(value)
+ end
+
+ def initialize(name)
+ if name.is_a?(Regexp)
+ @name = name
+ else
+ # Convert to a string and then a symbol, so things like true/false
+ # still show up as symbols.
+ @name = convert(name)
+ end
+
+ @aliases = []
+
+ @call = :instead
+ end
+
+ # Does a provided value match our value?
+ def match?(value)
+ if regex?
+ return true if name =~ value.to_s
+ else
+ return true if name == convert(value)
+ return @aliases.include?(convert(value))
+ end
+ end
+
+ # Is our value a regex?
+ def regex?
+ @name.is_a?(Regexp)
+ end
+
+ private
+
+ # A standard way of converting all of our values, so we're always
+ # comparing apples to apples.
+ def convert(value)
+ if value == ''
+ # We can't intern an empty string, yay.
+ value
+ else
+ value.to_s.to_sym
+ end
+ end
+ end
+
+ def aliasvalue(name, other)
+ other = other.to_sym
+ unless value = match?(other)
+ raise Puppet::DevError, "Cannot alias nonexistent value %s" % other
+ end
+
+ value.alias(name)
+ end
+
+ # Return a doc string for all of the values in this parameter/property.
+ def doc
+ unless defined?(@doc)
+ @doc = ""
+ unless values.empty?
+ @doc += " Valid values are "
+ @doc += @strings.collect do |value|
+ if aliases = value.aliases and ! aliases.empty?
+ "``%s`` (also called ``%s``)" % [value.name, aliases.join(", ")]
+ else
+ "``%s``" % value.name
+ end
+ end.join(", ") + "."
+ end
+
+ unless regexes.empty?
+ @doc += " Values can match ``" + regexes.join("``, ``") + "``."
+ end
+ end
+
+ @doc
+ end
+
+ # Does this collection contain any value definitions?
+ def empty?
+ @values.empty?
+ end
+
+ def initialize
+ # We often look values up by name, so a hash makes more sense.
+ @values = {}
+
+ # However, we want to retain the ability to match values in order,
+ # but we always prefer directly equality (i.e., strings) over regex matches.
+ @regexes = []
+ @strings = []
+ end
+
+ # Can we match a given value?
+ def match?(test_value)
+ # First look for normal values
+ if value = @strings.find { |v| v.match?(test_value) }
+ return value
+ end
+
+ # Then look for a regex match
+ @regexes.find { |v| v.match?(test_value) }
+ end
+
+ # If the specified value is allowed, then munge appropriately.
+ def munge(value)
+ return value if empty?
+
+ if instance = match?(value)
+ if instance.regex?
+ return value
+ else
+ return instance.name
+ end
+ else
+ return value
+ end
+ end
+
+ # Define a new valid value for a property. You must provide the value itself,
+ # usually as a symbol, or a regex to match the value.
+ #
+ # The first argument to the method is either the value itself or a regex.
+ # The second argument is an option hash; valid options are:
+ # * <tt>:event</tt>: The event that should be returned when this value is set.
+ # * <tt>:call</tt>: When to call any associated block. The default value
+ # is ``instead``, which means to call the value instead of calling the
+ # provider. You can also specify ``before`` or ``after``, which will
+ # call both the block and the provider, according to the order you specify
+ # (the ``first`` refers to when the block is called, not the provider).
+ def newvalue(name, options = {}, &block)
+ value = Value.new(name)
+ @values[value.name] = value
+ if value.regex?
+ @regexes << value
+ else
+ @strings << value
+ end
+
+ options.each { |opt, arg| value.send(opt.to_s + "=", arg) }
+ if block_given?
+ value.block = block
+ else
+ value.call = options[:call] || :none
+ end
+
+ if block_given? and ! value.regex?
+ value.method ||= "set_" + value.name.to_s
+ end
+
+ value
+ end
+
+ # Define one or more new values for our parameter.
+ def newvalues(*names)
+ names.each { |name| newvalue(name) }
+ end
+
+ def regexes
+ @regexes.collect { |r| r.name.inspect }
+ end
+
+ # Verify that the passed value is valid.
+ def validate(value)
+ return if empty?
+
+ unless @values.detect { |name, v| v.match?(value) }
+ str = "Invalid value %s. " % [value.inspect]
+
+ unless values.empty?
+ str += "Valid values are %s. " % values.join(", ")
+ end
+
+ unless regexes.empty?
+ str += "Valid values match %s." % regexes.join(", ")
+ end
+
+ raise ArgumentError, str
+ end
+ end
+
+ # Return a single value instance.
+ def value(name)
+ @values[name]
+ end
+
+ # Return the list of valid values.
+ def values
+ @strings.collect { |s| s.name }
+ end
+ end
+
class << self
include Puppet::Util
include Puppet::Util::Docs
- attr_reader :validater, :munger, :name, :default, :required_features
+ attr_reader :validater, :munger, :name, :default, :required_features, :value_collection
attr_accessor :metaparam
# Define the default value for a given parameter or parameter. This
@@ -36,36 +250,7 @@ class Puppet::Parameter
@doc ||= ""
unless defined? @addeddocvals
- unless values.empty?
- if @aliasvalues.empty?
- @doc += " Valid values are ``" +
- values.join("``, ``") + "``."
- else
- @doc += " Valid values are "
-
- @doc += values.collect do |value|
- ary = @aliasvalues.find do |name, val|
- val == value
- end
- if ary
- "``%s`` (also called ``%s``)" % [value, ary[0]]
- else
- "``#{value}``"
- end
- end.join(", ") + "."
- end
- end
-
- if defined? @parameterregexes and ! @parameterregexes.empty?
- regs = @parameterregexes
- if @parameterregexes.is_a? Hash
- regs = @parameterregexes.keys
- end
- unless regs.empty?
- @doc += " Values can also match ``" +
- regs.collect { |r| r.inspect }.join("``, ``") + "``."
- end
- end
+ @doc += value_collection.doc
if f = self.required_features
@doc += " Requires features %s." % f.flatten.collect { |f| f.to_s }.join(" ")
@@ -88,9 +273,7 @@ class Puppet::Parameter
end
def initvars
- @parametervalues = []
- @aliasvalues = {}
- @parameterregexes = []
+ @value_collection = ValueCollection.new
end
# This is how we munge the value. Basically, this is our
@@ -101,23 +284,6 @@ class Puppet::Parameter
# class's context, not the instance's, thus the two methods,
# instead of just one.
define_method(:unsafe_munge, &block)
-
- define_method(:munge) do |*args|
- begin
- ret = unsafe_munge(*args)
- rescue Puppet::Error => detail
- Puppet.debug "Reraising %s" % detail
- raise
- rescue => detail
- raise Puppet::DevError, "Munging failed for value %s in class %s: %s" %
- [args.inspect, self.name, detail], detail.backtrace
- end
-
- if self.shadow
- self.shadow.munge(*args)
- end
- ret
- end
end
# Mark whether we're the namevar.
@@ -140,6 +306,11 @@ class Puppet::Parameter
@required = true
end
+ # Specify features that are required for this parameter to work.
+ def required_features=(*args)
+ @required_features = args.flatten.collect { |a| a.to_s.downcase.intern }
+ end
+
# Is this parameter required? Defaults to false.
def required?
if defined? @required
@@ -151,88 +322,16 @@ class Puppet::Parameter
# Verify that we got a good value
def validate(&block)
- #@validater = block
define_method(:unsafe_validate, &block)
-
- define_method(:validate) do |*args|
- begin
- unsafe_validate(*args)
- rescue ArgumentError, Puppet::Error, TypeError
- raise
- rescue => detail
- raise Puppet::DevError,
- "Validate method failed for class %s: %s" %
- [self.name, detail], detail.backtrace
- end
- end
- end
-
- # Does the value match any of our regexes?
- def match?(value)
- value = value.to_s unless value.is_a? String
- @parameterregexes.find { |r|
- r = r[0] if r.is_a? Array # Properties use a hash here
- r =~ value
- }
end
# Define a new value for our parameter.
def newvalues(*names)
- names.each { |name|
- name = name.intern if name.is_a? String
-
- case name
- when Symbol
- if @parametervalues.include?(name)
- Puppet.warning "%s already has a value for %s" %
- [name, name]
- end
- @parametervalues << name
- when Regexp
- if @parameterregexes.include?(name)
- Puppet.warning "%s already has a value for %s" %
- [name, name]
- end
- @parameterregexes << name
- else
- raise ArgumentError, "Invalid value %s of type %s" %
- [name, name.class]
- end
- }
+ @value_collection.newvalues(*names)
end
def aliasvalue(name, other)
- other = symbolize(other)
- unless @parametervalues.include?(other)
- raise Puppet::DevError,
- "Cannot alias nonexistent value %s" % other
- end
-
- @aliasvalues[name] = other
- end
-
- def alias(name)
- @aliasvalues[name]
- end
-
- def regexes
- return @parameterregexes.dup
- end
-
- def required_features=(*args)
- @required_features = args.flatten.collect { |a| a.to_s.downcase.intern }
- end
-
- # Return the list of valid values.
- def values
- #[@aliasvalues.keys, @parametervalues.keys].flatten
- if @parametervalues.is_a? Array
- return @parametervalues.dup
- elsif @parametervalues.is_a? Hash
- return @parametervalues.keys
- else
- return []
- end
+ @value_collection.aliasvalue(name, other)
end
end
@@ -252,12 +351,15 @@ class Puppet::Parameter
attr_accessor :resource
# LAK 2007-05-09: Keep the @parent around for backward compatibility.
attr_accessor :parent
- attr_reader :shadow
def devfail(msg)
self.fail(Puppet::DevError, msg)
end
+ def expirer
+ resource.catalog
+ end
+
def fail(*args)
type = nil
if args[0].is_a?(Class)
@@ -289,17 +391,12 @@ class Puppet::Parameter
raise Puppet::DevError, "No resource set for %s" % self.class.name
end
- if ! self.metaparam? and klass = Puppet::Type.metaparamclass(self.class.name)
- setup_shadow(klass)
- end
-
set_options(options)
end
# Log a message using the resource's log level.
def log(msg)
unless @resource[:loglevel]
- p @resource
self.devfail "Parent %s has no loglevel" %
@resource.name
end
@@ -344,73 +441,45 @@ class Puppet::Parameter
end
# If the specified value is allowed, then munge appropriately.
- munge do |value|
- if self.class.values.empty? and self.class.regexes.empty?
- # This parameter isn't using defined values to do its work.
- return value
- end
-
- # We convert to a string and then a symbol so that things like
- # booleans work as we expect.
- intern = value.to_s.intern
-
- # If it's a valid value, always return it as a symbol.
- if self.class.values.include?(intern)
- retval = intern
- elsif other = self.class.alias(intern)
- retval = other
- elsif ary = self.class.match?(value)
- retval = value
- else
- # If it passed the validation but is not a registered value,
- # we just return it as is.
- retval = value
- end
+ # If the developer uses a 'munge' hook, this method will get overridden.
+ def unsafe_munge(value)
+ self.class.value_collection.munge(value)
+ end
- retval
+ # A wrapper around our munging that makes sure we raise useful exceptions.
+ def munge(value)
+ begin
+ ret = unsafe_munge(value)
+ rescue Puppet::Error => detail
+ Puppet.debug "Reraising %s" % detail
+ raise
+ rescue => detail
+ raise Puppet::DevError, "Munging failed for value %s in class %s: %s" % [value.inspect, self.name, detail], detail.backtrace
+ end
+ ret
end
# Verify that the passed value is valid.
- validate do |value|
- vals = self.class.values
- regs = self.class.regexes
-
- # this is true on properties
- regs = regs.keys if regs.is_a?(Hash)
-
- # This parameter isn't using defined values to do its work.
- return if vals.empty? and regs.empty?
-
- newval = value
- newval = value.to_s.intern unless value.is_a?(Symbol)
-
- name = newval
-
- unless vals.include?(newval) or name = self.class.alias(newval) or name = self.class.match?(value) # We match the string, not the symbol
- str = "Invalid '%s' value %s. " %
- [self.class.name, value.inspect]
-
- unless vals.empty?
- str += "Valid values are %s. " % vals.join(", ")
- end
-
- unless regs.empty?
- str += "Valid values match %s." % regs.collect { |r|
- r.to_s
- }.join(", ")
- end
+ # If the developer uses a 'validate' hook, this method will get overridden.
+ def unsafe_validate(value)
+ self.class.value_collection.validate(value)
+ end
- raise ArgumentError, str
+ # A protected validation method that only ever raises useful exceptions.
+ def validate(value)
+ begin
+ unsafe_validate(value)
+ rescue ArgumentError => detail
+ fail detail.to_s
+ rescue Puppet::Error, TypeError
+ raise
+ rescue => detail
+ raise Puppet::DevError, "Validate method failed for class %s: %s" % [self.name, detail], detail.backtrace
end
-
- # Now check for features.
- name = name[0] if name.is_a?(Array) # This is true for regexes.
- validate_features_per_value(name) if is_a?(Puppet::Property)
end
def remove
@resource = nil
- @shadow = nil
end
attr_reader :value
@@ -419,48 +488,18 @@ class Puppet::Parameter
# late-binding (e.g., users might not exist when the value is assigned
# but might when it is asked for).
def value=(value)
- if respond_to?(:validate)
- validate(value)
- end
+ validate(value)
- if respond_to?(:munge)
- value = munge(value)
- end
- @value = value
- end
-
- def inspect
- s = "Parameter(%s = %s" % [self.name, self.value || "nil"]
- if defined? @resource
- s += ", @resource = %s)" % @resource
- else
- s += ")"
- end
+ @value = munge(value)
end
# Retrieve the resource's provider. Some types don't have providers, in which
# case we return the resource object itself.
def provider
- @resource.provider || @resource
- end
-
- # If there's a shadowing metaparam, instantiate it now.
- # This allows us to create a property or parameter with the
- # same name as a metaparameter, and the metaparam will only be
- # stored as a shadow.
- def setup_shadow(klass)
- @shadow = klass.new(:resource => self.resource)
+ @resource.provider
end
def to_s
s = "Parameter(%s)" % self.name
end
-
- # Make sure that we've got all of the required features for a given value.
- def validate_features_per_value(value)
- if features = self.class.value_option(value, :required_features)
- raise ArgumentError, "Provider must have features '%s' to set '%s' to '%s'" % [features, self.class.name, value] unless provider.satisfies?(features)
- end
- end
end
-
diff --git a/lib/puppet/parser/ast/resource_defaults.rb b/lib/puppet/parser/ast/resource_defaults.rb
index 4919817fb..ed83d3573 100644
--- a/lib/puppet/parser/ast/resource_defaults.rb
+++ b/lib/puppet/parser/ast/resource_defaults.rb
@@ -12,7 +12,7 @@ class Puppet::Parser::AST
# object type.
def evaluate(scope)
# Use a resource reference to canonize the type
- ref = Puppet::ResourceReference.new(@type, "whatever")
+ ref = Puppet::Resource::Reference.new(@type, "whatever")
type = ref.type
params = @params.safeevaluate(scope)
diff --git a/lib/puppet/parser/collector.rb b/lib/puppet/parser/collector.rb
index bcba9528e..9423db26b 100644
--- a/lib/puppet/parser/collector.rb
+++ b/lib/puppet/parser/collector.rb
@@ -35,7 +35,7 @@ class Puppet::Parser::Collector
@scope = scope
# Canonize the type
- @type = Puppet::ResourceReference.new(type, "whatever").type
+ @type = Puppet::Resource::Reference.new(type, "whatever").type
@equery = equery
@vquery = vquery
diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/compiler.rb
index d67b3d275..7dcd50270 100644
--- a/lib/puppet/parser/compiler.rb
+++ b/lib/puppet/parser/compiler.rb
@@ -2,7 +2,7 @@
# Copyright (c) 2007. All rights reserved.
require 'puppet/node'
-require 'puppet/node/catalog'
+require 'puppet/resource/catalog'
require 'puppet/util/errors'
# Maintain a graph of scopes, along with a bunch of data
@@ -10,7 +10,7 @@ require 'puppet/util/errors'
class Puppet::Parser::Compiler
include Puppet::Util
include Puppet::Util::Errors
- attr_reader :parser, :node, :facts, :collections, :catalog, :node_scope
+ attr_reader :parser, :node, :facts, :collections, :catalog, :node_scope, :resources
# Add a collection to the global list.
def add_collection(coll)
@@ -31,6 +31,8 @@ class Puppet::Parser::Compiler
# Store a resource in our resource table.
def add_resource(scope, resource)
+ @resources << resource
+
# Note that this will fail if the resource is not unique.
@catalog.add_resource(resource)
@@ -204,11 +206,6 @@ class Puppet::Parser::Compiler
@resource_overrides[resource.ref]
end
- # Return a list of all resources.
- def resources
- @catalog.vertices
- end
-
# The top scope is usually the top-level scope, but if we're using AST nodes,
# then it is instead the node's scope.
def topscope
@@ -310,6 +307,7 @@ class Puppet::Parser::Compiler
@main_resource = Puppet::Parser::Resource.new(:type => "class", :title => :main, :scope => @topscope, :source => @main)
@topscope.resource = @main_resource
+ @resources << @main_resource
@catalog.add_resource(@main_resource)
@main_resource.evaluate
@@ -366,7 +364,7 @@ class Puppet::Parser::Compiler
# Make sure all of our resources and such have done any last work
# necessary.
def finish
- @catalog.vertices.each do |resource|
+ resources.each do |resource|
# Add in any resource overrides.
if overrides = resource_overrides(resource)
overrides.each do |over|
@@ -412,8 +410,11 @@ class Puppet::Parser::Compiler
@scope_graph = Puppet::SimpleGraph.new
# For maintaining the relationship between scopes and their resources.
- @catalog = Puppet::Node::Catalog.new(@node.name)
+ @catalog = Puppet::Resource::Catalog.new(@node.name)
@catalog.version = @parser.version
+
+ # local resource array to maintain resource ordering
+ @resources = []
end
# Set the node's parameters into the top-scope as variables.
@@ -436,7 +437,7 @@ class Puppet::Parser::Compiler
# We used to have hooks here for forking and saving, but I don't
# think it's worth retaining at this point.
- store_to_active_record(@node, @catalog.vertices)
+ store_to_active_record(@node, resources)
end
# Do the actual storage.
@@ -459,7 +460,7 @@ class Puppet::Parser::Compiler
# Return an array of all of the unevaluated resources. These will be definitions,
# which need to get evaluated into native resources.
def unevaluated_resources
- ary = @catalog.vertices.reject { |resource| resource.builtin? or resource.evaluated? }
+ ary = resources.reject { |resource| resource.builtin? or resource.evaluated? }
if ary.empty?
return nil
diff --git a/lib/puppet/parser/functions.rb b/lib/puppet/parser/functions.rb
index b1cd0d083..b9e49131c 100644
--- a/lib/puppet/parser/functions.rb
+++ b/lib/puppet/parser/functions.rb
@@ -92,7 +92,7 @@ module Functions
#ret += "%s\n%s\n" % [name, hash[:type]]
ret += "%s\n%s\n" % [name, "-" * name.to_s.length]
if hash[:doc]
- ret += hash[:doc].gsub(/\n\s*/, ' ')
+ ret += Puppet::Util::Docs.scrub(hash[:doc])
else
ret += "Undocumented.\n"
end
diff --git a/lib/puppet/parser/functions/versioncmp.rb b/lib/puppet/parser/functions/versioncmp.rb
new file mode 100644
index 000000000..62df38ffc
--- /dev/null
+++ b/lib/puppet/parser/functions/versioncmp.rb
@@ -0,0 +1,10 @@
+require 'puppet/util/package'
+
+Puppet::Parser::Functions::newfunction(:versioncmp, :doc => "Compares two versions.") do |args|
+
+ unless args.length == 2
+ raise Puppet::ParseError, "versioncmp should have 2 arguments"
+ end
+
+ return Puppet::Util::Package.versioncmp(args[0], args[1])
+end
diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb
index 423c34a4e..c728b54a2 100644
--- a/lib/puppet/parser/interpreter.rb
+++ b/lib/puppet/parser/interpreter.rb
@@ -26,10 +26,10 @@ class Puppet::Parser::Interpreter
def compile(node)
raise Puppet::ParseError, "Could not parse configuration; cannot compile on node %s" % node.name unless env_parser = parser(node.environment)
begin
- return Puppet::Parser::Compiler.new(node, env_parser).compile
+ return Puppet::Parser::Compiler.new(node, env_parser).compile.to_resource
rescue => detail
- puts detail.backtrace if Puppet[:trace]
- raise Puppet::Error, detail.to_s + " on node %s" % node.name
+ puts detail.backtrace if Puppet[:trace]
+ raise Puppet::Error, detail.to_s + " on node %s" % node.name
end
end
diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb
index 2fdd78ddf..d4c7a1f0f 100644
--- a/lib/puppet/parser/resource.rb
+++ b/lib/puppet/parser/resource.rb
@@ -176,7 +176,7 @@ class Puppet::Parser::Resource
db_resource.file = self.file
end
- updated_params = @params.inject({}) do |hash, ary|
+ updated_params = @params.reject { |name, param| param.value == :undef }.inject({}) do |hash, ary|
hash[ary[0].to_s] = ary[1]
hash
end
@@ -270,6 +270,7 @@ class Puppet::Parser::Resource
db_resource.save
@params.each { |name, param|
+ next if param.value == :undef
param.to_rails(db_resource)
}
@@ -278,69 +279,57 @@ class Puppet::Parser::Resource
return db_resource
end
- def to_s
- self.ref
- end
+ # Create a Puppet::Resource instance from this parser resource.
+ # We plan, at some point, on not needing to do this conversion, but
+ # it's sufficient for now.
+ def to_resource
+ result = Puppet::Resource.new(type, title)
- # Translate our object to a transportable object.
- def to_trans
- return nil if virtual?
-
- if builtin?
- to_transobject
- else
- to_transbucket
- end
- end
-
- def to_transbucket
- bucket = Puppet::TransBucket.new([])
-
- bucket.type = self.type
- bucket.name = self.title
-
- # TransBuckets don't support parameters, which is why they're being deprecated.
- return bucket
- end
-
- # Convert this resource to a RAL resource. We hackishly go via the
- # transportable stuff.
- def to_type
- to_trans.to_type
- end
-
- def to_transobject
- # Now convert to a transobject
- obj = Puppet::TransObject.new(@ref.title, @ref.type)
to_hash.each do |p, v|
- if v.is_a?(Reference)
- v = v.to_ref
+ if v.is_a?(Puppet::Parser::Resource::Reference)
+ v = Puppet::Resource::Reference.new(v.type, v.title)
elsif v.is_a?(Array)
- v = v.collect { |av|
- if av.is_a?(Reference)
- av = av.to_ref
+ v = v.collect do |av|
+ if av.is_a?(Puppet::Parser::Resource::Reference)
+ av = Puppet::Resource::Reference.new(av.type, av.title)
end
av
- }
+ end
end
# If the value is an array with only one value, then
# convert it to a single value. This is largely so that
# the database interaction doesn't have to worry about
# whether it returns an array or a string.
- obj[p.to_s] = if v.is_a?(Array) and v.length == 1
+ result[p] = if v.is_a?(Array) and v.length == 1
v[0]
else
v
end
end
- obj.file = self.file
- obj.line = self.line
+ result.file = self.file
+ result.line = self.line
+ result.tag(*self.tags)
+
+ return result
+ end
+
+ def to_s
+ self.ref
+ end
+
+ # Translate our object to a transportable object.
+ def to_trans
+ return nil if virtual?
- obj.tags = self.tags
+ return to_resource.to_trans
+ end
- return obj
+ # Convert this resource to a RAL resource. We hackishly go via the
+ # transportable stuff.
+ def to_ral
+ to_resource.to_ral
end
private
@@ -364,6 +353,7 @@ class Puppet::Parser::Resource
# LAK:NOTE Relationship metaparams get treated specially -- we stack them, instead of
# overriding.
next if @params[name] and not self.class.relationship_parameter?(name)
+ next if @params[name] and @params[name].value == :undef
# Skip metaparams for which we get no value.
next unless val = scope.lookupvar(name.to_s, false) and val != :undefined
diff --git a/lib/puppet/parser/resource/reference.rb b/lib/puppet/parser/resource/reference.rb
index cb505d606..e552b51fe 100644
--- a/lib/puppet/parser/resource/reference.rb
+++ b/lib/puppet/parser/resource/reference.rb
@@ -1,7 +1,7 @@
-require 'puppet/resource_reference'
+require 'puppet/resource/reference'
# A reference to a resource. Mostly just the type and title.
-class Puppet::Parser::Resource::Reference < Puppet::ResourceReference
+class Puppet::Parser::Resource::Reference < Puppet::Resource::Reference
include Puppet::Util::MethodHelper
include Puppet::Util::Errors
diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb
index 4acdf41c9..77e7b0cfd 100644
--- a/lib/puppet/parser/scope.rb
+++ b/lib/puppet/parser/scope.rb
@@ -52,11 +52,11 @@ class Puppet::Parser::Scope
if value.is_a?(String)
if value =~ /^-?\d+(:?\.\d+|(:?\.\d+)?e\d+)$/
return value.to_f
- elsif value =~ /^0x\d+/i
+ elsif value =~ /^0x[0-9a-f]+$/i
return value.to_i(16)
- elsif value =~ /^0\d+/i
+ elsif value =~ /^0[0-7]+$/
return value.to_i(8)
- elsif value =~ /^-?\d+/
+ elsif value =~ /^-?\d+$/
return value.to_i
else
return nil
diff --git a/lib/puppet/pgraph.rb b/lib/puppet/pgraph.rb
deleted file mode 100644
index 55ad7d2c1..000000000
--- a/lib/puppet/pgraph.rb
+++ /dev/null
@@ -1,121 +0,0 @@
-# Created by Luke A. Kanies on 2006-11-24.
-# Copyright (c) 2006. All rights reserved.
-
-require 'puppet/relationship'
-require 'puppet/simple_graph'
-
-# This class subclasses a graph class in order to handle relationships
-# among resources.
-class Puppet::PGraph < Puppet::SimpleGraph
- include Puppet::Util
-
- def add_edge(*args)
- @reversal = nil
- super
- end
-
- def add_vertex(*args)
- @reversal = nil
- super
- end
-
- # Which resources a given resource depends upon.
- def dependents(resource)
- tree_from_vertex(resource).keys
- end
-
- # Which resources depend upon the given resource.
- def dependencies(resource)
- # Cache the reversal graph, because it's somewhat expensive
- # to create.
- unless defined? @reversal and @reversal
- @reversal = reversal
- end
- # Strangely, it's significantly faster to search a reversed
- # tree in the :out direction than to search a normal tree
- # in the :in direction.
- @reversal.tree_from_vertex(resource, :out).keys
- end
-
- # Determine all of the leaf nodes below a given vertex.
- def leaves(vertex, direction = :out)
- tree = tree_from_vertex(vertex, direction)
- l = tree.keys.find_all { |c| adjacent(c, :direction => direction).empty? }
- return l
- end
-
- # Collect all of the edges that the passed events match. Returns
- # an array of edges.
- def matching_edges(events, base = nil)
- events.collect do |event|
- source = base || event.source
-
- unless vertex?(source)
- Puppet.warning "Got an event from invalid vertex %s" % source.ref
- next
- end
- # Get all of the edges that this vertex should forward events
- # to, which is the same thing as saying all edges directly below
- # This vertex in the graph.
- adjacent(source, :direction => :out, :type => :edges).find_all do |edge|
- edge.match?(event.name)
- end
- end.compact.flatten
- end
-
- # Take container information from another graph and use it
- # to replace any container vertices with their respective leaves.
- # This creates direct relationships where there were previously
- # indirect relationships through the containers.
- def splice!(other, type)
- # We have to get the container list via a topological sort on the
- # configuration graph, because otherwise containers that contain
- # other containers will add those containers back into the
- # graph. We could get a similar affect by only setting relationships
- # to container leaves, but that would result in many more
- # relationships.
- containers = other.topsort.find_all { |v| v.is_a?(type) and vertex?(v) }
- containers.each do |container|
- # Get the list of children from the other graph.
- children = other.adjacent(container, :direction => :out)
-
- # Just remove the container if it's empty.
- if children.empty?
- remove_vertex!(container)
- next
- end
-
- # First create new edges for each of the :in edges
- [:in, :out].each do |dir|
- edges = adjacent(container, :direction => dir, :type => :edges)
- edges.each do |edge|
- children.each do |child|
- if dir == :in
- s = edge.source
- t = child
- else
- s = child
- t = edge.target
- end
-
- add_edge(s, t, edge.label)
- end
-
- # Now get rid of the edge, so remove_vertex! works correctly.
- remove_edge!(edge)
- end
- end
- remove_vertex!(container)
- end
- end
-
- # A different way of walking a tree, and a much faster way than the
- # one that comes with GRATR.
- def tree_from_vertex(start, direction = :out)
- predecessor={}
- walk(start, direction) do |parent, child|
- predecessor[child] = parent
- end
- predecessor
- end
-end
diff --git a/lib/puppet/property.rb b/lib/puppet/property.rb
index e6d0704e6..f8a17ac07 100644
--- a/lib/puppet/property.rb
+++ b/lib/puppet/property.rb
@@ -4,8 +4,7 @@
require 'puppet'
require 'puppet/parameter'
-module Puppet
-class Property < Puppet::Parameter
+class Puppet::Property < Puppet::Parameter
# Because 'should' uses an array, we have a special method for handling
# it. We also want to keep copies of the original values, so that
@@ -54,39 +53,26 @@ class Property < Puppet::Parameter
end
# Look up a value's name, so we can find options and such.
- def self.value_name(value)
- if value != '' and name = symbolize(value) and @parametervalues.include?(name)
- return name
- elsif ary = self.match?(value)
- return ary[0]
- else
- return nil
+ def self.value_name(name)
+ if value = value_collection.match?(name)
+ value.name
end
end
# Retrieve an option set when a value was defined.
def self.value_option(name, option)
- option = option.to_sym
- if hash = @parameteroptions[name]
- hash[option]
- else
- nil
+ if value = value_collection.value(name)
+ value.send(option)
end
end
- # Create the value management variables.
- def self.initvars
- @parametervalues = {}
- @aliasvalues = {}
- @parameterregexes = {}
- @parameteroptions = {}
- end
-
# Define a new valid value for a property. You must provide the value itself,
# usually as a symbol, or a regex to match the value.
#
# The first argument to the method is either the value itself or a regex.
# The second argument is an option hash; valid options are:
+ # * <tt>:method</tt>: The name of the method to define. Defaults to 'set_<value>'.
+ # * <tt>:required_features</tt>: A list of features this value requires.
# * <tt>:event</tt>: The event that should be returned when this value is set.
# * <tt>:call</tt>: When to call any associated block. The default value
# is ``instead``, which means to call the value instead of calling the
@@ -94,52 +80,12 @@ class Property < Puppet::Parameter
# call both the block and the provider, according to the order you specify
# (the ``first`` refers to when the block is called, not the provider).
def self.newvalue(name, options = {}, &block)
- name = name.intern if name.is_a? String
-
- @parameteroptions[name] = {}
- paramopts = @parameteroptions[name]
+ value = value_collection.newvalue(name, options, &block)
- # Symbolize everything
- options.each do |opt, val|
- paramopts[symbolize(opt)] = symbolize(val)
- end
-
- # By default, call the block instead of the provider.
- if block_given?
- paramopts[:call] ||= :instead
- else
- paramopts[:call] ||= :none
- end
- # If there was no block given, we still want to store the information
- # for validation, but we won't be defining a method
- block ||= true
-
- case name
- when Symbol
- if @parametervalues.include?(name)
- Puppet.warning "%s reassigning value %s" % [self.name, name]
- end
- @parametervalues[name] = block
-
- if block_given?
- method = "set_" + name.to_s
- settor = paramopts[:settor] || (self.name.to_s + "=")
- define_method(method, &block)
- paramopts[:method] = method
- end
- when Regexp
- # The regexes are handled in parameter.rb. This value is used
- # for validation.
- @parameterregexes[name] = block
-
- # This is used for looking up the block for execution.
- if block_given?
- paramopts[:block] = block
- end
- else
- raise ArgumentError, "Invalid value %s of type %s" %
- [name, name.class]
+ if value.method and value.block
+ define_method(value.method, &value.block)
end
+ value
end
# Call the provider method.
@@ -175,9 +121,9 @@ class Property < Puppet::Parameter
elsif block = self.class.value_option(name, :block)
# FIXME It'd be better here to define a method, so that
# the blocks could return values.
- # If the regex was defined with no associated block, then just pass
- # through and the correct event will be passed back.
event = self.instance_eval(&block)
+ else
+ devfail "Could not find method for value '%s'" % name
end
return event, name
end
@@ -231,18 +177,14 @@ class Property < Puppet::Parameter
return event
end
+ attr_reader :shadow
+
# initialize our property
def initialize(hash = {})
super
- end
-
- def inspect
- str = "Property('%s', " % self.name
- if defined? @should and @should
- str += "@should = '%s')" % @should.join(", ")
- else
- str += "@should = nil)"
+ if ! self.metaparam? and klass = Puppet::Type.metaparamclass(self.class.name)
+ setup_shadow(klass)
end
end
@@ -308,6 +250,13 @@ class Property < Puppet::Parameter
self.class.array_matching == :all
end
+ # Execute our shadow's munge code, too, if we have one.
+ def munge(value)
+ self.shadow.munge(value) if self.shadow
+
+ super
+ end
+
# each property class must define the name() method, and property instances
# do not change that name
# this implicitly means that a given object can only have one property
@@ -342,29 +291,39 @@ class Property < Puppet::Parameter
# Set a name for looking up associated options like the event.
name = self.class.value_name(value)
- call = self.class.value_option(name, :call)
+ call = self.class.value_option(name, :call) || :none
- # If we're supposed to call the block first or instead, call it now
- if call == :before or call == :instead
+ if call == :instead
event, tmp = call_valuemethod(name, value)
- end
- unless call == :instead
+ elsif call == :none
if @resource.provider
call_provider(value)
else
# They haven't provided a block, and our parent does not have
# a provider, so we have no idea how to handle this.
- self.fail "%s cannot handle values of type %s" %
- [self.class.name, value.inspect]
+ self.fail "%s cannot handle values of type %s" % [self.class.name, value.inspect]
end
- end
- if call == :after
- event, tmp = call_valuemethod(name, value)
+ else
+ # LAK:NOTE 20081031 This is a change in behaviour -- you could
+ # previously specify :call => [;before|:after], which would call
+ # the setter *in addition to* the block. I'm convinced this
+ # was never used, and it makes things unecessarily complicated.
+ # If you want to specify a block and still call the setter, then
+ # do so in the block.
+ devfail "Cannot use obsolete :call value '%s' for property '%s'" % [call, self.class.name]
end
return event(name, event)
end
+ # If there's a shadowing metaparam, instantiate it now.
+ # This allows us to create a property or parameter with the
+ # same name as a metaparameter, and the metaparam will only be
+ # stored as a shadow.
+ def setup_shadow(klass)
+ @shadow = klass.new(:resource => self.resource)
+ end
+
# Only return the first value
def should
if defined? @should
@@ -390,18 +349,8 @@ class Property < Puppet::Parameter
@shouldorig = values
- if self.respond_to?(:validate)
- values.each { |val|
- validate(val)
- }
- end
- if self.respond_to?(:munge)
- @should = values.collect { |val|
- self.munge(val)
- }
- else
- @should = values
- end
+ values.each { |val| validate(val) }
+ @should = values.collect { |val| self.munge(val) }
end
def should_to_s(newvalue)
@@ -413,10 +362,7 @@ class Property < Puppet::Parameter
end
end
- # The default 'sync' method only selects among a list of registered # values.
def sync
- self.devfail("No values defined for %s" % self.class.name) unless self.class.values
-
if value = self.should
set(value)
else
@@ -432,7 +378,7 @@ class Property < Puppet::Parameter
if @resource.respond_to? :tags
@tags = @resource.tags
end
- @tags << self.name
+ @tags << self.name.to_s
end
@tags
end
@@ -441,6 +387,20 @@ class Property < Puppet::Parameter
return "%s(%s)" % [@resource.name,self.name]
end
+ # Verify that the passed value is valid.
+ # If the developer uses a 'validate' hook, this method will get overridden.
+ def unsafe_validate(value)
+ super
+ validate_features_per_value(value)
+ end
+
+ # Make sure that we've got all of the required features for a given value.
+ def validate_features_per_value(value)
+ if features = self.class.value_option(self.class.value_name(value), :required_features)
+ raise ArgumentError, "Provider must have features '%s' to set '%s' to '%s'" % [features.collect { |f| f.to_s }.join(", "), self.class.name, value] unless provider.satisfies?(features)
+ end
+ end
+
# Just return any should value we might have.
def value
self.should
@@ -545,5 +505,3 @@ class Property < Puppet::Parameter
end
end
end
-end
-
diff --git a/lib/puppet/provider.rb b/lib/puppet/provider.rb
index c02e15029..ce195c086 100644
--- a/lib/puppet/provider.rb
+++ b/lib/puppet/provider.rb
@@ -260,7 +260,7 @@ class Puppet::Provider
# use the hash here for later events.
@property_hash = resource
elsif resource
- @resource = resource if resource
+ @resource = resource
# LAK 2007-05-09: Keep the model stuff around for backward compatibility
@model = resource
@property_hash = {}
diff --git a/lib/puppet/provider/augeas/augeas.rb b/lib/puppet/provider/augeas/augeas.rb
index 8d4f6d55a..2457840d1 100644
--- a/lib/puppet/provider/augeas/augeas.rb
+++ b/lib/puppet/provider/augeas/augeas.rb
@@ -22,9 +22,9 @@ require 'augeas' if Puppet.features.augeas?
Puppet::Type.type(:augeas).provide(:augeas) do
#class Puppet::Provider::Augeas < Puppet::Provider
include Puppet::Util
-
- confine :true => Puppet.features.augeas?
-
+
+ confine :true => Puppet.features.augeas?
+
has_features :parse_commands, :need_to_run?,:execute_changes
# Extracts an 2 dimensional array of commands which are in the
@@ -38,10 +38,27 @@ Puppet::Type.type(:augeas).provide(:augeas) do
if data.is_a?(String)
data.each_line do |line|
cmd_array = Array.new()
- tokens = line.split(" ")
- cmd = tokens.shift()
- file = tokens.shift()
- other = tokens.join(" ")
+ single = line.index("'")
+ double = line.index('"')
+ tokens = nil
+ delim = " "
+ if ((single != nil) or (double != nil))
+ single = 99999 if single == nil
+ double = 99999 if double == nil
+ delim = '"' if double < single
+ delim = "'" if single < double
+ end
+ tokens = line.split(delim)
+ # If the length of tokens is 2, thn that means the pattern was
+ # command file "some text", therefore we need to re-split
+ # the first line
+ if tokens.length == 2
+ tokens = (tokens[0].split(" ")) << tokens[1]
+ end
+ cmd = tokens.shift().strip()
+ delim = "" if delim == " "
+ file = tokens.shift().strip()
+ other = tokens.join(" ").strip()
cmd_array << cmd if !cmd.nil?
cmd_array << file if !file.nil?
cmd_array << other if other != ""
@@ -63,7 +80,7 @@ Puppet::Type.type(:augeas).provide(:augeas) do
debug("Opening augeas with root #{root}, lens path #{load_path}, flags #{flags}")
Augeas.open(root, load_path,flags)
end
-
+
# Used by the need_to_run? method to process get filters. Returns
# true if there is a match, false if otherwise
# Assumes a syntax of get /files/path [COMPARATOR] value
@@ -93,8 +110,8 @@ Puppet::Type.type(:augeas).provide(:augeas) do
end
end
return_value
- end
-
+ end
+
# Used by the need_to_run? method to process match filters. Returns
# true if there is a match, false if otherwise
def process_match(cmd_array)
@@ -131,8 +148,8 @@ Puppet::Type.type(:augeas).provide(:augeas) do
end
end
return_value
- end
-
+ end
+
# Determines if augeas acutally needs to run.
def need_to_run?
return_value = true
@@ -152,8 +169,8 @@ Puppet::Type.type(:augeas).provide(:augeas) do
end
end
return_value
- end
-
+ end
+
# Actually execute the augeas changes.
def execute_changes
aug = open_augeas
@@ -204,7 +221,7 @@ Puppet::Type.type(:augeas).provide(:augeas) do
fail("Save failed with return code #{success}")
end
- return :executed
- end
-
+ return :executed
+ end
+
end
diff --git a/lib/puppet/provider/mailalias/aliases.rb b/lib/puppet/provider/mailalias/aliases.rb
index 8b5c45617..f9217123e 100755
--- a/lib/puppet/provider/mailalias/aliases.rb
+++ b/lib/puppet/provider/mailalias/aliases.rb
@@ -17,6 +17,14 @@ Puppet::Type.type(:mailalias).provide(:aliases,
record
end
+ def process(line)
+ ret = {}
+ records = line.split(':',2)
+ ret[:name] = records[0].strip()
+ ret[:recipient] = records[1].strip()
+ ret
+ end
+
def to_line(record)
dest = record[:recipient].collect do |d|
# Quote aliases that have non-alpha chars
diff --git a/lib/puppet/provider/package/rug.rb b/lib/puppet/provider/package/rug.rb
index 1e1d6763f..b68ec30c5 100644
--- a/lib/puppet/provider/package/rug.rb
+++ b/lib/puppet/provider/package/rug.rb
@@ -1,4 +1,4 @@
-Puppet.type(:package).provide :rug, :parent => :rpm do
+Puppet::Type.type(:package).provide :rug, :parent => :rpm do
desc "Support for suse ``rug`` package manager."
has_feature :versionable
diff --git a/lib/puppet/provider/package/up2date.rb b/lib/puppet/provider/package/up2date.rb
index e2a9d06f1..d8a12652f 100644
--- a/lib/puppet/provider/package/up2date.rb
+++ b/lib/puppet/provider/package/up2date.rb
@@ -1,4 +1,4 @@
-Puppet.type(:package).provide :up2date, :parent => :rpm, :source => :rpm do
+Puppet::Type.type(:package).provide :up2date, :parent => :rpm, :source => :rpm do
desc "Support for Red Hat's proprietary ``up2date`` package update
mechanism."
diff --git a/lib/puppet/provider/ssh_authorized_key/parsed.rb b/lib/puppet/provider/ssh_authorized_key/parsed.rb
index 77af58ef5..5604ba32a 100644
--- a/lib/puppet/provider/ssh_authorized_key/parsed.rb
+++ b/lib/puppet/provider/ssh_authorized_key/parsed.rb
@@ -40,25 +40,55 @@ Puppet::Type.type(:ssh_authorized_key).provide(:parsed,
# This was done in the type class but path expansion was failing for
# not yet existing users, the only workaround I found was to move that
# in the provider.
- if user = @resource.should(:user)
- target = File.expand_path("~%s/.ssh/authorized_keys" % user)
- @property_hash[:target] = target
- @resource[:target] = target
- end
+ @resource[:target] = target
super
end
+ def target
+ if user
+ File.expand_path("~%s/.ssh/authorized_keys" % user)
+ elsif target = @resource.should(:target)
+ target
+ end
+ end
+
+ def user
+ @resource.should(:user)
+ end
+
+ def dir_perm
+ # Determine correct permission for created directory and file
+ # we can afford more restrictive permissions when the user is known
+ if target
+ if user
+ 0700
+ else
+ 0755
+ end
+ end
+ end
+
+ def file_perm
+ if target
+ if user
+ 0600
+ else
+ 0644
+ end
+ end
+ end
+
def flush
# As path expansion had to be moved in the provider, we cannot generate new file
# resources and thus have to chown and chmod here. It smells hackish.
-
+
# Create target's parent directory if nonexistant
- if target = @property_hash[:target]
- dir = File.dirname(@property_hash[:target])
+ if target
+ dir = File.dirname(target)
if not File.exist? dir
Puppet.debug("Creating directory %s which did not exist" % dir)
- Dir.mkdir(dir, 0700)
+ Dir.mkdir(dir, dir_perm)
end
end
@@ -66,9 +96,19 @@ Puppet::Type.type(:ssh_authorized_key).provide(:parsed,
super
# Ensure correct permissions
- if target and user = @property_hash[:user]
- File.chown(Puppet::Util.uid(user), nil, dir)
- File.chown(Puppet::Util.uid(user), nil, @property_hash[:target])
+ if target and user
+ uid = Puppet::Util.uid(user)
+
+ if uid
+ File.chown(uid, nil, dir)
+ File.chown(uid, nil, target)
+ else
+ raise Puppet::Error, "Specified user does not exist"
+ end
+ end
+
+ if target
+ File.chmod(file_perm, target)
end
end
diff --git a/lib/puppet/reports/store.rb b/lib/puppet/reports/store.rb
index dfc992820..0c7f8cea9 100644
--- a/lib/puppet/reports/store.rb
+++ b/lib/puppet/reports/store.rb
@@ -12,7 +12,7 @@ Puppet::Reports.register_report(:store) do
def mkclientdir(client, dir)
config = Puppet::Util::Settings.new
- config.setdefaults("reportclient-#{client}",
+ config.setdefaults("reportclient-#{client}".to_sym,
"client-#{client}-dir" => { :default => dir,
:mode => 0750,
:desc => "Client dir for %s" % client,
@@ -21,7 +21,7 @@ Puppet::Reports.register_report(:store) do
}
)
- config.use("reportclient-#{client}")
+ config.use("reportclient-#{client}".to_sym)
end
def process
diff --git a/lib/puppet/resource.rb b/lib/puppet/resource.rb
new file mode 100644
index 000000000..add32b7cf
--- /dev/null
+++ b/lib/puppet/resource.rb
@@ -0,0 +1,200 @@
+require 'puppet'
+require 'puppet/util/tagging'
+require 'puppet/resource/reference'
+
+# The simplest resource class. Eventually it will function as the
+# base class for all resource-like behaviour.
+class Puppet::Resource
+ include Puppet::Util::Tagging
+ include Enumerable
+ attr_accessor :type, :title, :file, :line, :catalog, :implicit
+
+ # Proxy these methods to the parameters hash. It's likely they'll
+ # be overridden at some point, but this works for now.
+ %w{has_key? keys length delete empty? <<}.each do |method|
+ define_method(method) do |*args|
+ @parameters.send(method, *args)
+ end
+ end
+
+ # Set a given parameter. Converts all passed names
+ # to lower-case symbols.
+ def []=(param, value)
+ @parameters[parameter_name(param)] = value
+ end
+
+ # Return a given parameter's value. Converts all passed names
+ # to lower-case symbols.
+ def [](param)
+ @parameters[parameter_name(param)]
+ end
+
+ # Compatibility method.
+ def builtin?
+ builtin_type?
+ end
+
+ # Is this a builtin resource type?
+ def builtin_type?
+ @reference.builtin_type?
+ end
+
+ # Iterate over each param/value pair, as required for Enumerable.
+ def each
+ @parameters.each { |p,v| yield p, v }
+ end
+
+ # Create our resource.
+ def initialize(type, title, parameters = {})
+ @reference = Puppet::Resource::Reference.new(type, title)
+ @parameters = {}
+
+ parameters.each do |param, value|
+ self[param] = value
+ end
+
+ tag(@reference.type)
+ tag(@reference.title) if valid_tag?(@reference.title)
+ end
+
+ # Provide a reference to our resource in the canonical form.
+ def ref
+ @reference.to_s
+ end
+
+ # Get our title information from the reference, since it will canonize it for us.
+ def title
+ @reference.title
+ end
+
+ # Get our type information from the reference, since it will canonize it for us.
+ def type
+ @reference.type
+ end
+
+ # Produce a simple hash of our parameters.
+ def to_hash
+ result = @parameters.dup
+ unless result.include?(namevar)
+ result[namevar] = title
+ end
+ if result.has_key?(nil)
+ raise "wtf? %s" % namevar.inspect
+ end
+ result
+ end
+
+ def to_s
+ return ref
+ end
+
+ # Convert our resource to Puppet code.
+ def to_manifest
+ "%s { '%s':\n%s\n}" % [self.type.to_s.downcase, self.title,
+ @parameters.collect { |p, v|
+ if v.is_a? Array
+ " #{p} => [\'#{v.join("','")}\']"
+ else
+ " #{p} => \'#{v}\'"
+ end
+ }.join(",\n")
+ ]
+ end
+
+ def to_ref
+ ref
+ end
+
+ # Convert our resource to a RAL resource instance. Creates component
+ # instances for resource types that don't exist.
+ def to_ral
+ if typeklass = Puppet::Type.type(self.type)
+ return typeklass.new(self)
+ else
+ return Puppet::Type::Component.new(self)
+ end
+ end
+
+ # Translate our object to a backward-compatible transportable object.
+ def to_trans
+ if @reference.builtin_type?
+ result = to_transobject
+ else
+ result = to_transbucket
+ end
+
+ result.file = self.file
+ result.line = self.line
+
+ return result
+ end
+
+ # Create an old-style TransObject instance, for builtin resource types.
+ def to_transobject
+ # Now convert to a transobject
+ result = Puppet::TransObject.new(@reference.title, @reference.type)
+ to_hash.each do |p, v|
+ if v.is_a?(Puppet::Resource::Reference)
+ v = v.to_trans_ref
+ elsif v.is_a?(Array)
+ v = v.collect { |av|
+ if av.is_a?(Puppet::Resource::Reference)
+ av = av.to_trans_ref
+ end
+ av
+ }
+ end
+
+ # If the value is an array with only one value, then
+ # convert it to a single value. This is largely so that
+ # the database interaction doesn't have to worry about
+ # whether it returns an array or a string.
+ result[p.to_s] = if v.is_a?(Array) and v.length == 1
+ v[0]
+ else
+ v
+ end
+ end
+
+ result.tags = self.tags
+
+ return result
+ end
+
+ private
+
+ # Produce a canonical method name.
+ def parameter_name(param)
+ param = param.to_s.downcase.to_sym
+ if param == :name and n = namevar()
+ param = namevar
+ end
+ param
+ end
+
+ # The namevar for our resource type. If the type doesn't exist,
+ # always use :name.
+ def namevar
+ if t = resource_type
+ t.namevar
+ else
+ :name
+ end
+ end
+
+ # Retrieve the resource type.
+ def resource_type
+ Puppet::Type.type(type)
+ end
+
+ # Create an old-style TransBucket instance, for non-builtin resource types.
+ def to_transbucket
+ bucket = Puppet::TransBucket.new([])
+
+ bucket.type = self.type
+ bucket.name = self.title
+
+ # TransBuckets don't support parameters, which is why they're being deprecated.
+ return bucket
+ end
+end
diff --git a/lib/puppet/node/catalog.rb b/lib/puppet/resource/catalog.rb
index 17927388a..78f6a6f79 100644
--- a/lib/puppet/node/catalog.rb
+++ b/lib/puppet/resource/catalog.rb
@@ -1,18 +1,23 @@
require 'puppet/indirector'
-require 'puppet/pgraph'
+require 'puppet/simple_graph'
require 'puppet/transaction'
+require 'puppet/util/cacher'
+
require 'puppet/util/tagging'
# This class models a node catalog. It is the thing
# meant to be passed from server to client, and it contains all
# of the information in the catalog, including the resources
# and the relationships between them.
-class Puppet::Node::Catalog < Puppet::PGraph
+class Puppet::Resource::Catalog < Puppet::SimpleGraph
+ class DuplicateResourceError < Puppet::Error; end
+
extend Puppet::Indirector
indirects :catalog, :terminus_class => :compiler
include Puppet::Util::Tagging
+ include Puppet::Util::Cacher::Expirer
# The host name this is a catalog for.
attr_accessor :name
@@ -27,10 +32,6 @@ class Puppet::Node::Catalog < Puppet::PGraph
# How we should extract the catalog for sending to the client.
attr_reader :extraction_format
- # We need the ability to set this externally, so we can yaml-dump the
- # catalog.
- attr_accessor :edgelist_class
-
# Whether this is a host catalog, which behaves very differently.
# In particular, reports are sent, graphs are made, and state is
# stored in the state database. If this is set incorrectly, then you often
@@ -38,11 +39,6 @@ class Puppet::Node::Catalog < Puppet::PGraph
# that the host catalog needs.
attr_accessor :host_config
- # Whether this graph is another catalog's relationship graph.
- # We don't want to accidentally create a relationship graph for another
- # relationship graph.
- attr_accessor :is_relationship_graph
-
# Whether this catalog was retrieved from the cache, which affects
# whether it is written back out again.
attr_accessor :from_cache
@@ -58,16 +54,21 @@ class Puppet::Node::Catalog < Puppet::PGraph
end
# Add one or more resources to our graph and to our resource table.
+ # This is actually a relatively complicated method, because it handles multiple
+ # aspects of Catalog behaviour:
+ # * Add the resource to the resource table
+ # * Add the resource to the resource graph
+ # * Add the resource to the relationship graph
+ # * Add any aliases that make sense for the resource (e.g., name != title)
def add_resource(*resources)
resources.each do |resource|
unless resource.respond_to?(:ref)
- raise ArgumentError, "Can only add objects that respond to :ref"
+ raise ArgumentError, "Can only add objects that respond to :ref, not instances of %s" % resource.class
end
-
- fail_unless_unique(resource)
-
+ end.find_all { |resource| fail_or_skip_unless_unique(resource) }.each do |resource|
ref = resource.ref
+ @transient_resources << resource if applying?
@resource_table[ref] = resource
# If the name and title differ, set up an alias
@@ -76,9 +77,15 @@ class Puppet::Node::Catalog < Puppet::PGraph
self.alias(resource, resource.name) if resource.isomorphic?
end
- resource.catalog = self if resource.respond_to?(:catalog=) and ! is_relationship_graph
+ resource.catalog = self if resource.respond_to?(:catalog=)
add_vertex(resource)
+
+ if @relationship_graph
+ @relationship_graph.add_vertex(resource)
+ end
+
+ yield(resource) if block_given?
end
end
@@ -99,6 +106,7 @@ class Puppet::Node::Catalog < Puppet::PGraph
raise(ArgumentError, "Cannot alias %s to %s; resource %s already exists" % [resource.ref, name, newref])
end
@resource_table[newref] = resource
+ @aliases[resource.ref] ||= []
@aliases[resource.ref] << newref
end
@@ -111,6 +119,10 @@ class Puppet::Node::Catalog < Puppet::PGraph
def apply(options = {})
@applying = true
+ # Expire all of the resource data -- this ensures that all
+ # data we're operating against is entirely current.
+ expire()
+
Puppet::Util::Storage.load if host_config?
transaction = Puppet::Transaction.new(self)
@@ -156,7 +168,7 @@ class Puppet::Node::Catalog < Puppet::PGraph
@resource_table.clear
if defined?(@relationship_graph) and @relationship_graph
- @relationship_graph.clear(false)
+ @relationship_graph.clear
@relationship_graph = nil
end
end
@@ -165,48 +177,25 @@ class Puppet::Node::Catalog < Puppet::PGraph
@classes.dup
end
- # Create an implicit resource, meaning that it will lose out
- # to any explicitly defined resources. This method often returns
- # nil.
- # The quirk of this method is that it's not possible to create
- # an implicit resource before an explicit resource of the same name,
- # because all explicit resources are created before any generate()
- # methods are called on the individual resources. Thus, this
- # method can safely just check if an explicit resource already exists
- # and toss this implicit resource if so.
- def create_implicit_resource(type, options)
- unless options.include?(:implicit)
- options[:implicit] = true
- end
-
- # This will return nil if an equivalent explicit resource already exists.
- # When resource classes no longer retain references to resource instances,
- # this will need to be modified to catch that conflict and discard
- # implicit resources.
- if resource = create_resource(type, options)
- resource.implicit = true
-
- return resource
- else
- return nil
- end
- end
-
# Create a new resource and register it in the catalog.
def create_resource(type, options)
unless klass = Puppet::Type.type(type)
raise ArgumentError, "Unknown resource type %s" % type
end
- return unless resource = klass.create(options)
+ return unless resource = klass.new(options)
- @transient_resources << resource if applying?
add_resource(resource)
- if @relationship_graph
- @relationship_graph.add_resource(resource) unless @relationship_graph.resource(resource.ref)
- end
resource
end
+ def expired?(ts)
+ if applying?
+ return super
+ else
+ return true
+ end
+ end
+
# Make sure we support the requested extraction format.
def extraction_format=(value)
unless respond_to?("extract_to_%s" % value)
@@ -221,7 +210,8 @@ class Puppet::Node::Catalog < Puppet::PGraph
end
# Create the traditional TransBuckets and TransObjects from our catalog
- # graph. This will hopefully be deprecated soon.
+ # graph. LAK:NOTE(20081211): This is a pre-0.25 backward compatibility method.
+ # It can be removed as soon as xmlrpc is killed.
def extract_to_transportable
top = nil
current = nil
@@ -297,7 +287,7 @@ class Puppet::Node::Catalog < Puppet::PGraph
@applying = false
@relationship_graph = nil
- @aliases = Hash.new { |hash, key| hash[key] = [] }
+ @aliases = {}
if block_given?
yield(self)
@@ -315,23 +305,19 @@ class Puppet::Node::Catalog < Puppet::PGraph
# And filebuckets
if bucket = Puppet::Type.type(:filebucket).mkdefaultbucket
- add_resource(bucket)
+ add_resource(bucket) unless resource(bucket.ref)
end
end
# Create a graph of all of the relationships in our catalog.
def relationship_graph
- raise(Puppet::DevError, "Tried get a relationship graph for a relationship graph") if self.is_relationship_graph
-
unless defined? @relationship_graph and @relationship_graph
# It's important that we assign the graph immediately, because
# the debug messages below use the relationships in the
# relationship graph to determine the path to the resources
# spitting out the messages. If this is not set,
# then we get into an infinite loop.
- @relationship_graph = Puppet::Node::Catalog.new
- @relationship_graph.host_config = host_config?
- @relationship_graph.is_relationship_graph = true
+ @relationship_graph = Puppet::SimpleGraph.new
# First create the dependency graph
self.vertices.each do |vertex|
@@ -343,7 +329,7 @@ class Puppet::Node::Catalog < Puppet::PGraph
# Lastly, add in any autorequires
@relationship_graph.vertices.each do |vertex|
- vertex.autorequire.each do |edge|
+ vertex.autorequire(self).each do |edge|
unless @relationship_graph.edge?(edge.source, edge.target) # don't let automatic relationships conflict with manual ones.
unless @relationship_graph.edge?(edge.target, edge.source)
vertex.debug "Autorequiring %s" % [edge.source]
@@ -354,13 +340,12 @@ class Puppet::Node::Catalog < Puppet::PGraph
end
end
end
-
- @relationship_graph.write_graph(:relationships)
+ @relationship_graph.write_graph(:relationships) if host_config?
# Then splice in the container information
@relationship_graph.splice!(self, Puppet::Type::Component)
- @relationship_graph.write_graph(:expanded_relationships)
+ @relationship_graph.write_graph(:expanded_relationships) if host_config?
end
@relationship_graph
end
@@ -371,8 +356,10 @@ class Puppet::Node::Catalog < Puppet::PGraph
def remove_resource(*resources)
resources.each do |resource|
@resource_table.delete(resource.ref)
- @aliases[resource.ref].each { |res_alias| @resource_table.delete(res_alias) }
- @aliases[resource.ref].clear
+ if aliases = @aliases[resource.ref]
+ aliases.each { |res_alias| @resource_table.delete(res_alias) }
+ @aliases.delete(resource.ref)
+ end
remove_vertex!(resource) if vertex?(resource)
@relationship_graph.remove_vertex!(resource) if @relationship_graph and @relationship_graph.vertex?(resource)
resource.remove
@@ -384,18 +371,14 @@ class Puppet::Node::Catalog < Puppet::PGraph
# Always create a resource reference, so that it always canonizes how we
# are referring to them.
if title
- ref = Puppet::ResourceReference.new(type, title).to_s
+ ref = Puppet::Resource::Reference.new(type, title).to_s
else
# If they didn't provide a title, then we expect the first
# argument to be of the form 'Class[name]', which our
# Reference class canonizes for us.
- ref = Puppet::ResourceReference.new(nil, type).to_s
- end
- if resource = @resource_table[ref]
- return resource
- elsif defined?(@relationship_graph) and @relationship_graph
- @relationship_graph.resource(ref)
+ ref = Puppet::Resource::Reference.new(nil, type).to_s
end
+ @resource_table[ref]
end
# Return an array of all resources.
@@ -405,39 +388,31 @@ class Puppet::Node::Catalog < Puppet::PGraph
# Convert our catalog into a RAL catalog.
def to_ral
- to_catalog :to_type
+ to_catalog :to_ral
+ end
+
+ # Convert our catalog into a catalog of Puppet::Resource instances.
+ def to_resource
+ to_catalog :to_resource
end
- # Turn our parser catalog into a transportable catalog.
- def to_transportable
- to_catalog :to_transobject
+ # Store the classes in the classfile.
+ def write_class_file
+ begin
+ ::File.open(Puppet[:classfile], "w") do |f|
+ f.puts classes.join("\n")
+ end
+ rescue => detail
+ Puppet.err "Could not create class file %s: %s" % [Puppet[:classfile], detail]
+ end
end
# Produce the graph files if requested.
def write_graph(name)
# We only want to graph the main host catalog.
return unless host_config?
-
- return unless Puppet[:graph]
-
- Puppet.settings.use(:graphing)
- file = File.join(Puppet[:graphdir], "%s.dot" % name.to_s)
- File.open(file, "w") { |f|
- f.puts to_dot("name" => name.to_s.capitalize)
- }
- end
-
- # LAK:NOTE We cannot yaml-dump the class in the edgelist_class, because classes cannot be
- # dumped by default, nor does yaml-dumping # the edge-labels work at this point (I don't
- # know why).
- # Neither of these matters right now, but I suppose it could at some point.
- # We also have to have the vertex_dict dumped after the resource table, because yaml can't
- # seem to handle the output of yaml-dumping the vertex_dict.
- def to_yaml_properties
- props = instance_variables.reject { |v| %w{@edgelist_class @edge_labels @vertex_dict}.include?(v) }
- props << "@vertex_dict"
- props
+ super
end
private
@@ -448,12 +423,28 @@ class Puppet::Node::Catalog < Puppet::PGraph
@transient_resources.clear
@relationship_graph = nil
end
+
+ # Expire any cached data the resources are keeping.
+ expire()
end
# Verify that the given resource isn't defined elsewhere.
- def fail_unless_unique(resource)
+ def fail_or_skip_unless_unique(resource)
# Short-curcuit the common case,
- return unless existing_resource = @resource_table[resource.ref]
+ return resource unless existing_resource = @resource_table[resource.ref]
+
+ if resource.implicit?
+ resource.debug "Generated resource conflicts with explicit resource; ignoring generated resource"
+ return nil
+ elsif old = resource(resource.ref) and old.implicit?
+ # The existing resource is implicit; remove it and replace it with
+ # the new one.
+ old.debug "Replacing with new resource"
+ remove_resource(old)
+ return resource
+ end
+
+ # If we've gotten this far, it's a real conflict
# Either it's a defined type, which are never
# isomorphic, or it's a non-isomorphic type, so
@@ -469,7 +460,7 @@ class Puppet::Node::Catalog < Puppet::PGraph
msg << "; cannot redefine"
end
- raise ArgumentError.new(msg)
+ raise DuplicateResourceError.new(msg)
end
# An abstracted method for converting one catalog into another type of catalog.
@@ -478,6 +469,8 @@ class Puppet::Node::Catalog < Puppet::PGraph
def to_catalog(convert)
result = self.class.new(self.name)
+ result.version = self.version
+
map = {}
vertices.each do |resource|
next if resource.respond_to?(:virtual?) and resource.virtual?
@@ -486,15 +479,22 @@ class Puppet::Node::Catalog < Puppet::PGraph
#Aliases aren't working in the ral catalog because the current instance of the resource
#has a reference to the catalog being converted. . . So, give it a reference to the new one
#problem solved. . .
- if resource.is_a?(Puppet::TransObject)
+ if resource.is_a?(Puppet::Resource)
+ resource = resource.dup
+ resource.catalog = result
+ elsif resource.is_a?(Puppet::TransObject)
resource = resource.dup
resource.catalog = result
elsif resource.is_a?(Puppet::Parser::Resource)
- resource = resource.to_transobject
+ resource = resource.to_resource
resource.catalog = result
end
- newres = resource.send(convert)
+ if resource.is_a?(Puppet::Resource) and convert.to_s == "to_resource"
+ newres = resource
+ else
+ newres = resource.send(convert)
+ end
# We can't guarantee that resources don't munge their names
# (like files do with trailing slashes), so we have to keep track
diff --git a/lib/puppet/resource_reference.rb b/lib/puppet/resource/reference.rb
index 44b518816..750c10e41 100644
--- a/lib/puppet/resource_reference.rb
+++ b/lib/puppet/resource/reference.rb
@@ -3,34 +3,50 @@
# Copyright (c) 2007. All rights reserved.
require 'puppet'
+require 'puppet/resource'
# A simple class to canonize how we refer to and retrieve
# resources.
-class Puppet::ResourceReference
+class Puppet::Resource::Reference
attr_reader :type
attr_accessor :title, :catalog
- def initialize(type, title)
- # This will set @type if it looks like a resource reference.
- self.title = title
+ def ==(ref)
+ return false unless ref.is_a?(Puppet::Resource::Reference)
+ return true if ref.type == self.type and ref.title == self.title
+ return false
+ end
+
+ def builtin_type?
+ builtin_type ? true : false
+ end
- # Don't override whatever was done by setting the title.
- self.type = type if self.type.nil?
+ def initialize(argtype, argtitle = nil)
+ if argtitle.nil?
+ if argtype.is_a?(Puppet::Type)
+ self.title = argtype.title
+ self.type = argtype.class.name
+ else
+ self.title = argtype
+ if self.title == argtype
+ raise ArgumentError, "No title provided and title '%s' is not a valid resource reference" % argtype.inspect
+ end
+ end
+ else
+ # This will set @type if it looks like a resource reference.
+ self.title = argtitle
+
+ # Don't override whatever was done by setting the title.
+ self.type ||= argtype
+ end
@builtin_type = nil
end
# Find our resource.
def resolve
- if catalog
- return catalog.resource(to_s)
- end
- # If it's builtin, then just ask for it directly from the type.
- if t = builtin_type
- t[@title]
- else # Else, look for a component with the full reference as the name.
- Puppet::Type::Component[to_s]
- end
+ return catalog.resource(to_s) if catalog
+ return nil
end
# If the title has square brackets, treat it like a reference and
@@ -54,6 +70,18 @@ class Puppet::ResourceReference
end
end
+ # Convert to the reference format that TransObject uses. Yay backward
+ # compatibility.
+ def to_trans_ref
+ # We have to return different cases to provide backward compatibility
+ # from 0.24.x to 0.23.x.
+ if builtin_type?
+ return [type.to_s.downcase, title.to_s]
+ else
+ return [type.to_s, title.to_s]
+ end
+ end
+
# Convert to the standard way of referring to resources.
def to_s
"%s[%s]" % [@type, @title]
@@ -61,16 +89,12 @@ class Puppet::ResourceReference
private
- def builtin_type?
- builtin_type ? true : false
- end
-
def builtin_type
if @builtin_type.nil?
if @type =~ /::/
@builtin_type = false
elsif klass = Puppet::Type.type(@type.to_s.downcase)
- @builtin_type = klass
+ @builtin_type = true
else
@builtin_type = false
end
diff --git a/lib/puppet/simple_graph.rb b/lib/puppet/simple_graph.rb
index 48f393f77..b9ea0f394 100644
--- a/lib/puppet/simple_graph.rb
+++ b/lib/puppet/simple_graph.rb
@@ -19,8 +19,7 @@ class Puppet::SimpleGraph
def initialize(vertex)
@vertex = vertex
- @adjacencies = {:in => Hash.new { |h,k| h[k] = [] }, :out => Hash.new { |h,k| h[k] = [] }}
- #@adjacencies = {:in => [], :out => []}
+ @adjacencies = {:in => {}, :out => {}}
end
# Find adjacent vertices or edges.
@@ -35,7 +34,7 @@ class Puppet::SimpleGraph
# Add an edge to our list.
def add_edge(direction, edge)
- @adjacencies[direction][other_vertex(direction, edge)] << edge
+ opposite_adjacencies(direction, edge) << edge
end
# Return all known edges.
@@ -45,7 +44,7 @@ class Puppet::SimpleGraph
# Test whether we share an edge with a given vertex.
def has_edge?(direction, vertex)
- return true if @adjacencies[direction][vertex].length > 0
+ return true if vertex_adjacencies(direction, vertex).length > 0
return false
end
@@ -74,12 +73,29 @@ class Puppet::SimpleGraph
# Remove an edge from our list. Assumes that we've already checked
# that the edge is valid.
def remove_edge(direction, edge)
- @adjacencies[direction][other_vertex(direction, edge)].delete(edge)
+ opposite_adjacencies(direction, edge).delete(edge)
end
def to_s
vertex.to_s
end
+
+ private
+
+ # These methods exist so we don't need a Hash with a default proc.
+
+ # Look up the adjacencies for a vertex at the other end of an
+ # edge.
+ def opposite_adjacencies(direction, edge)
+ opposite_vertex = other_vertex(direction, edge)
+ vertex_adjacencies(direction, opposite_vertex)
+ end
+
+ # Look up the adjacencies for a given vertex.
+ def vertex_adjacencies(direction, vertex)
+ @adjacencies[direction][vertex] ||= []
+ @adjacencies[direction][vertex]
+ end
end
def initialize
@@ -94,11 +110,55 @@ class Puppet::SimpleGraph
@edges.clear
end
+ # Which resources a given resource depends upon.
+ def dependents(resource)
+ tree_from_vertex(resource).keys
+ end
+
+ # Which resources depend upon the given resource.
+ def dependencies(resource)
+ # Cache the reversal graph, because it's somewhat expensive
+ # to create.
+ unless defined? @reversal and @reversal
+ @reversal = reversal
+ end
+ # Strangely, it's significantly faster to search a reversed
+ # tree in the :out direction than to search a normal tree
+ # in the :in direction.
+ @reversal.tree_from_vertex(resource, :out).keys
+ end
+
# Whether our graph is directed. Always true. Used to produce dot files.
def directed?
true
end
+ # Determine all of the leaf nodes below a given vertex.
+ def leaves(vertex, direction = :out)
+ tree = tree_from_vertex(vertex, direction)
+ l = tree.keys.find_all { |c| adjacent(c, :direction => direction).empty? }
+ return l
+ end
+
+ # Collect all of the edges that the passed events match. Returns
+ # an array of edges.
+ def matching_edges(events, base = nil)
+ events.collect do |event|
+ source = base || event.source
+
+ unless vertex?(source)
+ Puppet.warning "Got an event from invalid vertex %s" % source.ref
+ next
+ end
+ # Get all of the edges that this vertex should forward events
+ # to, which is the same thing as saying all edges directly below
+ # This vertex in the graph.
+ adjacent(source, :direction => :out, :type => :edges).find_all do |edge|
+ edge.match?(event.name)
+ end
+ end.compact.flatten
+ end
+
# Return a reversed version of this graph.
def reversal
result = self.class.new
@@ -154,6 +214,7 @@ class Puppet::SimpleGraph
# Add a new vertex to the graph.
def add_vertex(vertex)
+ @reversal = nil
return false if vertex?(vertex)
setup_vertex(vertex)
true # don't return the VertexWrapper instance.
@@ -180,6 +241,7 @@ class Puppet::SimpleGraph
# Add a new edge. The graph user has to create the edge instance,
# since they have to specify what kind of edge it is.
def add_edge(source, target = nil, label = nil)
+ @reversal = nil
if target
edge = Puppet::Relationship.new(source, target, label)
else
@@ -250,6 +312,52 @@ class Puppet::SimpleGraph
# induced_subgraph(gv).write_to_graphic_file('jpg', name)
# end
# end
+
+ # Take container information from another graph and use it
+ # to replace any container vertices with their respective leaves.
+ # This creates direct relationships where there were previously
+ # indirect relationships through the containers.
+ def splice!(other, type)
+ # We have to get the container list via a topological sort on the
+ # configuration graph, because otherwise containers that contain
+ # other containers will add those containers back into the
+ # graph. We could get a similar affect by only setting relationships
+ # to container leaves, but that would result in many more
+ # relationships.
+ containers = other.topsort.find_all { |v| v.is_a?(type) and vertex?(v) }
+ containers.each do |container|
+ # Get the list of children from the other graph.
+ children = other.adjacent(container, :direction => :out)
+
+ # Just remove the container if it's empty.
+ if children.empty?
+ remove_vertex!(container)
+ next
+ end
+
+ # First create new edges for each of the :in edges
+ [:in, :out].each do |dir|
+ edges = adjacent(container, :direction => dir, :type => :edges)
+ edges.each do |edge|
+ children.each do |child|
+ if dir == :in
+ s = edge.source
+ t = child
+ else
+ s = child
+ t = edge.target
+ end
+
+ add_edge(s, t, edge.label)
+ end
+
+ # Now get rid of the edge, so remove_vertex! works correctly.
+ remove_edge!(edge)
+ end
+ end
+ remove_vertex!(container)
+ end
+ end
def to_yaml_properties
instance_variables
@@ -263,6 +371,16 @@ class Puppet::SimpleGraph
end
end
+ # A different way of walking a tree, and a much faster way than the
+ # one that comes with GRATR.
+ def tree_from_vertex(start, direction = :out)
+ predecessor={}
+ walk(start, direction) do |parent, child|
+ predecessor[child] = parent
+ end
+ predecessor
+ end
+
# LAK:FIXME This is just a paste of the GRATR code with slight modifications.
# Return a DOT::DOTDigraph for directed graphs or a DOT::DOTSubgraph for an
@@ -304,6 +422,14 @@ class Puppet::SimpleGraph
system('dotty', dotfile)
end
+ # Just walk the tree and pass each edge.
+ def walk(source, direction, &block)
+ adjacent(source, :direction => direction).each do |target|
+ yield source, target
+ walk(target, direction, &block)
+ end
+ end
+
# Use +dot+ to create a graphical representation of the graph. Returns the
# filename of the graphics file.
def write_to_graphic_file (fmt='png', dotfile='graph')
@@ -315,4 +441,16 @@ class Puppet::SimpleGraph
system( "dot -T#{fmt} #{src} -o #{dot}" )
dot
end
+
+ # Produce the graph files if requested.
+ def write_graph(name)
+ return unless Puppet[:graph]
+
+ Puppet.settings.use(:graphing)
+
+ file = File.join(Puppet[:graphdir], "%s.dot" % name.to_s)
+ File.open(file, "w") { |f|
+ f.puts to_dot("name" => name.to_s.capitalize)
+ }
+ end
end
diff --git a/lib/puppet/ssl.rb b/lib/puppet/ssl.rb
new file mode 100644
index 000000000..1a3e8d13d
--- /dev/null
+++ b/lib/puppet/ssl.rb
@@ -0,0 +1,7 @@
+# Just to make the constants work out.
+require 'puppet'
+require 'openssl'
+
+module Puppet::SSL # :nodoc:
+ require 'puppet/ssl/host'
+end
diff --git a/lib/puppet/ssl/base.rb b/lib/puppet/ssl/base.rb
new file mode 100644
index 000000000..d67861f4b
--- /dev/null
+++ b/lib/puppet/ssl/base.rb
@@ -0,0 +1,62 @@
+require 'puppet/ssl'
+
+# The base class for wrapping SSL instances.
+class Puppet::SSL::Base
+ # For now, use the YAML separator.
+ SEPARATOR = "\n---\n"
+
+ def self.from_multiple_s(text)
+ text.split(SEPARATOR).collect { |inst| from_s(inst) }
+ end
+
+ def self.to_multiple_s(instances)
+ instances.collect { |inst| inst.to_s }.join(SEPARATOR)
+ end
+
+ def self.wraps(klass)
+ @wrapped_class = klass
+ end
+
+ def self.wrapped_class
+ raise(Puppet::DevError, "%s has not declared what class it wraps" % self) unless defined?(@wrapped_class)
+ @wrapped_class
+ end
+
+ attr_accessor :name, :content
+
+ # Is this file for the CA?
+ def ca?
+ name == Puppet::SSL::Host.ca_name
+ end
+
+ def generate
+ raise Puppet::DevError, "%s did not override 'generate'" % self.class
+ end
+
+ def initialize(name)
+ @name = name.to_s.downcase
+ end
+
+ # Read content from disk appropriately.
+ def read(path)
+ @content = wrapped_class.new(File.read(path))
+ end
+
+ # Convert our thing to pem.
+ def to_s
+ return "" unless content
+ content.to_pem
+ end
+
+ # Provide the full text of the thing we're dealing with.
+ def to_text
+ return "" unless content
+ content.to_text
+ end
+
+ private
+
+ def wrapped_class
+ self.class.wrapped_class
+ end
+end
diff --git a/lib/puppet/ssl/certificate.rb b/lib/puppet/ssl/certificate.rb
new file mode 100644
index 000000000..f9297f380
--- /dev/null
+++ b/lib/puppet/ssl/certificate.rb
@@ -0,0 +1,34 @@
+require 'puppet/ssl/base'
+
+# Manage certificates themselves. This class has no
+# 'generate' method because the CA is responsible
+# for turning CSRs into certificates; we can only
+# retrieve them from the CA (or not, as is often
+# the case).
+class Puppet::SSL::Certificate < Puppet::SSL::Base
+ # This is defined from the base class
+ wraps OpenSSL::X509::Certificate
+
+ extend Puppet::Indirector
+ indirects :certificate, :terminus_class => :file
+
+ # Convert a string into an instance.
+ def self.from_s(string)
+ instance = wrapped_class.new(string)
+ name = instance.subject.to_s.sub(/\/CN=/i, '').downcase
+ result = new(name)
+ result.content = instance
+ result
+ end
+
+ # Because of how the format handler class is included, this
+ # can't be in the base class.
+ def self.supported_formats
+ [:s]
+ end
+
+ def expiration
+ return nil unless content
+ return content.not_after
+ end
+end
diff --git a/lib/puppet/ssl/certificate_authority.rb b/lib/puppet/ssl/certificate_authority.rb
new file mode 100644
index 000000000..08feff0ac
--- /dev/null
+++ b/lib/puppet/ssl/certificate_authority.rb
@@ -0,0 +1,289 @@
+require 'puppet/ssl/host'
+require 'puppet/ssl/certificate_request'
+require 'puppet/util/cacher'
+
+# The class that knows how to sign certificates. It creates
+# a 'special' SSL::Host whose name is 'ca', thus indicating
+# that, well, it's the CA. There's some magic in the
+# indirector/ssl_file terminus base class that does that
+# for us.
+# This class mostly just signs certs for us, but
+# it can also be seen as a general interface into all of the
+# SSL stuff.
+class Puppet::SSL::CertificateAuthority
+ require 'puppet/ssl/certificate_factory'
+ require 'puppet/ssl/inventory'
+ require 'puppet/ssl/certificate_revocation_list'
+
+ require 'puppet/ssl/certificate_authority/interface'
+
+ class << self
+ include Puppet::Util::Cacher
+
+ cached_attr(:singleton_instance) { new }
+ end
+
+ def self.ca?
+ return false unless Puppet[:ca]
+ return false unless Puppet[:name] == "puppetmasterd"
+ return true
+ end
+
+ # If this process can function as a CA, then return a singleton
+ # instance.
+ def self.instance
+ return nil unless ca?
+
+ singleton_instance
+ end
+
+ attr_reader :name, :host
+
+ # Create and run an applicator. I wanted to build an interface where you could do
+ # something like 'ca.apply(:generate).to(:all) but I don't think it's really possible.
+ def apply(method, options)
+ unless options[:to]
+ raise ArgumentError, "You must specify the hosts to apply to; valid values are an array or the symbol :all"
+ end
+ applier = Interface.new(method, options[:to])
+
+ applier.apply(self)
+ end
+
+ # If autosign is configured, then autosign all CSRs that match our configuration.
+ def autosign
+ return unless auto = autosign?
+
+ store = nil
+ if auto != true
+ store = autosign_store(auto)
+ end
+
+ Puppet::SSL::CertificateRequest.search("*").each do |csr|
+ sign(csr.name) if auto == true or store.allowed?(csr.name, "127.1.1.1")
+ end
+ end
+
+ # Do we autosign? This returns true, false, or a filename.
+ def autosign?
+ auto = Puppet[:autosign]
+ return false if ['false', false].include?(auto)
+ return true if ['true', true].include?(auto)
+
+ raise ArgumentError, "The autosign configuration '%s' must be a fully qualified file" % auto unless auto =~ /^\//
+ if FileTest.exist?(auto)
+ return auto
+ else
+ return false
+ end
+ end
+
+ # Create an AuthStore for autosigning.
+ def autosign_store(file)
+ auth = Puppet::Network::AuthStore.new
+ File.readlines(file).each do |line|
+ next if line =~ /^\s*#/
+ next if line =~ /^\s*$/
+ auth.allow(line.chomp)
+ end
+
+ auth
+ end
+
+ # Retrieve (or create, if necessary) the certificate revocation list.
+ def crl
+ unless defined?(@crl)
+ unless @crl = Puppet::SSL::CertificateRevocationList.find("ca")
+ @crl = Puppet::SSL::CertificateRevocationList.new("ca")
+ @crl.generate(host.certificate.content, host.key.content)
+ @crl.save
+ end
+ end
+ @crl
+ end
+
+ # Delegate this to our Host class.
+ def destroy(name)
+ Puppet::SSL::Host.destroy(name)
+ end
+
+ # Generate a new certificate.
+ def generate(name)
+ raise ArgumentError, "A Certificate already exists for %s" % name if Puppet::SSL::Certificate.find(name)
+ host = Puppet::SSL::Host.new(name)
+
+ host.generate_certificate_request
+
+ sign(name)
+ end
+
+ # Generate our CA certificate.
+ def generate_ca_certificate
+ generate_password unless password?
+
+ host.generate_key unless host.key
+
+ # Create a new cert request. We do this
+ # specially, because we don't want to actually
+ # save the request anywhere.
+ request = Puppet::SSL::CertificateRequest.new(host.name)
+ request.generate(host.key)
+
+ # Create a self-signed certificate.
+ @certificate = sign(host.name, :ca, request)
+
+ # And make sure we initialize our CRL.
+ crl()
+ end
+
+ def initialize
+ Puppet.settings.use :main, :ssl, :ca
+
+ @name = Puppet[:certname]
+
+ @host = Puppet::SSL::Host.new(Puppet::SSL::Host.ca_name)
+
+ setup()
+ end
+
+ # Retrieve (or create, if necessary) our inventory manager.
+ def inventory
+ unless defined?(@inventory)
+ @inventory = Puppet::SSL::Inventory.new
+ end
+ @inventory
+ end
+
+ # Generate a new password for the CA.
+ def generate_password
+ pass = ""
+ 20.times { pass += (rand(74) + 48).chr }
+
+ begin
+ Puppet.settings.write(:capass) { |f| f.print pass }
+ rescue Errno::EACCES => detail
+ raise Puppet::Error, "Could not write CA password: %s" % detail.to_s
+ end
+
+ @password = pass
+
+ return pass
+ end
+
+ # List all signed certificates.
+ def list
+ Puppet::SSL::Certificate.search("*").collect { |c| c.name }
+ end
+
+ # Read the next serial from the serial file, and increment the
+ # file so this one is considered used.
+ def next_serial
+ serial = nil
+
+ # This is slightly odd. If the file doesn't exist, our readwritelock creates
+ # it, but with a mode we can't actually read in some cases. So, use
+ # a default before the lock.
+ unless FileTest.exist?(Puppet[:serial])
+ serial = 0x0
+ end
+
+ Puppet.settings.readwritelock(:serial) { |f|
+ if FileTest.exist?(Puppet[:serial])
+ serial ||= File.read(Puppet.settings[:serial]).chomp.hex
+ end
+
+ # We store the next valid serial, not the one we just used.
+ f << "%04X" % (serial + 1)
+ }
+
+ return serial
+ end
+
+ # Does the password file exist?
+ def password?
+ FileTest.exist? Puppet[:capass]
+ end
+
+ # Print a given host's certificate as text.
+ def print(name)
+ if cert = Puppet::SSL::Certificate.find(name)
+ return cert.to_text
+ else
+ return nil
+ end
+ end
+
+ # Revoke a given certificate.
+ def revoke(name)
+ raise ArgumentError, "Cannot revoke certificates when the CRL is disabled" unless crl
+
+ if cert = Puppet::SSL::Certificate.find(name)
+ serial = cert.content.serial
+ elsif ! serial = inventory.serial(name)
+ raise ArgumentError, "Could not find a serial number for %s" % name
+ end
+ crl.revoke(serial, host.key.content)
+ end
+
+ # This initializes our CA so it actually works. This should be a private
+ # method, except that you can't any-instance stub private methods, which is
+ # *awesome*. This method only really exists to provide a stub-point during
+ # testing.
+ def setup
+ generate_ca_certificate unless @host.certificate
+ end
+
+ # Sign a given certificate request.
+ def sign(hostname, cert_type = :server, self_signing_csr = nil)
+ # This is a self-signed certificate
+ if self_signing_csr
+ csr = self_signing_csr
+ issuer = csr.content
+ else
+ unless csr = Puppet::SSL::CertificateRequest.find(hostname)
+ raise ArgumentError, "Could not find certificate request for %s" % hostname
+ end
+ issuer = host.certificate.content
+ end
+
+ cert = Puppet::SSL::Certificate.new(hostname)
+ cert.content = Puppet::SSL::CertificateFactory.new(cert_type, csr.content, issuer, next_serial).result
+ cert.content.sign(host.key.content, OpenSSL::Digest::SHA1.new)
+
+ Puppet.notice "Signed certificate request for %s" % hostname
+
+ # Add the cert to the inventory before we save it, since
+ # otherwise we could end up with it being duplicated, if
+ # this is the first time we build the inventory file.
+ inventory.add(cert)
+
+ # Save the now-signed cert. This should get routed correctly depending
+ # on the certificate type.
+ cert.save
+
+ # And remove the CSR if this wasn't self signed.
+ Puppet::SSL::CertificateRequest.destroy(csr.name) unless self_signing_csr
+
+ return cert
+ end
+
+ # Verify a given host's certificate.
+ def verify(name)
+ unless cert = Puppet::SSL::Certificate.find(name)
+ raise ArgumentError, "Could not find a certificate for %s" % name
+ end
+ store = OpenSSL::X509::Store.new
+ store.add_file Puppet[:cacert]
+ store.add_crl crl.content if self.crl
+ store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
+
+ unless store.verify(cert.content)
+ raise "Certificate for %s failed verification" % name
+ end
+ end
+
+ # List the waiting certificate requests.
+ def waiting?
+ Puppet::SSL::CertificateRequest.search("*").collect { |r| r.name }
+ end
+end
diff --git a/lib/puppet/ssl/certificate_authority/interface.rb b/lib/puppet/ssl/certificate_authority/interface.rb
new file mode 100644
index 000000000..b355e21f0
--- /dev/null
+++ b/lib/puppet/ssl/certificate_authority/interface.rb
@@ -0,0 +1,110 @@
+# This class is basically a hidden class that knows how to act
+# on the CA. It's only used by the 'puppetca' executable, and its
+# job is to provide a CLI-like interface to the CA class.
+class Puppet::SSL::CertificateAuthority::Interface
+ INTERFACE_METHODS = [:destroy, :list, :revoke, :generate, :sign, :print, :verify]
+
+ class InterfaceError < ArgumentError; end
+
+ attr_reader :method, :subjects
+
+ # Actually perform the work.
+ def apply(ca)
+ unless subjects or method == :list
+ raise ArgumentError, "You must provide hosts or :all when using %s" % method
+ end
+
+ begin
+ if respond_to?(method)
+ return send(method, ca)
+ end
+
+ (subjects == :all ? ca.list : subjects).each do |host|
+ ca.send(method, host)
+ end
+ rescue InterfaceError
+ raise
+ rescue => detail
+ puts detail.backtrace if Puppet[:trace]
+ Puppet.err "Could not call %s: %s" % [method, detail]
+ end
+ end
+
+ def generate(ca)
+ raise InterfaceError, "It makes no sense to generate all hosts; you must specify a list" if subjects == :all
+
+ subjects.each do |host|
+ ca.generate(host)
+ end
+ end
+
+ def initialize(method, subjects)
+ self.method = method
+ self.subjects = subjects
+ end
+
+ # List the hosts.
+ def list(ca)
+ unless subjects
+ puts ca.waiting?.join("\n")
+ return nil
+ end
+
+ signed = ca.list
+ requests = ca.waiting?
+
+ if subjects == :all
+ hosts = [signed, requests].flatten
+ else
+ hosts = subjects
+ end
+
+ hosts.uniq.sort.each do |host|
+ if signed.include?(host)
+ puts "+ " + host
+ else
+ puts host
+ end
+ end
+ end
+
+ # Set the method to apply.
+ def method=(method)
+ raise ArgumentError, "Invalid method %s to apply" % method unless INTERFACE_METHODS.include?(method)
+ @method = method
+ end
+
+ # Print certificate information.
+ def print(ca)
+ (subjects == :all ? ca.list : subjects).each do |host|
+ if value = ca.print(host)
+ puts value
+ else
+ Puppet.err "Could not find certificate for %s" % host
+ end
+ end
+ end
+
+ # Sign a given certificate.
+ def sign(ca)
+ list = subjects == :all ? ca.waiting? : subjects
+ raise InterfaceError, "No waiting certificate requests to sign" if list.empty?
+ list.each do |host|
+ ca.sign(host)
+ end
+ end
+
+ # Set the list of hosts we're operating on. Also supports keywords.
+ def subjects=(value)
+ unless value == :all or value.is_a?(Array)
+ raise ArgumentError, "Subjects must be an array or :all; not %s" % value
+ end
+
+ if value.is_a?(Array) and value.empty?
+ value = nil
+ end
+
+ @subjects = value
+ end
+end
+
diff --git a/lib/puppet/ssl/certificate_factory.rb b/lib/puppet/ssl/certificate_factory.rb
new file mode 100644
index 000000000..41155fd41
--- /dev/null
+++ b/lib/puppet/ssl/certificate_factory.rb
@@ -0,0 +1,145 @@
+require 'puppet/ssl'
+
+# The tedious class that does all the manipulations to the
+# certificate to correctly sign it. Yay.
+class Puppet::SSL::CertificateFactory
+ # How we convert from various units to the required seconds.
+ UNITMAP = {
+ "y" => 365 * 24 * 60 * 60,
+ "d" => 24 * 60 * 60,
+ "h" => 60 * 60,
+ "s" => 1
+ }
+
+ attr_reader :name, :cert_type, :csr, :issuer, :serial
+
+ def initialize(cert_type, csr, issuer, serial)
+ @cert_type, @csr, @issuer, @serial = cert_type, csr, issuer, serial
+
+ @name = @csr.subject
+ end
+
+ # Actually generate our certificate.
+ def result
+ @cert = OpenSSL::X509::Certificate.new
+
+ @cert.version = 2 # X509v3
+ @cert.subject = @csr.subject
+ @cert.issuer = @issuer.subject
+ @cert.public_key = @csr.public_key
+ @cert.serial = @serial
+
+ build_extensions()
+
+ set_ttl
+
+ @cert
+ end
+
+ private
+
+ # This is pretty ugly, but I'm not really sure it's even possible to do
+ # it any other way.
+ def build_extensions
+ @ef = OpenSSL::X509::ExtensionFactory.new
+
+ @ef.subject_certificate = @cert
+
+ if @issuer.is_a?(OpenSSL::X509::Request) # It's a self-signed cert
+ @ef.issuer_certificate = @cert
+ else
+ @ef.issuer_certificate = @issuer
+ end
+
+ @subject_alt_name = []
+ @key_usage = nil
+ @ext_key_usage = nil
+ @extensions = []
+
+ method = "add_#{@cert_type.to_s}_extensions"
+
+ begin
+ send(method)
+ rescue NoMethodError
+ raise ArgumentError, "%s is an invalid certificate type" % @cert_type
+ end
+
+ @extensions << @ef.create_extension("nsComment", "Puppet Ruby/OpenSSL Generated Certificate")
+ @extensions << @ef.create_extension("basicConstraints", @basic_constraint, true)
+ @extensions << @ef.create_extension("subjectKeyIdentifier", "hash")
+ @extensions << @ef.create_extension("keyUsage", @key_usage.join(",")) if @key_usage
+ @extensions << @ef.create_extension("extendedKeyUsage", @ext_key_usage.join(",")) if @ext_key_usage
+ @extensions << @ef.create_extension("subjectAltName", @subject_alt_name.join(",")) if ! @subject_alt_name.empty?
+
+ @cert.extensions = @extensions
+
+ # for some reason this _must_ be the last extension added
+ @extensions << @ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always") if @cert_type == :ca
+ end
+
+ # TTL for new certificates in seconds. If config param :ca_ttl is set,
+ # use that, otherwise use :ca_days for backwards compatibility
+ def ttl
+ ttl = Puppet.settings[:ca_ttl]
+
+ return ttl unless ttl.is_a?(String)
+
+ raise ArgumentError, "Invalid ca_ttl #{ttl}" unless ttl =~ /^(\d+)(y|d|h|s)$/
+
+ return $1.to_i * UNITMAP[$2]
+ end
+
+ def set_ttl
+ # Make the certificate valid as of yesterday, because
+ # so many people's clocks are out of sync.
+ from = Time.now - (60*60*24)
+ @cert.not_before = from
+ @cert.not_after = from + ttl
+ end
+
+ # Woot! We're a CA.
+ def add_ca_extensions
+ @basic_constraint = "CA:TRUE"
+ @key_usage = %w{cRLSign keyCertSign}
+ end
+
+ # We're a terminal CA, probably not self-signed.
+ def add_terminalsubca_extensions
+ @basic_constraint = "CA:TRUE,pathlen:0"
+ @key_usage = %w{cRLSign keyCertSign}
+ end
+
+ # We're a normal server.
+ def add_server_extensions
+ @basic_constraint = "CA:FALSE"
+ dnsnames = Puppet[:certdnsnames]
+ name = @name.to_s.sub(%r{/CN=},'')
+ if dnsnames != ""
+ dnsnames.split(':').each { |d| @subject_alt_name << 'DNS:' + d }
+ @subject_alt_name << 'DNS:' + name # Add the fqdn as an alias
+ elsif name == Facter.value(:fqdn) # we're a CA server, and thus probably the server
+ @subject_alt_name << 'DNS:' + "puppet" # Add 'puppet' as an alias
+ @subject_alt_name << 'DNS:' + name # Add the fqdn as an alias
+ @subject_alt_name << 'DNS:' + name.sub(/^[^.]+./, "puppet.") # add puppet.domain as an alias
+ end
+ @key_usage = %w{digitalSignature keyEncipherment}
+ @ext_key_usage = %w{serverAuth clientAuth emailProtection}
+ end
+
+ # Um, no idea.
+ def add_ocsp_extensions
+ @basic_constraint = "CA:FALSE"
+ @key_usage = %w{nonRepudiation digitalSignature}
+ @ext_key_usage = %w{serverAuth OCSPSigning}
+ end
+
+ # Normal client.
+ def add_client_extensions
+ @basic_constraint = "CA:FALSE"
+ @key_usage = %w{nonRepudiation digitalSignature keyEncipherment}
+ @ext_key_usage = %w{clientAuth emailProtection}
+
+ @extensions << @ef.create_extension("nsCertType", "client,email")
+ end
+end
+
diff --git a/lib/puppet/ssl/certificate_request.rb b/lib/puppet/ssl/certificate_request.rb
new file mode 100644
index 000000000..6a0464a33
--- /dev/null
+++ b/lib/puppet/ssl/certificate_request.rb
@@ -0,0 +1,51 @@
+require 'puppet/ssl/base'
+
+# Manage certificate requests.
+class Puppet::SSL::CertificateRequest < Puppet::SSL::Base
+ wraps OpenSSL::X509::Request
+
+ extend Puppet::Indirector
+ indirects :certificate_request, :terminus_class => :file
+
+ # Convert a string into an instance.
+ def self.from_s(string)
+ instance = wrapped_class.new(string)
+ name = instance.subject.to_s.sub(/\/CN=/i, '').downcase
+ result = new(name)
+ result.content = instance
+ result
+ end
+
+ # Because of how the format handler class is included, this
+ # can't be in the base class.
+ def self.supported_formats
+ [:s]
+ end
+
+ # How to create a certificate request with our system defaults.
+ def generate(key)
+ Puppet.info "Creating a new SSL certificate request for %s" % name
+
+ # Support either an actual SSL key, or a Puppet key.
+ key = key.content if key.is_a?(Puppet::SSL::Key)
+
+ csr = OpenSSL::X509::Request.new
+ csr.version = 0
+ csr.subject = OpenSSL::X509::Name.new([["CN", name]])
+ csr.public_key = key.public_key
+ csr.sign(key, OpenSSL::Digest::MD5.new)
+
+ raise Puppet::Error, "CSR sign verification failed; you need to clean the certificate request for %s on the server" % name unless csr.verify(key.public_key)
+
+ @content = csr
+ end
+
+ def save(args = {})
+ super()
+
+ # Try to autosign the CSR.
+ if ca = Puppet::SSL::CertificateAuthority.instance
+ ca.autosign
+ end
+ end
+end
diff --git a/lib/puppet/ssl/certificate_revocation_list.rb b/lib/puppet/ssl/certificate_revocation_list.rb
new file mode 100644
index 000000000..f3c1a348a
--- /dev/null
+++ b/lib/puppet/ssl/certificate_revocation_list.rb
@@ -0,0 +1,86 @@
+require 'puppet/ssl/base'
+require 'puppet/indirector'
+
+# Manage the CRL.
+class Puppet::SSL::CertificateRevocationList < Puppet::SSL::Base
+ wraps OpenSSL::X509::CRL
+
+ extend Puppet::Indirector
+ indirects :certificate_revocation_list, :terminus_class => :file
+
+ # Convert a string into an instance.
+ def self.from_s(string)
+ instance = wrapped_class.new(string)
+ result = new('foo') # The name doesn't matter
+ result.content = instance
+ result
+ end
+
+ # Because of how the format handler class is included, this
+ # can't be in the base class.
+ def self.supported_formats
+ [:s]
+ end
+
+ # Knows how to create a CRL with our system defaults.
+ def generate(cert, cakey)
+ Puppet.info "Creating a new certificate revocation list"
+ @content = wrapped_class.new
+ @content.issuer = cert.subject
+ @content.version = 1
+
+ # Init the CRL number.
+ crlNum = OpenSSL::ASN1::Integer(0)
+ @content.extensions = [OpenSSL::X509::Extension.new("crlNumber", crlNum)]
+
+ # Set last/next update
+ @content.last_update = Time.now
+ # Keep CRL valid for 5 years
+ @content.next_update = Time.now + 5 * 365*24*60*60
+
+ @content.sign(cakey, OpenSSL::Digest::SHA1.new)
+
+ @content
+ end
+
+ # The name doesn't actually matter; there's only one CRL.
+ # We just need the name so our Indirector stuff all works more easily.
+ def initialize(fakename)
+ raise Puppet::Error, "Cannot manage the CRL when :cacrl is set to false" if [false, "false"].include?(Puppet[:cacrl])
+
+ @name = "crl"
+ end
+
+ # Revoke the certificate with serial number SERIAL issued by this
+ # CA, then write the CRL back to disk. The REASON must be one of the
+ # OpenSSL::OCSP::REVOKED_* reasons
+ def revoke(serial, cakey, reason = OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE)
+ Puppet.notice "Revoked certificate with serial %s" % serial
+ time = Time.now
+
+ # Add our revocation to the CRL.
+ revoked = OpenSSL::X509::Revoked.new
+ revoked.serial = serial
+ revoked.time = time
+ enum = OpenSSL::ASN1::Enumerated(reason)
+ ext = OpenSSL::X509::Extension.new("CRLReason", enum)
+ revoked.add_extension(ext)
+ @content.add_revoked(revoked)
+
+ # Increment the crlNumber
+ e = @content.extensions.find { |e| e.oid == 'crlNumber' }
+ ext = @content.extensions.reject { |e| e.oid == 'crlNumber' }
+ crlNum = OpenSSL::ASN1::Integer(e ? e.value.to_i + 1 : 0)
+ ext << OpenSSL::X509::Extension.new("crlNumber", crlNum)
+ @content.extensions = ext
+
+ # Set last/next update
+ @content.last_update = time
+ # Keep CRL valid for 5 years
+ @content.next_update = time + 5 * 365*24*60*60
+
+ @content.sign(cakey, OpenSSL::Digest::SHA1.new)
+
+ save
+ end
+end
diff --git a/lib/puppet/ssl/host.rb b/lib/puppet/ssl/host.rb
new file mode 100644
index 000000000..a65490c40
--- /dev/null
+++ b/lib/puppet/ssl/host.rb
@@ -0,0 +1,258 @@
+require 'puppet/ssl'
+require 'puppet/ssl/key'
+require 'puppet/ssl/certificate'
+require 'puppet/ssl/certificate_request'
+require 'puppet/ssl/certificate_revocation_list'
+require 'puppet/util/cacher'
+
+# The class that manages all aspects of our SSL certificates --
+# private keys, public keys, requests, etc.
+class Puppet::SSL::Host
+ # Yay, ruby's strange constant lookups.
+ Key = Puppet::SSL::Key
+ Certificate = Puppet::SSL::Certificate
+ CertificateRequest = Puppet::SSL::CertificateRequest
+ CertificateRevocationList = Puppet::SSL::CertificateRevocationList
+
+ attr_reader :name
+ attr_accessor :ca
+
+ attr_writer :key, :certificate, :certificate_request
+
+ class << self
+ include Puppet::Util::Cacher
+
+ cached_attr(:localhost) do
+ result = new()
+ result.generate unless result.certificate
+ result.key # Make sure it's read in
+ result
+ end
+ end
+
+ CA_NAME = "ca"
+ # This is the constant that people will use to mark that a given host is
+ # a certificate authority.
+ def self.ca_name
+ CA_NAME
+ end
+
+ class << self
+ attr_reader :ca_location
+ end
+
+ # Configure how our various classes interact with their various terminuses.
+ def self.configure_indirection(terminus, cache = nil)
+ Certificate.terminus_class = terminus
+ CertificateRequest.terminus_class = terminus
+ CertificateRevocationList.terminus_class = terminus
+
+ if cache
+ # This is weird; we don't actually cache our keys, we
+ # use what would otherwise be the cache as our normal
+ # terminus.
+ Key.terminus_class = cache
+ else
+ Key.terminus_class = terminus
+ end
+
+ if cache
+ Certificate.cache_class = cache
+ CertificateRequest.cache_class = cache
+ CertificateRevocationList.cache_class = cache
+ else
+ # Make sure we have no cache configured. puppetmasterd
+ # switches the configurations around a bit, so it's important
+ # that we specify the configs for absolutely everything, every
+ # time.
+ Certificate.cache_class = nil
+ CertificateRequest.cache_class = nil
+ CertificateRevocationList.cache_class = nil
+ end
+ end
+
+ CA_MODES = {
+ # Our ca is local, so we use it as the ultimate source of information
+ # And we cache files locally.
+ :local => [:ca, :file],
+ # We're a remote CA client.
+ :remote => [:rest, :file],
+ # We are the CA, so we don't have read/write access to the normal certificates.
+ :only => [:ca],
+ # We have no CA, so we just look in the local file store.
+ :none => [:file]
+ }
+
+ # Specify how we expect to interact with our certificate authority.
+ def self.ca_location=(mode)
+ raise ArgumentError, "CA Mode can only be %s" % CA_MODES.collect { |m| m.to_s }.join(", ") unless CA_MODES.include?(mode)
+
+ @ca_location = mode
+
+ configure_indirection(*CA_MODES[@ca_location])
+ end
+
+ # Remove all traces of a given host
+ def self.destroy(name)
+ [Key, Certificate, CertificateRequest].inject(false) do |result, klass|
+ if klass.destroy(name)
+ result = true
+ end
+ result
+ end
+ end
+
+ # Search for more than one host, optionally only specifying
+ # an interest in hosts with a given file type.
+ # This just allows our non-indirected class to have one of
+ # indirection methods.
+ def self.search(options = {})
+ classes = [Key, CertificateRequest, Certificate]
+ if klass = options[:for]
+ classlist = [klass].flatten
+ else
+ classlist = [Key, CertificateRequest, Certificate]
+ end
+
+ # Collect the results from each class, flatten them, collect all of the names, make the name list unique,
+ # then create a Host instance for each one.
+ classlist.collect { |klass| klass.search }.flatten.collect { |r| r.name }.uniq.collect do |name|
+ new(name)
+ end
+ end
+
+ # Is this a ca host, meaning that all of its files go in the CA location?
+ def ca?
+ ca
+ end
+
+ def key
+ return nil unless @key ||= Key.find(name)
+ @key
+ end
+
+ # This is the private key; we can create it from scratch
+ # with no inputs.
+ def generate_key
+ @key = Key.new(name)
+ @key.generate
+ begin
+ @key.save
+ rescue
+ @key = nil
+ raise
+ end
+ true
+ end
+
+ def certificate_request
+ return nil unless @certificate_request ||= CertificateRequest.find(name)
+ @certificate_request
+ end
+
+ # Our certificate request requires the key but that's all.
+ def generate_certificate_request
+ generate_key unless key
+ @certificate_request = CertificateRequest.new(name)
+ @certificate_request.generate(key.content)
+ begin
+ @certificate_request.save
+ rescue
+ @certificate_request = nil
+ raise
+ end
+
+ return true
+ end
+
+ def certificate
+ unless @certificate
+ # get the CA cert first, since it's required for the normal cert
+ # to be of any use.
+ return nil unless Certificate.find("ca") unless ca?
+ @certificate = Certificate.find(name)
+ end
+ @certificate
+ end
+
+ # Generate all necessary parts of our ssl host.
+ def generate
+ generate_key unless key
+ generate_certificate_request unless certificate_request
+
+ # If we can get a CA instance, then we're a valid CA, and we
+ # should use it to sign our request; else, just try to read
+ # the cert.
+ if ! certificate() and ca = Puppet::SSL::CertificateAuthority.instance
+ ca.sign(self.name)
+ end
+ end
+
+ def initialize(name = nil)
+ @name = (name || Puppet[:certname]).downcase
+ @key = @certificate = @certificate_request = nil
+ @ca = (name == self.class.ca_name)
+ end
+
+ # Extract the public key from the private key.
+ def public_key
+ key.content.public_key
+ end
+
+ # Create/return a store that uses our SSL info to validate
+ # connections.
+ def ssl_store(purpose = OpenSSL::X509::PURPOSE_ANY)
+ unless defined?(@ssl_store) and @ssl_store
+ @ssl_store = OpenSSL::X509::Store.new
+ @ssl_store.purpose = purpose
+
+ # Use the file path here, because we don't want to cause
+ # a lookup in the middle of setting our ssl connection.
+ @ssl_store.add_file(Puppet[:localcacert])
+
+ # If there's a CRL, add it to our store.
+ if crl = Puppet::SSL::CertificateRevocationList.find("ca")
+ @ssl_store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK
+ @ssl_store.add_crl(crl.content)
+ end
+ return @ssl_store
+ end
+ @ssl_store
+ end
+
+ # Attempt to retrieve a cert, if we don't already have one.
+ def wait_for_cert(time)
+ return if certificate
+ begin
+ generate
+
+ return if certificate
+ rescue StandardError => detail
+ Puppet.err "Could not request certificate: %s" % detail.to_s
+ if time < 1
+ puts "Exiting; failed to retrieve certificate and watiforcert is disabled"
+ exit(1)
+ else
+ sleep(time)
+ end
+ retry
+ end
+
+ if time < 1
+ puts "Exiting; no certificate found and waitforcert is disabled"
+ exit(1)
+ end
+
+ while true do
+ sleep time
+ begin
+ break if certificate
+ Puppet.notice "Did not receive certificate"
+ rescue StandardError => detail
+ Puppet.err "Could not request certificate: %s" % detail.to_s
+ end
+ end
+ end
+end
+
+require 'puppet/ssl/certificate_authority'
diff --git a/lib/puppet/ssl/inventory.rb b/lib/puppet/ssl/inventory.rb
new file mode 100644
index 000000000..38cbf46e9
--- /dev/null
+++ b/lib/puppet/ssl/inventory.rb
@@ -0,0 +1,52 @@
+require 'puppet/ssl'
+require 'puppet/ssl/certificate'
+
+# Keep track of all of our known certificates.
+class Puppet::SSL::Inventory
+ attr_reader :path
+
+ # Add a certificate to our inventory.
+ def add(cert)
+ cert = cert.content if cert.is_a?(Puppet::SSL::Certificate)
+
+ # Create our file, if one does not already exist.
+ rebuild unless FileTest.exist?(@path)
+
+ Puppet.settings.write(:cert_inventory, "a") do |f|
+ f.print format(cert)
+ end
+ end
+
+ # Format our certificate for output.
+ def format(cert)
+ iso = '%Y-%m-%dT%H:%M:%S%Z'
+ return "0x%04x %s %s %s\n" % [cert.serial, cert.not_before.strftime(iso), cert.not_after.strftime(iso), cert.subject]
+ end
+
+ def initialize
+ @path = Puppet[:cert_inventory]
+ end
+
+ # Rebuild the inventory from scratch. This should happen if
+ # the file is entirely missing or if it's somehow corrupted.
+ def rebuild
+ Puppet.notice "Rebuilding inventory file"
+
+ Puppet.settings.write(:cert_inventory) do |f|
+ f.print "# Inventory of signed certificates\n# SERIAL NOT_BEFORE NOT_AFTER SUBJECT\n"
+ end
+
+ Puppet::SSL::Certificate.search("*").each { |cert| add(cert) }
+ end
+
+ # Find the serial number for a given certificate.
+ def serial(name)
+ return nil unless FileTest.exist?(@path)
+
+ File.readlines(@path).each do |line|
+ next unless line =~ /^(\S+).+\/CN=#{name}$/
+
+ return Integer($1)
+ end
+ end
+end
diff --git a/lib/puppet/ssl/key.rb b/lib/puppet/ssl/key.rb
new file mode 100644
index 000000000..d91df03f6
--- /dev/null
+++ b/lib/puppet/ssl/key.rb
@@ -0,0 +1,56 @@
+require 'puppet/ssl/base'
+require 'puppet/indirector'
+
+# Manage private and public keys as a pair.
+class Puppet::SSL::Key < Puppet::SSL::Base
+ wraps OpenSSL::PKey::RSA
+
+ extend Puppet::Indirector
+ indirects :key, :terminus_class => :file
+
+ # Because of how the format handler class is included, this
+ # can't be in the base class.
+ def self.supported_formats
+ [:s]
+ end
+
+ attr_accessor :password_file
+
+ # Knows how to create keys with our system defaults.
+ def generate
+ Puppet.info "Creating a new SSL key for %s" % name
+ @content = OpenSSL::PKey::RSA.new(Puppet[:keylength].to_i)
+ end
+
+ def initialize(name)
+ super
+
+ if ca?
+ @password_file = Puppet[:capass]
+ else
+ @password_file = Puppet[:passfile]
+ end
+ end
+
+ def password
+ return nil unless password_file and FileTest.exist?(password_file)
+
+ ::File.read(password_file)
+ end
+
+ # Optionally support specifying a password file.
+ def read(path)
+ return super unless password_file
+
+ #@content = wrapped_class.new(::File.read(path), password)
+ @content = wrapped_class.new(::File.read(path), password)
+ end
+
+ def to_s
+ if pass = password
+ @content.export(OpenSSL::Cipher::DES.new(:EDE3, :CBC), pass)
+ else
+ return super
+ end
+ end
+end
diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb
index f3defb7a2..30f86a9d7 100644
--- a/lib/puppet/transaction.rb
+++ b/lib/puppet/transaction.rb
@@ -154,13 +154,13 @@ class Transaction
# contained resources might never get cleaned up.
def cleanup
if defined? @generated
- relationship_graph.remove_resource(*@generated)
+ catalog.remove_resource(*@generated)
end
end
# Copy an important relationships from the parent to the newly-generated
# child resource.
- def copy_relationships(resource, children)
+ def make_parent_child_relationship(resource, children)
depthfirst = resource.depthfirst?
children.each do |gen_child|
@@ -169,7 +169,7 @@ class Transaction
else
edge = [resource, gen_child]
end
- relationship_graph.add_resource(gen_child) unless relationship_graph.resource(gen_child.ref)
+ relationship_graph.add_vertex(gen_child)
unless relationship_graph.edge?(edge[1], edge[0])
relationship_graph.add_edge(*edge)
@@ -188,24 +188,7 @@ class Transaction
# See if the resource generates new resources at evaluation time.
def eval_generate(resource)
- if resource.respond_to?(:eval_generate)
- begin
- children = resource.eval_generate
- rescue => detail
- if Puppet[:trace]
- puts detail.backtrace
- end
- resource.err "Failed to generate additional resources during transaction: %s" %
- detail
- return nil
- end
-
- if children
- children.each { |child| child.finish }
- @generated += children
- return children
- end
- end
+ generate_additional_resources(resource, :eval_generate)
end
# Evaluate a single resource.
@@ -245,13 +228,6 @@ class Transaction
end
end
- # Create a child/parent relationship. We do this after everything else because
- # we want explicit relationships to be able to override automatic relationships,
- # including this one.
- if children
- copy_relationships(resource, children)
- end
-
# A bit of hackery here -- if skipcheck is true, then we're the
# top-level resource. If that's the case, then make sure all of
# the changes list this resource as a proxy. This is really only
@@ -355,39 +331,34 @@ class Transaction
return skip
end
- # Collect any dynamically generated resources.
+ # A general method for recursively generating new resources from a
+ # resource.
+ def generate_additional_resources(resource, method)
+ return [] unless resource.respond_to?(method)
+ begin
+ made = resource.send(method)
+ rescue => detail
+ resource.err "Failed to generate additional resources using '%s': %s" % [method, detail]
+ end
+ return [] unless made
+ made = [made] unless made.is_a?(Array)
+ made.uniq!
+ made.each do |res|
+ @catalog.add_resource(res) { |r| r.finish }
+ end
+ make_parent_child_relationship(resource, made)
+ made
+ end
+
+ # Collect any dynamically generated resources. This method is called
+ # before the transaction starts.
def generate
list = @catalog.vertices
-
- # Store a list of all generated resources, so that we can clean them up
- # after the transaction closes.
- @generated = []
-
newlist = []
while ! list.empty?
list.each do |resource|
- if resource.respond_to?(:generate)
- begin
- made = resource.generate
- rescue => detail
- resource.err "Failed to generate additional resources: %s" %
- detail
- end
- next unless made
- unless made.is_a?(Array)
- made = [made]
- end
- made.uniq!
- made.each do |res|
- @catalog.add_resource(res)
- res.catalog = catalog
- newlist << res
- @generated << res
- res.finish
- end
- end
+ newlist += generate_additional_resources(resource, :generate)
end
- list.clear
list = newlist
newlist = []
end
@@ -429,10 +400,10 @@ class Transaction
# this should only be called by a Puppet::Type::Component resource now
# and it should only receive an array
def initialize(resources)
- if resources.is_a?(Puppet::Node::Catalog)
+ if resources.is_a?(Puppet::Resource::Catalog)
@catalog = resources
- elsif resources.is_a?(Puppet::PGraph)
- raise "Transactions should get catalogs now, not PGraph"
+ elsif resources.is_a?(Puppet::SimpleGraph)
+ raise "Transactions should get catalogs now, not SimpleGraph"
else
raise "Transactions require catalogs"
end
@@ -534,23 +505,13 @@ class Transaction
if Puppet[:report]
begin
- reportclient().report(report)
+ report.save()
rescue => detail
Puppet.err "Reporting failed: %s" % detail
end
end
end
- def reportclient
- unless defined? @reportclient
- @reportclient = Puppet::Network::Client.report.new(
- :Server => Puppet[:reportserver]
- )
- end
-
- @reportclient
- end
-
# Roll all completed changes back.
def rollback
@targets.clear
diff --git a/lib/puppet/transaction/report.rb b/lib/puppet/transaction/report.rb
index 89da7ed9c..e5b8650bb 100644
--- a/lib/puppet/transaction/report.rb
+++ b/lib/puppet/transaction/report.rb
@@ -11,7 +11,13 @@ class Puppet::Transaction::Report
indirects :report, :terminus_class => :processor
attr_accessor :logs, :metrics, :time, :host
-
+
+ # This is necessary since Marshall doesn't know how to
+ # dump hash with default proc (see below @records)
+ def self.default_format
+ :yaml
+ end
+
def <<(msg)
@logs << msg
return self
diff --git a/lib/puppet/transportable.rb b/lib/puppet/transportable.rb
index 41c51fde6..052f6bab1 100644
--- a/lib/puppet/transportable.rb
+++ b/lib/puppet/transportable.rb
@@ -1,5 +1,5 @@
require 'puppet'
-require 'puppet/resource_reference'
+require 'puppet/resource/reference'
require 'yaml'
module Puppet
@@ -36,7 +36,7 @@ module Puppet
def ref
unless defined? @ref
- @ref = Puppet::ResourceReference.new(@type, @name)
+ @ref = Puppet::Resource::Reference.new(@type, @name)
end
@ref.to_s
end
@@ -78,6 +78,14 @@ module Puppet
]
end
+ # Create a normalized resource from our TransObject.
+ def to_resource
+ result = Puppet::Resource.new(type, name, @params.dup)
+ result.tag(*tags)
+
+ result
+ end
+
def to_yaml_properties
instance_variables.reject { |v| %w{@ref}.include?(v) }
end
@@ -86,12 +94,8 @@ module Puppet
ref
end
- def to_type
- if typeklass = Puppet::Type.type(self.type)
- return typeklass.create(self)
- else
- return to_component
- end
+ def to_ral
+ to_resource.to_ral
end
end
@@ -181,7 +185,7 @@ module Puppet
# Create a resource graph from our structure.
def to_catalog(clear_on_failure = true)
- catalog = Puppet::Node::Catalog.new(Facter.value("hostname"))
+ catalog = Puppet::Resource::Catalog.new(Facter.value("hostname"))
# This should really use the 'delve' method, but this
# whole class is going away relatively soon, hopefully,
@@ -189,13 +193,13 @@ module Puppet
delver = proc do |obj|
obj.catalog = catalog
unless container = catalog.resource(obj.to_ref)
- container = obj.to_type
+ container = obj.to_ral
catalog.add_resource container
end
obj.each do |child|
child.catalog = catalog
unless resource = catalog.resource(child.to_ref)
- resource = child.to_type
+ resource = child.to_ral
catalog.add_resource resource
end
@@ -221,11 +225,11 @@ module Puppet
def to_ref
unless defined? @ref
if self.type and self.name
- @ref = Puppet::ResourceReference.new(self.type, self.name)
+ @ref = Puppet::Resource::Reference.new(self.type, self.name)
elsif self.type and ! self.name # This is old-school node types
- @ref = Puppet::ResourceReference.new("node", self.type)
+ @ref = Puppet::Resource::Reference.new("node", self.type)
elsif ! self.type and self.name
- @ref = Puppet::ResourceReference.new("component", self.name)
+ @ref = Puppet::Resource::Reference.new("component", self.name)
else
@ref = nil
end
@@ -233,18 +237,14 @@ module Puppet
@ref.to_s if @ref
end
- def to_type
- Puppet.debug("TransBucket '%s' has no type" % @name) unless defined? @type
+ def to_ral
+ to_resource.to_ral
+ end
- # Nodes have the same name and type
- trans = TransObject.new(to_ref, :component)
- if defined? @parameters
- @parameters.each { |param,value|
- Puppet.debug "Defining %s on %s" % [param, to_ref]
- trans[param] = value
- }
- end
- return Puppet::Type::Component.create(trans)
+ # Create a normalized resource from our TransObject.
+ def to_resource
+ params = defined?(@parameters) ? @parameters.dup : {}
+ Puppet::Resource.new(type, name, params)
end
def param(param,value)
diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb
index b57c74b95..706ea1386 100644
--- a/lib/puppet/type.rb
+++ b/lib/puppet/type.rb
@@ -9,7 +9,8 @@ require 'puppet/metatype/manager'
require 'puppet/util/errors'
require 'puppet/util/log_paths'
require 'puppet/util/logging'
-require 'puppet/resource_reference'
+require 'puppet/resource/reference'
+require 'puppet/util/cacher'
# see the bottom of the file for the rest of the inclusions
@@ -19,6 +20,7 @@ class Type
include Puppet::Util::Errors
include Puppet::Util::LogPaths
include Puppet::Util::Logging
+ include Puppet::Util::Cacher
###############################
# Code related to resource type attributes.
@@ -33,12 +35,11 @@ class Type
properties()
end
- # All parameters, in the appropriate order. The namevar comes first,
- # then the properties, then the params and metaparams in the order they
- # were specified in the files.
+ # All parameters, in the appropriate order. The namevar comes first, then
+ # the provider, then the properties, and finally the params and metaparams
+ # in the order they were specified in the files.
def self.allattrs
- # now get all of the arguments, in a specific order
- # Cache this, since it gets called so many times
+ # Cache this, since it gets called multiple times
namevar = self.namevar
order = [namevar]
@@ -50,7 +51,7 @@ class Type
self.metaparams].flatten.reject { |param|
# we don't want our namevar in there multiple times
param == namevar
- }
+ }
order.flatten!
@@ -95,10 +96,6 @@ class Type
when @validproperties.include?(attr): :property
when @paramhash.include?(attr): :param
when @@metaparamhash.include?(attr): :meta
- else
- raise Puppet::DevError,
- "Invalid attribute '%s' for class '%s'" %
- [attr, self.name]
end
end
@@ -122,30 +119,6 @@ class Type
end
end
- # A similar function but one that yields the class and type.
- # This is mainly so that setdefaults doesn't call quite so many functions.
- def self.eachattr(*ary)
- if ary.empty?
- ary = nil
- end
-
- # We have to do this in a specific order, so that defaults are
- # created in that order (e.g., providers should be set up before
- # anything else).
- allattrs.each do |name|
- next unless ary.nil? or ary.include?(name)
- if obj = @properties.find { |p| p.name == name }
- yield obj, :property
- elsif obj = @parameters.find { |p| p.name == name }
- yield obj, :param
- elsif obj = @@metaparams.find { |p| p.name == name }
- yield obj, :meta
- else
- raise Puppet::DevError, "Could not find parameter %s" % name
- end
- end
- end
-
def self.eachmetaparam
@@metaparams.each { |p| yield p.name }
end
@@ -184,8 +157,6 @@ class Type
end
end
end
-
- # If this param handles relationships, store that information
end
# Is the parameter in question a meta-parameter?
@@ -274,15 +245,6 @@ class Type
param.isnamevar if options[:namevar]
- # These might be enabled later.
-# define_method(name) do
-# @parameters[name].value
-# end
-#
-# define_method(name.to_s + "=") do |value|
-# newparam(param, value)
-# end
-
if param.isnamevar?
@namevar = param.name
end
@@ -347,14 +309,6 @@ class Type
@properties << prop
end
-# define_method(name) do
-# @parameters[name].should
-# end
-#
-# define_method(name.to_s + "=") do |value|
-# newproperty(name, :should => value)
-# end
-
return prop
end
@@ -423,38 +377,6 @@ class Type
end
end
- # fix any namevar => param translations
- def argclean(oldhash)
- # This duplication is here because it might be a transobject.
- hash = oldhash.dup.to_hash
-
- if hash.include?(:resource)
- hash.delete(:resource)
- end
- namevar = self.class.namevar
-
- # Do a simple translation for those cases where they've passed :name
- # but that's not our namevar
- if hash.include? :name and namevar != :name
- if hash.include? namevar
- raise ArgumentError, "Cannot provide both name and %s" % namevar
- end
- hash[namevar] = hash[:name]
- hash.delete(:name)
- end
-
- # Make sure we have a name, one way or another
- unless hash.include? namevar
- if defined? @title and @title
- hash[namevar] = @title
- else
- raise Puppet::Error, "Was not passed a namevar or title"
- end
- end
-
- return hash
- end
-
# Return either the attribute alias or the attribute.
def attr_alias(name)
name = symbolize(name)
@@ -489,7 +411,7 @@ class Type
name = attr_alias(name)
unless self.class.validattr?(name)
- raise TypeError.new("Invalid parameter %s(%s)" % [name, name.inspect])
+ fail("Invalid parameter %s(%s)" % [name, name.inspect])
end
if name == :name
@@ -512,7 +434,7 @@ class Type
name = attr_alias(name)
unless self.class.validattr?(name)
- raise TypeError.new("Invalid parameter %s" % [name])
+ fail("Invalid parameter %s" % [name])
end
if name == :name
@@ -551,6 +473,12 @@ class Type
}
end
+ # Let the catalog determine whether a given cached value is
+ # still valid or has expired.
+ def expirer
+ catalog
+ end
+
# retrieve the 'should' value for a specified property
def should(name)
name = attr_alias(name)
@@ -601,10 +529,7 @@ class Type
# return the value of a parameter
def parameter(name)
- unless name.is_a? Symbol
- name = name.intern
- end
- return @parameters[name].value
+ @parameters[name.to_sym]
end
# Is the named property defined?
@@ -615,8 +540,9 @@ class Type
return @parameters.include?(name)
end
- # return an actual type by name; to return the value, use 'inst[name]'
- # FIXME this method should go away
+ # Return an actual property instance by name; to return the value, use 'resource[param]'
+ # LAK:NOTE(20081028) Since the 'parameter' method is now a superset of this method,
+ # this one should probably go away at some point.
def property(name)
if obj = @parameters[symbolize(name)] and obj.is_a?(Puppet::Property)
return obj
@@ -625,36 +551,21 @@ class Type
end
end
-# def set(name, value)
-# send(name.to_s + "=", value)
-# end
-#
-# def get(name)
-# send(name)
-# end
-
# For any parameters or properties that have defaults and have not yet been
# set, set them now. This method can be handed a list of attributes,
# and if so it will only set defaults for those attributes.
- def setdefaults(*ary)
- #self.class.eachattr(*ary) { |klass, type|
- self.class.eachattr(*ary) { |klass, type|
- # not many attributes will have defaults defined, so we short-circuit
- # those away
- next unless klass.method_defined?(:default)
- next if @parameters[klass.name]
-
- next unless obj = self.newattr(klass)
-
- # We have to check for nil values, not "truth", so we allow defaults
- # to false.
- value = obj.default and ! value.nil?
- if ! value.nil?
- obj.value = value
- else
- @parameters.delete(obj.name)
- end
- }
+ def set_default(attr)
+ return unless klass = self.class.attrclass(attr)
+ return unless klass.method_defined?(:default)
+ return if @parameters.include?(klass.name)
+
+ return unless parameter = newattr(klass.name)
+
+ if value = parameter.default and ! value.nil?
+ parameter.value = value
+ else
+ @parameters.delete(parameter.name)
+ end
end
# Convert our object to a hash. This just includes properties.
@@ -706,7 +617,7 @@ class Type
###############################
# Code related to the closure-like behaviour of the resource classes.
- attr_writer :implicit
+ attr_accessor :implicit
# Is this type's name isomorphic with the object? That is, if the
# name conflicts, does it necessarily mean that the objects conflict?
@@ -756,6 +667,9 @@ class Type
###############################
# Code related to the container behaviour.
+
+ # this is a retarded hack method to get around the difference between
+ # component children and file children
def self.depthfirst?
if defined? @depthfirst
return @depthfirst
@@ -768,19 +682,6 @@ class Type
self.class.depthfirst?
end
- # Add a hook for testing for recursion.
- def parentof?(child)
- if (self == child)
- debug "parent is equal to child"
- return true
- elsif defined? @parent and @parent.parentof?(child)
- debug "My parent is parent of child"
- return true
- else
- return false
- end
- end
-
# Remove an object. The argument determines whether the object's
# subscriptions get eliminated, too.
def remove(rmdeps = true)
@@ -790,7 +691,6 @@ class Type
obj.remove
end
@parameters.clear
- self.class.delete(self)
@parent = nil
@@ -814,13 +714,6 @@ class Type
raise Puppet::Error, "Provider %s is not functional on this platform" % provider.class.name
end
end
- #Puppet.err "Evaluating %s" % self.path.join(":")
- unless defined? @evalcount
- self.err "No evalcount defined on '%s' of type '%s'" %
- [self.title,self.class]
- @evalcount = 0
- end
- @evalcount += 1
# this only operates on properties, not properties + children
# it's important that we call retrieve() on the type instance,
@@ -977,18 +870,15 @@ class Type
# Code related to managing resource instances.
require 'puppet/transportable'
- # Make 'new' private, so people have to use create instead.
- class << self
- private :new
- end
-
# retrieve a named instance of the current type
def self.[](name)
+ raise "Global resource access is deprecated"
@objects[name] || @aliases[name]
end
# add an instance by name to the class list of instances
def self.[]=(name,object)
+ raise "Global resource storage is deprecated"
newobj = nil
if object.is_a?(Puppet::Type)
newobj = object
@@ -1020,6 +910,7 @@ class Type
# Create an alias. We keep these in a separate hash so that we don't encounter
# the objects multiple times when iterating over them.
def self.alias(name, obj)
+ raise "Global resource aliasing is deprecated"
if @objects.include?(name)
unless @objects[name] == obj
raise Puppet::Error.new(
@@ -1043,6 +934,7 @@ class Type
# remove all of the instances of a single type
def self.clear
+ raise "Global resource removal is deprecated"
if defined? @objects
@objects.each do |name, obj|
obj.remove(true)
@@ -1057,96 +949,14 @@ class Type
# Force users to call this, so that we can merge objects if
# necessary.
def self.create(args)
- # Don't modify the original hash; instead, create a duplicate and modify it.
- # We have to dup and use the ! so that it stays a TransObject if it is
- # one.
- hash = args.dup
- symbolizehash!(hash)
-
- # If we're the base class, then pass the info on appropriately
- if self == Puppet::Type
- type = nil
- if hash.is_a? Puppet::TransObject
- type = hash.type
- else
- # If we're using the type to determine object type, then delete it
- if type = hash[:type]
- hash.delete(:type)
- end
- end
-
- # If they've specified a type and called on the base, then
- # delegate to the subclass.
- if type
- if typeklass = self.type(type)
- return typeklass.create(hash)
- else
- raise Puppet::Error, "Unknown type %s" % type
- end
- else
- raise Puppet::Error, "No type found for %s" % hash.inspect
- end
- end
-
- # Handle this new object being implicit
- implicit = hash[:implicit] || false
- if hash.include?(:implicit)
- hash.delete(:implicit)
- end
-
- name = nil
- unless hash.is_a? Puppet::TransObject
- hash = self.hash2trans(hash)
- end
-
- # XXX This will have to change when transobjects change to using titles
- title = hash.name
-
- # if the object already exists
- if self.isomorphic? and retobj = self[title]
- # if only one of our objects is implicit, then it's easy to see
- # who wins -- the non-implicit one.
- if retobj.implicit? and ! implicit
- Puppet.notice "Removing implicit %s" % retobj.title
- # Remove all of the objects, but do not remove their subscriptions.
- retobj.remove(false)
-
- # now pass through and create the new object
- elsif implicit
- Puppet.debug "Ignoring implicit %s[%s]" % [self.name, title]
- return nil
- else
- raise Puppet::Error, "%s is already being managed" % retobj.ref
- end
- end
-
- # create it anew
- # if there's a failure, destroy the object if it got that far, but raise
- # the error.
- begin
- obj = new(hash)
- rescue => detail
- Puppet.err "Could not create %s: %s" % [title, detail.to_s]
- if obj
- obj.remove(true)
- elsif obj = self[title]
- obj.remove(true)
- end
- raise
- end
-
- if implicit
- obj.implicit = true
- end
-
- # Store the object by title
- self[obj.title] = obj
-
- return obj
+ # LAK:DEP Deprecation notice added 12/17/2008
+ Puppet.warning "Puppet::Type.create is deprecated; use Puppet::Type.new"
+ new(args)
end
# remove a specified object
def self.delete(resource)
+ raise "Global resource removal is deprecated"
return unless defined? @objects
if @objects.include?(resource.title)
@objects.delete(resource.title)
@@ -1167,6 +977,7 @@ class Type
# iterate across each of the type's instances
def self.each
+ raise "Global resource iteration is deprecated"
return unless defined? @objects
@objects.each { |name,instance|
yield instance
@@ -1175,55 +986,10 @@ class Type
# does the type have an object with the given name?
def self.has_key?(name)
+ raise "Global resource access is deprecated"
return @objects.has_key?(name)
end
- # Convert a hash to a TransObject.
- def self.hash2trans(hash)
- title = nil
- if hash.include? :title
- title = hash[:title]
- hash.delete(:title)
- elsif hash.include? self.namevar
- title = hash[self.namevar]
- hash.delete(self.namevar)
-
- if hash.include? :name
- raise ArgumentError, "Cannot provide both name and %s to %s" %
- [self.namevar, self.name]
- end
- elsif hash[:name]
- title = hash[:name]
- hash.delete :name
- end
-
- if catalog = hash[:catalog]
- hash.delete(:catalog)
- end
-
- raise(Puppet::Error, "You must specify a title for objects of type %s" % self.to_s) unless title
-
- if hash.include? :type
- unless self.validattr? :type
- hash.delete :type
- end
- end
-
- # okay, now make a transobject out of hash
- begin
- trans = Puppet::TransObject.new(title, self.name.to_s)
- trans.catalog = catalog if catalog
- hash.each { |param, value|
- trans[param] = value
- }
- rescue => detail
- raise Puppet::Error, "Could not create %s: %s" %
- [name, detail]
- end
-
- return trans
- end
-
# Retrieve all known instances. Either requires providers or must be overridden.
def self.instances
unless defined?(@providers) and ! @providers.empty?
@@ -1234,10 +1000,6 @@ class Type
provider_instances = {}
providers_by_source.collect do |provider|
provider.instances.collect do |instance|
- # First try to get the resource if it already exists
- # Skip instances that map to a managed resource with a different provider
- next if resource = self[instance.name] and resource.provider.class != instance.class
-
# We always want to use the "first" provider instance we find, unless the resource
# is already managed and has a different provider set
if other = provider_instances[instance.name]
@@ -1247,12 +1009,7 @@ class Type
end
provider_instances[instance.name] = instance
- if resource
- resource.provider = instance
- resource
- else
- create(:name => instance.name, :provider => instance, :check => :all)
- end
+ create(:name => instance.name, :provider => instance, :check => :all)
end
end.flatten.compact
end
@@ -1269,6 +1026,47 @@ class Type
end.compact
end
+ # Convert a simple hash into a Resource instance. This is a convenience method,
+ # so people can create RAL resources with a hash and get the same behaviour
+ # as we get internally when we use Resource instances.
+ # This should only be used directly from Ruby -- it's not used when going through
+ # normal Puppet usage.
+ def self.hash2resource(hash)
+ hash = hash.inject({}) { |result, ary| result[ary[0].to_sym] = ary[1]; result }
+
+ if title = hash[:title]
+ hash.delete(:title)
+ else
+ if self.namevar != :name
+ if hash.include?(:name) and hash.include?(self.namevar)
+ raise Puppet::Error, "Cannot provide both name and %s to resources of type %s" % [self.namevar, self.name]
+ end
+ if title = hash[self.namevar]
+ hash.delete(self.namevar)
+ end
+ end
+
+ unless title ||= hash[:name]
+ raise Puppet::Error, "You must specify a name or title for resources"
+ end
+ end
+
+
+ # Now create our resource.
+ resource = Puppet::Resource.new(self.name, title)
+ [:catalog, :implicit].each do |attribute|
+ if value = hash[attribute]
+ hash.delete(attribute)
+ resource.send(attribute.to_s + "=", value)
+ end
+ end
+
+ hash.each do |param, value|
+ resource[param] = value
+ end
+ return resource
+ end
+
# Create the path for logging and such.
def pathbuilder
if p = parent
@@ -1362,47 +1160,6 @@ class Type
}
end
end
-
- # We've got four relationship metaparameters, so this method is used
- # to reduce code duplication between them.
- def munge_relationship(param, values)
- # We need to support values passed in as an array or as a
- # resource reference.
- result = []
-
- # 'values' could be an array or a reference. If it's an array,
- # it could be an array of references or an array of arrays.
- if values.is_a?(Puppet::Type)
- result << [values.class.name, values.title]
- else
- unless values.is_a?(Array)
- devfail "Relationships must be resource references"
- end
- if values[0].is_a?(String) or values[0].is_a?(Symbol)
- # we're a type/title array reference
- values[0] = symbolize(values[0])
- result << values
- else
- # we're an array of stuff
- values.each do |value|
- if value.is_a?(Puppet::Type)
- result << [value.class.name, value.title]
- elsif value.is_a?(Array)
- value[0] = symbolize(value[0])
- result << value
- else
- devfail "Invalid relationship %s" % value.inspect
- end
- end
- end
- end
-
- if existing = self[param]
- result = existing + result
- end
-
- result
- end
newmetaparam(:loglevel) do
desc "Sets the level that information will be logged.
@@ -1476,9 +1233,6 @@ class Type
next
end
- # LAK:FIXME Old-school, add the alias to the class.
- @resource.class.alias(other, @resource)
-
# Newschool, add it to the catalog.
@resource.catalog.alias(@resource, other)
end
@@ -1519,16 +1273,22 @@ class Type
@subclasses << sub
end
- def munge(rels)
- @resource.munge_relationship(self.class.name, rels)
+ def munge(references)
+ references = [references] unless references.is_a?(Array)
+ references.collect do |ref|
+ if ref.is_a?(Puppet::Resource::Reference)
+ ref
+ else
+ Puppet::Resource::Reference.new(ref)
+ end
+ end
end
def validate_relationship
- @value.each do |value|
- unless @resource.catalog.resource(*value)
+ @value.each do |ref|
+ unless @resource.catalog.resource(ref.to_s)
description = self.class.direction == :in ? "dependency" : "dependent"
- fail Puppet::Error, "Could not find %s %s[%s] for %s" %
- [description, value[0].to_s.capitalize, value[1], resource.ref]
+ fail "Could not find %s %s for %s" % [description, ref.to_s, resource.ref]
end
end
end
@@ -1542,26 +1302,23 @@ class Type
# which resource is applied first and which resource is considered
# to be the event generator.
def to_edges
- @value.collect do |value|
- # we just have a name and a type, and we need to convert it
- # to an object...
- tname, name = value
- reference = Puppet::ResourceReference.new(tname, name)
+ @value.collect do |reference|
+ reference.catalog = resource.catalog
# Either of the two retrieval attempts could have returned
# nil.
- unless object = reference.resolve
+ unless related_resource = reference.resolve
self.fail "Could not retrieve dependency '%s' of %s" % [reference, @resource.ref]
end
# Are we requiring them, or vice versa? See the method docs
# for futher info on this.
if self.class.direction == :in
- source = object
+ source = related_resource
target = @resource
else
source = @resource
- target = object
+ target = related_resource
end
if method = self.class.callback
@@ -1569,12 +1326,12 @@ class Type
:event => self.class.events,
:callback => method
}
- self.debug("subscribes to %s" % [object.ref])
+ self.debug("subscribes to %s" % [related_resource.ref])
else
# If there's no callback, there's no point in even adding
# a label.
subargs = nil
- self.debug("requires %s" % [object.ref])
+ self.debug("requires %s" % [related_resource.ref])
end
rel = Puppet::Relationship.new(source, target, subargs)
@@ -1740,47 +1497,6 @@ class Type
return @defaultprovider
end
- # Convert a hash, as provided by, um, a provider, into an instance of self.
- def self.hash2obj(hash)
- obj = nil
-
- namevar = self.namevar
- unless hash.include?(namevar) and hash[namevar]
- raise Puppet::DevError, "Hash was not passed with namevar"
- end
-
- # if the obj already exists with that name...
- if obj = self[hash[namevar]]
- # We're assuming here that objects with the same name
- # are the same object, which *should* be the case, assuming
- # we've set up our naming stuff correctly everywhere.
-
- # Mark found objects as present
- hash.each { |param, value|
- if property = obj.property(param)
- elsif val = obj[param]
- obj[param] = val
- else
- # There is a value on disk, but it should go away
- obj[param] = :absent
- end
- }
- else
- # create a new obj, since no existing one seems to
- # match
- obj = self.create(namevar => hash[namevar])
-
- # We can't just pass the hash in at object creation time,
- # because it sets the should value, not the is value.
- hash.delete(namevar)
- hash.each { |param, value|
- obj[param] = value unless obj.add_property_parameter(param)
- }
- end
-
- return obj
- end
-
# Retrieve a provider by name.
def self.provider(name)
name = Puppet::Util.symbolize(name)
@@ -1960,11 +1676,14 @@ class Type
# Figure out of there are any objects we can automatically add as
# dependencies.
- def autorequire
+ def autorequire(rel_catalog = nil)
+ rel_catalog ||= catalog
+ raise(Puppet::DevError, "You cannot add relationships without a catalog") unless rel_catalog
+
reqs = []
self.class.eachautorequire { |type, block|
# Ignore any types we can't find, although that would be a bit odd.
- next unless typeobj = Puppet.type(type)
+ next unless typeobj = Puppet::Type.type(type)
# Retrieve the list of names from the block.
next unless list = self.instance_eval(&block)
@@ -1978,15 +1697,15 @@ class Type
# Support them passing objects directly, to save some effort.
unless dep.is_a? Puppet::Type
# Skip autorequires that we aren't managing
- unless dep = typeobj[dep]
+ unless dep = rel_catalog.resource(type, dep)
next
end
end
-
+
reqs << Puppet::Relationship.new(dep, self)
}
}
-
+
return reqs
end
@@ -1999,61 +1718,6 @@ class Type
end
end.flatten.reject { |r| r.nil? }
end
-
- # Does this resource have a relationship with the other? We have to
- # check each object for both directions of relationship.
- def requires?(other)
- them = [other.class.name, other.title]
- me = [self.class.name, self.title]
- self.class.relationship_params.each do |param|
- case param.direction
- when :in: return true if v = self[param.name] and v.include?(them)
- when :out: return true if v = other[param.name] and v.include?(me)
- end
- end
- return false
- end
-
- # we've received an event
- # we only support local events right now, so we can pass actual
- # objects around, including the transaction object
- # the assumption here is that container objects will pass received
- # methods on to contained objects
- # i.e., we don't trigger our children, our refresh() method calls
- # refresh() on our children
- def trigger(event, source)
- trans = event.transaction
- if @callbacks.include?(source)
- [:ALL_EVENTS, event.event].each { |eventname|
- if method = @callbacks[source][eventname]
- if trans.triggered?(self, method) > 0
- next
- end
- if self.respond_to?(method)
- self.send(method)
- end
-
- trans.triggered(self, method)
- end
- }
- end
- end
-
- # Unsubscribe from a given object, possibly with a specific event.
- def unsubscribe(object, event = nil)
- # First look through our own relationship params
- [:require, :subscribe].each do |param|
- if values = self[param]
- newvals = values.reject { |d|
- d == [object.class.name, object.title]
- }
- if newvals.length != values.length
- self.delete(param)
- self[param] = newvals
- end
- end
- end
- end
###############################
# All of the scheduling code.
@@ -2062,9 +1726,14 @@ class Type
# the instantiation phase, so that the schedule can be anywhere in the
# file.
def schedule
+ unless catalog
+ warning "Cannot schedule without a schedule-containing catalog"
+ return nil
+ end
+
unless defined? @schedule
if name = self[:schedule]
- if sched = Puppet.type(:schedule)[name]
+ if sched = catalog.resource(:schedule, name)
@schedule = sched
else
self.fail "Could not find schedule %s" % name
@@ -2227,147 +1896,84 @@ class Type
public
- def initvars
- @evalcount = 0
- @tags = []
-
- # callbacks are per object and event
- @callbacks = Hash.new { |chash, key|
- chash[key] = {}
- }
-
- # properties and parameters are treated equivalently from the outside:
- # as name-value pairs (using [] and []=)
- # internally, however, parameters are merely a hash, while properties
- # point to Property objects
- # further, the lists of valid properties and parameters are defined
- # at the class level
- unless defined? @parameters
- @parameters = {}
- end
-
- # keeping stats for the total number of changes, and how many were
- # completely sync'ed
- # this isn't really sufficient either, because it adds lots of special
- # cases such as failed changes
- # it also doesn't distinguish between changes from the current transaction
- # vs. changes over the process lifetime
- @totalchanges = 0
- @syncedchanges = 0
- @failedchanges = 0
-
- @inited = true
- end
+ attr_reader :original_parameters
# initialize the type instance
- def initialize(hash)
- unless defined? @inited
- self.initvars
- end
- namevar = self.class.namevar
-
- orighash = hash
-
- # If we got passed a transportable object, we just pull a bunch of info
- # directly from it. This is the main object instantiation mechanism.
- if hash.is_a?(Puppet::TransObject)
- # XXX This will need to change when transobjects change to titles.
- self.title = hash.name
-
- #self[:name] = hash[:name]
- [:file, :line, :tags, :catalog].each { |getter|
- if hash.respond_to?(getter)
- setter = getter.to_s + "="
- if val = hash.send(getter)
- self.send(setter, val)
- end
- end
- }
+ def initialize(resource)
+ raise Puppet::DevError, "Got TransObject instead of Resource or hash" if resource.is_a?(Puppet::TransObject)
+ resource = self.class.hash2resource(resource) unless resource.is_a?(Puppet::Resource)
- hash = hash.to_hash
+ # The list of parameter/property instances.
+ @parameters = {}
+
+ # Set the title first, so any failures print correctly.
+ if resource.type.to_s.downcase.to_sym == self.class.name
+ self.title = resource.title
else
- if hash[:title]
- @title = hash[:title]
- hash.delete(:title)
- end
+ # This should only ever happen for components
+ self.title = resource.ref
end
- # Before anything else, set our parent if it was included
- if hash.include?(:parent)
- @parent = hash[:parent]
- hash.delete(:parent)
+ [:file, :line, :catalog, :implicit].each do |getter|
+ setter = getter.to_s + "="
+ if val = resource.send(getter)
+ self.send(setter, val)
+ end
end
- # Munge up the namevar stuff so we only have one value.
- hash = self.argclean(hash)
+ @tags = resource.tags
- # Let's do the name first, because some things need to happen once
- # we have the name but before anything else
+ @original_parameters = resource.to_hash
- attrs = self.class.allattrs
+ set_name(@original_parameters)
- if hash.include?(namevar)
- #self.send(namevar.to_s + "=", hash[namevar])
- self[namevar] = hash[namevar]
- hash.delete(namevar)
- if attrs.include?(namevar)
- attrs.delete(namevar)
- else
- self.devfail "My namevar isn't a valid attribute...?"
- end
- else
- self.devfail "I was not passed a namevar"
- end
+ set_default(:provider)
- # If the name and title differ, set up an alias
- if self.name != self.title
- if obj = self.class[self.name]
- if self.class.isomorphic?
- raise Puppet::Error, "%s already exists with name %s" %
- [obj.title, self.name]
- end
- else
- self.class.alias(self.name, self)
- end
- end
+ set_parameters(@original_parameters)
- if hash.include?(:provider)
- self[:provider] = hash[:provider]
- hash.delete(:provider)
- else
- setdefaults(:provider)
- end
+ self.validate if self.respond_to?(:validate)
+ end
- # This is all of our attributes except the namevar.
- attrs.each { |attr|
- if hash.include?(attr)
- begin
+ private
+
+ # Set our resource's name.
+ def set_name(hash)
+ n = self.class.namevar
+ self[n] = hash[n]
+ hash.delete(n)
+ end
+
+ # Set all of the parameters from a hash, in the appropriate order.
+ def set_parameters(hash)
+ # Use the order provided by allattrs, but add in any
+ # extra attributes from the resource so we get failures
+ # on invalid attributes.
+ no_values = []
+ (self.class.allattrs + hash.keys).uniq.each do |attr|
+ begin
+ # Set any defaults immediately. This is mostly done so
+ # that the default provider is available for any other
+ # property validation.
+ if hash.has_key?(attr)
self[attr] = hash[attr]
- rescue ArgumentError, Puppet::Error, TypeError
- raise
- rescue => detail
- error = Puppet::DevError.new( "Could not set %s on %s: %s" % [attr, self.class.name, detail])
- error.set_backtrace(detail.backtrace)
- raise error
+ else
+ no_values << attr
end
- hash.delete attr
+ rescue ArgumentError, Puppet::Error, TypeError
+ raise
+ rescue => detail
+ error = Puppet::DevError.new( "Could not set %s on %s: %s" % [attr, self.class.name, detail])
+ error.set_backtrace(detail.backtrace)
+ raise error
end
- }
-
- # Set all default values.
- self.setdefaults
-
- if hash.length > 0
- self.debug hash.inspect
- self.fail("Class %s does not accept argument(s) %s" %
- [self.class.name, hash.keys.join(" ")])
end
-
- if self.respond_to?(:validate)
- self.validate
+ no_values.each do |attr|
+ set_default(attr)
end
end
+ public
+
# Set up all of our autorequires.
def finish
# Scheduling has to be done when the whole config is instantiated, so
@@ -2395,14 +2001,6 @@ class Type
#@cache[name] = value
end
-# def set(name, value)
-# send(name.to_s + "=", value)
-# end
-#
-# def get(name)
-# send(name)
-# end
-
# For now, leave the 'name' method functioning like it used to. Once 'title'
# works everywhere, I'll switch it.
def name
diff --git a/lib/puppet/type/component.rb b/lib/puppet/type/component.rb
index 356205089..724e2985a 100644
--- a/lib/puppet/type/component.rb
+++ b/lib/puppet/type/component.rb
@@ -1,127 +1,52 @@
-# the object allowing us to build complex structures
-# this thing contains everything else, including itself
-
require 'puppet'
require 'puppet/type'
require 'puppet/transaction'
-require 'puppet/pgraph'
Puppet::Type.newtype(:component) do
include Enumerable
- attr_accessor :children
newparam(:name) do
desc "The name of the component. Generally optional."
isnamevar
end
- newparam(:type) do
- desc "The type that this component maps to. Generally some kind of
- class from the language."
-
- defaultto "component"
+ # Override how parameters are handled so that we support the extra
+ # parameters that are used with defined resource types.
+ def [](param)
+ return super if self.class.validattr?(param)
+ @extra_parameters[param.to_sym]
end
- # Remove a child from the component.
- def delete(child)
- if @children.include?(child)
- @children.delete(child)
- return true
- else
- return super
- end
- end
-
- # Recurse deeply through the tree, but only yield types, not properties.
- def delve(&block)
- self.each do |obj|
- if obj.is_a?(self.class)
- obj.delve(&block)
- end
- end
- block.call(self)
- end
-
- # Return each child in turn.
- def each
- @children.each { |child| yield child }
- end
-
- # Do all of the polishing off, mostly doing autorequires and making
- # dependencies. This will get run once on the top-level component,
- # and it will do everything necessary.
- def finalize
- started = {}
- finished = {}
-
- # First do all of the finish work, which mostly involves
- self.delve do |object|
- # Make sure we don't get into loops
- if started.has_key?(object)
- debug "Already finished %s" % object.title
- next
- else
- started[object] = true
- end
- unless finished.has_key?(object)
- object.finish
- finished[object] = true
- end
- end
-
- @finalized = true
- end
-
- def finalized?
- if defined? @finalized
- return @finalized
- else
- return false
- end
+ # Override how parameters are handled so that we support the extra
+ # parameters that are used with defined resource types.
+ def []=(param, value)
+ return super if self.class.validattr?(param)
+ @extra_parameters[param.to_sym] = value
end
# Initialize a new component
def initialize(*args)
- @children = []
- super
-
- @reference = Puppet::ResourceReference.new(:component, @title)
-
- unless self.class[@reference.to_s]
- self.class.alias(@reference.to_s, self)
- end
- end
-
- def initvars
+ @extra_parameters = {}
super
- @children = []
- end
- # Add a hook for testing for recursion.
- def parentof?(child)
- if super(child)
- return true
- elsif @children.include?(child)
- debug "child is already in children array"
- return true
- else
- return false
+ if catalog and ! catalog.resource(ref)
+ catalog.alias(self, ref)
end
end
# Component paths are special because they function as containers.
def pathbuilder
- if @reference.type == "Class"
+ if reference.type == "Class"
# 'main' is the top class, so we want to see '//' instead of
# its name.
- if @reference.title == "main"
+ if reference.title == "main"
myname = ""
else
- myname = @reference.title
+ myname = reference.title
end
else
- myname = @reference.to_s
+ myname = reference.to_s
end
if p = self.parent
return [p.pathbuilder, myname]
@@ -131,12 +56,16 @@ Puppet::Type.newtype(:component) do
end
def ref
- @reference.to_s
+ reference.to_s
end
# We want our title to just be the whole reference, rather than @title.
def title
- @reference.to_s
+ ref
+ end
+
+ def title=(str)
+ @reference = Puppet::Resource::Reference.new(str)
end
def refresh
@@ -149,6 +78,10 @@ Puppet::Type.newtype(:component) do
end
def to_s
- @reference.to_s
+ reference.to_s
end
+
+ private
+
+ attr_reader :reference
end
diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb
index b6396b0bf..3d721ac69 100644
--- a/lib/puppet/type/file.rb
+++ b/lib/puppet/type/file.rb
@@ -28,7 +28,7 @@ module Puppet
validate do |value|
unless value =~ /^#{File::SEPARATOR}/
- raise Puppet::Error, "File paths must be fully qualified"
+ raise Puppet::Error, "File paths must be fully qualified, not '%s'" % value
end
end
end
@@ -78,9 +78,8 @@ module Puppet
munge do |value|
# I don't really know how this is happening.
- if value.is_a?(Array)
- value = value.shift
- end
+ value = value.shift if value.is_a?(Array)
+
case value
when false, "false", :false:
false
@@ -92,7 +91,7 @@ module Puppet
# We can't depend on looking this up right now,
# we have to do it after all of the objects
# have been instantiated.
- if bucketobj = Puppet::Type.type(:filebucket)[value]
+ if resource.catalog and bucketobj = resource.catalog.resource(:filebucket, value)
@resource.bucket = bucketobj.bucket
bucketobj.title
else
@@ -156,8 +155,6 @@ module Puppet
engine, so shell metacharacters are fully supported, e.g. ``[a-z]*``.
Matches that would descend into the directory structure are ignored,
e.g., ``*/*``."
-
- defaultto false
validate do |value|
unless value.is_a?(Array) or value.is_a?(String) or value == false
@@ -243,6 +240,9 @@ module Puppet
CREATORS.each do |param|
count += 1 if self.should(param)
end
+ if @parameters.include?(:source)
+ count += 1
+ end
if count > 1
self.fail "You cannot specify more than one of %s" % CREATORS.collect { |p| p.to_s}.join(", ")
end
@@ -268,7 +268,7 @@ module Puppet
obj[:check] = :all
files << obj
else
- files << self.create(
+ files << self.new(
:name => path, :check => :all
)
end
@@ -278,11 +278,6 @@ module Puppet
@depthfirst = false
-
- def argument?(arg)
- @arghash.include?(arg)
- end
-
# Determine the user to write files as.
def asuser
if self.should(:owner) and ! self.should(:owner).is_a?(Symbol)
@@ -301,33 +296,32 @@ module Puppet
return asuser
end
+ # Does the file currently exist? Just checks for whether
+ # we have a stat
+ def exist?
+ stat ? true : false
+ end
+
# We have to do some extra finishing, to retrieve our bucket if
# there is one.
def finish
- # Let's cache these values, since there should really only be
- # a couple of these buckets
- @@filebuckets ||= {}
-
# Look up our bucket, if there is one
if bucket = self.bucket
case bucket
when String:
- if obj = @@filebuckets[bucket]
- # This sets the @value on :backup, too
- self.bucket = obj
+ if catalog and obj = catalog.resource(:filebucket, bucket)
+ self.bucket = obj.bucket
elsif bucket == "puppet"
obj = Puppet::Network::Client.client(:Dipper).new(
:Path => Puppet[:clientbucketdir]
)
self.bucket = obj
- @@filebuckets[bucket] = obj
- elsif obj = Puppet::Type.type(:filebucket).bucket(bucket)
- @@filebuckets[bucket] = obj
- self.bucket = obj
else
- self.fail "Could not find filebucket %s" % bucket
+ self.fail "Could not find filebucket '%s'" % bucket
end
when Puppet::Network::Client.client(:Dipper): # things are hunky-dorey
+ when Puppet::Type::Filebucket # things are hunky-dorey
+ self.bucket = bucket.bucket
else
self.fail "Invalid bucket type %s" % bucket.class
end
@@ -337,7 +331,23 @@ module Puppet
# Create any children via recursion or whatever.
def eval_generate
- recurse()
+ return [] unless self.recurse?
+
+ recurse
+ #recurse.reject do |resource|
+ # catalog.resource(:file, resource[:path])
+ #end.each do |child|
+ # catalog.add_resource child
+ # catalog.relationship_graph.add_edge self, child
+ #end
+ end
+
+ def flush
+ # We want to make sure we retrieve metadata anew on each transaction.
+ @parameters.each do |name, param|
+ param.flush if param.respond_to?(:flush)
+ end
+ @stat = nil
end
# Deal with backups.
@@ -432,28 +442,12 @@ module Puppet
end
when "link": return true
else
- self.notice "Cannot backup files of type %s" %
- File.stat(file).ftype
+ self.notice "Cannot backup files of type %s" % File.stat(file).ftype
return false
end
end
- def handleignore(children)
- return children unless self[:ignore]
- self[:ignore].each { |ignore|
- ignored = []
- Dir.glob(File.join(self[:path],ignore), File::FNM_DOTMATCH) {
- |match| ignored.push(File.basename(match))
- }
- children = children - ignored
- }
- return children
- end
-
def initialize(hash)
- # Store a copy of the arguments for later.
- tmphash = hash.to_hash
-
# Used for caching clients
@clients = {}
@@ -464,194 +458,31 @@ module Puppet
@title.sub!(/\/$/, "") unless @title == "/"
- # Clean out as many references to any file paths as possible.
- # This was the source of many, many bugs.
- @arghash = tmphash
- @arghash.delete(self.class.namevar)
-
- [:source, :parent].each do |param|
- if @arghash.include?(param)
- @arghash.delete(param)
- end
- end
-
@stat = nil
end
-
- # Build a recursive map of a link source
- def linkrecurse(recurse)
- target = @parameters[:target].should
-
- method = :lstat
- if self[:links] == :follow
- method = :stat
- end
-
- targetstat = nil
- unless FileTest.exist?(target)
- return
- end
- # Now stat our target
- targetstat = File.send(method, target)
- unless targetstat.ftype == "directory"
- return
- end
-
- # Now that we know our corresponding target is a directory,
- # change our type
- self[:ensure] = :directory
-
- unless FileTest.readable? target
- self.notice "Cannot manage %s: permission denied" % self.name
- return
- end
-
- children = Dir.entries(target).reject { |d| d =~ /^\.+$/ }
-
- # Get rid of ignored children
- if @parameters.include?(:ignore)
- children = handleignore(children)
- end
-
- added = []
- children.each do |file|
- Dir.chdir(target) do
- longname = File.join(target, file)
-
- # Files know to create directories when recursion
- # is enabled and we're making links
- args = {
- :recurse => recurse,
- :ensure => longname
- }
-
- if child = self.newchild(file, true, args)
- added << child
- end
- end
- end
-
- added
- end
-
- # Build up a recursive map of what's around right now
- def localrecurse(recurse)
- unless FileTest.exist?(self[:path]) and self.stat.directory?
- #self.info "%s is not a directory; not recursing" %
- # self[:path]
- return
- end
-
- unless FileTest.readable? self[:path]
- self.notice "Cannot manage %s: permission denied" % self.name
- return
- end
-
- children = Dir.entries(self[:path])
-
- #Get rid of ignored children
- if @parameters.include?(:ignore)
- children = handleignore(children)
- end
-
- added = []
- children.each { |file|
- file = File.basename(file)
- next if file =~ /^\.\.?$/ # skip . and ..
- options = {:recurse => recurse}
-
- if child = self.newchild(file, true, options)
- added << child
- end
- }
-
- added
- end
# Create a new file or directory object as a child to the current
# object.
- def newchild(path, local, hash = {})
- raise(Puppet::DevError, "File recursion cannot happen without a catalog") unless catalog
-
- # make local copy of arguments
- args = symbolize_options(@arghash)
-
- # There's probably a better way to do this, but we don't want
- # to pass this info on.
- if v = args[:ensure]
- v = symbolize(v)
- args.delete(:ensure)
- end
-
- if path =~ %r{^#{File::SEPARATOR}}
- self.devfail(
- "Must pass relative paths to PFile#newchild()"
- )
- else
- path = File.join(self[:path], path)
- end
+ def newchild(path)
+ full_path = File.join(self[:path], path)
- args[:path] = path
+ # Add some new values to our original arguments -- these are the ones
+ # set at initialization. We specifically want to exclude any param
+ # values set by the :source property or any default values.
+ # LAK:NOTE This is kind of silly, because the whole point here is that
+ # the values set at initialization should live as long as the resource
+ # but values set by default or by :source should only live for the transaction
+ # or so. Unfortunately, we don't have a straightforward way to manage
+ # the different lifetimes of this data, so we kludge it like this.
+ # The right-side hash wins in the merge.
+ options = @original_parameters.merge(:path => full_path, :implicit => true).reject { |param, value| value.nil? }
- unless hash.include?(:recurse)
- if args.include?(:recurse)
- if args[:recurse].is_a?(Integer)
- args[:recurse] -= 1 # reduce the level of recursion
- end
- end
-
- end
-
- hash.each { |key,value|
- args[key] = value
- }
-
- child = nil
-
- # The child might already exist because 'localrecurse' runs
- # before 'sourcerecurse'. I could push the override stuff into
- # a separate method or something, but the work is the same other
- # than this last bit, so it doesn't really make sense.
- if child = catalog.resource(:file, path)
- unless child.parent.object_id == self.object_id
- self.debug "Not managing more explicit file %s" %
- path
- return nil
- end
-
- # This is only necessary for sourcerecurse, because we might have
- # created the object with different 'should' values than are
- # set remotely.
- unless local
- args.each { |var,value|
- next if var == :path
- next if var == :name
-
- # behave idempotently
- unless child.should(var) == value
- child[var] = value
- end
- }
- end
- return nil
- else # create it anew
- #notice "Creating new file with args %s" % args.inspect
- args[:parent] = self
- begin
- # This method is used by subclasses of :file, so use the class name rather than hard-coding
- # :file.
- return nil unless child = catalog.create_implicit_resource(self.class.name, args)
- rescue => detail
- self.notice "Cannot manage: %s" % [detail]
- return nil
- end
+ # These should never be passed to our children.
+ [:parent, :ensure, :recurse, :target].each do |param|
+ options.delete(param) if options.include?(param)
end
- # LAK:FIXME This shouldn't be necessary, but as long as we're
- # modeling the relationship graph specifically, it is.
- catalog.relationship_graph.add_edge self, child
-
- return child
+ return self.class.new(options)
end
# Files handle paths specially, because they just lengthen their
@@ -681,70 +512,118 @@ module Puppet
@parameters.include?(:purge) and (self[:purge] == :true or self[:purge] == "true")
end
- # Recurse into the directory. This basically just calls 'localrecurse'
- # and maybe 'sourcerecurse', returning the collection of generated
- # files.
+ # Recursively generate a list of file resources, which will
+ # be used to copy remote files, manage local files, and/or make links
+ # to map to another directory.
def recurse
- # are we at the end of the recursion?
- return unless self.recurse?
-
- recurse = self[:recurse]
- # we might have a string, rather than a number
- if recurse.is_a?(String)
- if recurse =~ /^[0-9]+$/
- recurse = Integer(recurse)
- else # anything else is infinite recursion
- recurse = true
- end
+ children = recurse_local
+
+ if self[:target]
+ recurse_link(children)
+ elsif self[:source]
+ recurse_remote(children)
end
- if recurse.is_a?(Integer)
- recurse -= 1
+ return children.values.sort { |a, b| a[:path] <=> b[:path] }
+ end
+
+ # A simple method for determining whether we should be recursing.
+ def recurse?
+ return false unless @parameters.include?(:recurse)
+
+ val = @parameters[:recurse].value
+
+ if val and (val == true or val > 0)
+ return true
+ else
+ return false
end
-
- children = []
-
- # We want to do link-recursing before normal recursion so that all
- # of the target stuff gets copied over correctly.
- if @parameters.include? :target and ret = self.linkrecurse(recurse)
- children += ret
+ end
+
+ # Recurse the target of the link.
+ def recurse_link(children)
+ perform_recursion(self[:target]).each do |meta|
+ if meta.relative_path == "."
+ self[:ensure] = :directory
+ next
+ end
+
+ children[meta.relative_path] ||= newchild(meta.relative_path)
+ if meta.ftype == "directory"
+ children[meta.relative_path][:ensure] = :directory
+ else
+ children[meta.relative_path][:ensure] = :link
+ children[meta.relative_path][:target] = meta.full_path
+ end
end
- if ret = self.localrecurse(recurse)
- children += ret
+ children
+ end
+
+ # Recurse the file itself, returning a Metadata instance for every found file.
+ def recurse_local
+ result = perform_recursion(self[:path])
+ return {} unless result
+ result.inject({}) do |hash, meta|
+ next hash if meta.relative_path == "."
+
+ hash[meta.relative_path] = newchild(meta.relative_path)
+ hash
end
+ end
- # These will be files pulled in by the file source
- sourced = false
- if @parameters.include?(:source)
- ret, sourced = self.sourcerecurse(recurse)
- if ret
- children += ret
+ # Recurse against our remote file.
+ def recurse_remote(children)
+ sourceselect = self[:sourceselect]
+
+ total = self[:source].collect do |source|
+ next unless result = perform_recursion(source)
+ return if top = result.find { |r| r.relative_path == "." } and top.ftype != "directory"
+ result.each { |data| data.source = "%s/%s" % [source, data.relative_path] }
+ break result if result and ! result.empty? and sourceselect == :first
+ result
+ end.flatten
+
+ # This only happens if we have sourceselect == :all
+ unless sourceselect == :first
+ found = []
+ total.reject! do |data|
+ result = found.include?(data.relative_path)
+ found << data.relative_path unless found.include?(data.relative_path)
+ result
end
end
- # The purge check needs to happen after all of the other recursion.
+ total.each do |meta|
+ if meta.relative_path == "."
+ parameter(:source).metadata = meta
+ next
+ end
+ children[meta.relative_path] ||= newchild(meta.relative_path)
+ children[meta.relative_path][:source] = meta.source
+ children[meta.relative_path][:checksum] = :md5 if meta.ftype == "file"
+
+ children[meta.relative_path].parameter(:source).metadata = meta
+ end
+
+ # If we're purging resources, then delete any resource that isn't on the
+ # remote system.
if self.purge?
- children.each do |child|
- if (sourced and ! sourced.include?(child[:path])) or ! child.managed?
+ # Make a hash of all of the resources we found remotely -- all we need is the
+ # fast lookup, the values don't matter.
+ remotes = total.inject({}) { |hash, meta| hash[meta.relative_path] = true; hash }
+
+ children.each do |name, child|
+ unless remotes.include?(name)
child[:ensure] = :absent
end
end
end
-
+
children
end
- # A simple method for determining whether we should be recursing.
- def recurse?
- return false unless @parameters.include?(:recurse)
-
- val = @parameters[:recurse].value
-
- if val and (val == true or val > 0)
- return true
- else
- return false
- end
+ def perform_recursion(path)
+ Puppet::FileServing::Metadata.search(path, :links => self[:links], :recurse => self[:recurse], :ignore => self[:ignore])
end
# Remove the old backup.
@@ -777,7 +656,8 @@ module Puppet
# Remove any existing data. This is only used when dealing with
# links or directories.
def remove_existing(should)
- return unless s = stat(true)
+ expire()
+ return unless s = stat
self.fail "Could not back up; will not replace" unless handlebackup
@@ -800,114 +680,15 @@ module Puppet
else
self.fail "Could not back up files of type %s" % s.ftype
end
+ expire
end
# a wrapper method to make sure the file exists before doing anything
def retrieve
- unless stat = self.stat(true)
- self.debug "File does not exist"
- # If the file doesn't exist but we have a source, then call
- # retrieve on that property
-
- propertyvalues = properties().inject({}) { |hash, property|
- hash[property] = :absent
- hash
- }
-
- if @parameters.include?(:source)
- propertyvalues[:source] = @parameters[:source].retrieve
- end
- return propertyvalues
- end
-
- return currentpropvalues()
- end
-
- # This recurses against the remote source and makes sure the local
- # and remote structures match. It's run after 'localrecurse'. This
- # method only does anything when its corresponding remote entry is
- # a directory; in that case, this method creates file objects that
- # correspond to any contained remote files.
- def sourcerecurse(recurse)
- # we'll set this manually as necessary
- if @arghash.include?(:ensure)
- @arghash.delete(:ensure)
- end
-
- r = false
- if recurse
- unless recurse == 0
- r = 1
- end
- end
-
- ignore = self[:ignore]
-
- result = []
- found = []
-
- # Keep track of all the files we found in the source, so we can purge
- # appropriately.
- sourced = []
-
- success = false
-
- @parameters[:source].should.each do |source|
- sourceobj, path = uri2obj(source)
-
- # okay, we've got our source object; now we need to
- # build up a local file structure to match the remote
- # one
-
- server = sourceobj.server
-
- desc = server.list(path, self[:links], r, ignore)
- if desc == ""
- next
- end
-
- success = true
-
- # Now create a new child for every file returned in the list.
- result += desc.split("\n").collect { |line|
- file, type = line.split("\t")
- next if file == "/" # skip the listing object
- name = file.sub(/^\//, '')
-
- # This makes sure that the first source *always* wins
- # for conflicting files.
- next if found.include?(name)
-
- # For directories, keep all of the sources, so that
- # sourceselect still works as planned.
- if type == "directory"
- newsource = @parameters[:source].should.collect do |tmpsource|
- tmpsource + file
- end
- else
- newsource = source + file
- end
- args = {:source => newsource}
- if type == file
- args[:recurse] = nil
- end
-
- found << name
- sourced << File.join(self[:path], name)
-
- self.newchild(name, false, args)
- }.reject {|c| c.nil? }
-
- if self[:sourceselect] == :first
- return [result, sourced]
- end
+ if source = parameter(:source)
+ source.copy_source_values
end
-
- unless success
- raise Puppet::Error, "None of the provided sources exist"
- end
-
- return [result, sourced]
+ super
end
# Set the checksum, from another property. There are multiple
@@ -926,11 +707,33 @@ module Puppet
end
end
+ # Should this thing be a normal file? This is a relatively complex
+ # way of determining whether we're trying to create a normal file,
+ # and it's here so that the logic isn't visible in the content property.
+ def should_be_file?
+ return true if self[:ensure] == :file
+
+ # I.e., it's set to something like "directory"
+ return false if e = self[:ensure] and e != :present
+
+ # The user doesn't really care, apparently
+ if self[:ensure] == :present
+ return true unless s = stat
+ return true if s.ftype == "file"
+ return false
+ end
+
+ # If we've gotten here, then :ensure isn't set
+ return true if self[:content]
+ return true if stat and stat.ftype == "file"
+ return false
+ end
+
# Stat our file. Depending on the value of the 'links' attribute, we
# use either 'stat' or 'lstat', and we expect the properties to use the
# resulting stat object accordingly (mostly by testing the 'ftype'
# value).
- def stat(refresh = false)
+ cached_attr(:stat) do
method = :stat
# Files are the only types that support links
@@ -938,23 +741,15 @@ module Puppet
method = :lstat
end
path = self[:path]
- # Just skip them when they don't exist at all.
- unless FileTest.exists?(path) or FileTest.symlink?(path)
- @stat = nil
- return @stat
- end
- if @stat.nil? or refresh == true
- begin
- @stat = File.send(method, self[:path])
- rescue Errno::ENOENT => error
- @stat = nil
- rescue Errno::EACCES => error
- self.warning "Could not stat; permission denied"
- @stat = nil
- end
- end
- return @stat
+ begin
+ File.send(method, self[:path])
+ rescue Errno::ENOENT => error
+ return nil
+ rescue Errno::EACCES => error
+ warning "Could not stat; permission denied"
+ return nil
+ end
end
# We have to hack this just a little bit, because otherwise we'll get
@@ -968,75 +763,6 @@ module Puppet
obj
end
- def localfileserver
- unless defined? @@localfileserver
- args = {
- :Local => true,
- :Mount => { "/" => "localhost" },
- :Config => false
- }
- @@localfileserver = Puppet::Network::Handler.handler(:fileserver).new(args)
- end
- @@localfileserver
- end
-
- def uri2obj(source)
- sourceobj = Puppet::Type::File::FileSource.new
- path = nil
- unless source
- devfail "Got a nil source"
- end
- if source =~ /^\//
- source = "file://localhost/%s" % URI.escape(source)
- sourceobj.mount = "localhost"
- sourceobj.local = true
- end
- begin
- uri = URI.parse(URI.escape(source))
- rescue => detail
- self.fail "Could not understand source %s: %s" %
- [source, detail.to_s]
- end
-
- case uri.scheme
- when "file":
- sourceobj.server = localfileserver
- path = "/localhost" + uri.path
- when "puppet":
- # FIXME: We should cache clients by uri.host + uri.port
- # not by the full source path
- unless @clients.include?(source)
- host = uri.host
- host ||= Puppet[:server] unless Puppet[:name] == "puppet"
- if host.nil?
- server = localfileserver
- else
- args = { :Server => host }
- if uri.port
- args[:Port] = uri.port
- end
- server = Puppet::Network::Client.file.new(args)
- end
- @clients[source] = server
- end
- sourceobj.server = @clients[source]
-
- tmp = uri.path
- if tmp =~ %r{^/(\w+)}
- sourceobj.mount = $1
- path = tmp
- #path = tmp.sub(%r{^/\w+},'') || "/"
- else
- self.fail "Invalid source path %s" % tmp
- end
- else
- self.fail "Got other URL type '%s' from %s" %
- [uri.scheme, source]
- end
-
- return [sourceobj, path.sub(/\/\//, '/')]
- end
-
# Write out the file. Requires the content to be written,
# the property name for logging, and the checksum for validation.
def write(content, property, checksum = nil)
@@ -1137,13 +863,7 @@ module Puppet
end
end
end
- end # Puppet.type(:pfile)
-
- # the filesource class can't include the path, because the path
- # changes for every file instance
- class ::Puppet::Type::File::FileSource
- attr_accessor :mount, :root, :server, :local
- end
+ end # Puppet::Type.type(:pfile)
# We put all of the properties in separate files, because there are so many
# of them. The order these are loaded is important, because it determines
diff --git a/lib/puppet/type/file/checksum.rb b/lib/puppet/type/file/checksum.rb
index 785ed0fee..82fae748c 100755
--- a/lib/puppet/type/file/checksum.rb
+++ b/lib/puppet/type/file/checksum.rb
@@ -11,7 +11,11 @@ Puppet::Type.type(:file).newproperty(:checksum) do
like Tripwire without managing the file contents in any way. You can
specify that a file's checksum should be monitored and then subscribe to
the file from another object and receive events to signify
- checksum changes, for instance."
+ checksum changes, for instance.
+
+ There are a number of checksum types available including MD5 hashing (and
+ an md5lite variation that only hashes the first 500 characters of the
+ file."
@event = :file_changed
diff --git a/lib/puppet/type/file/content.rb b/lib/puppet/type/file/content.rb
index 1eb1423aa..6d6dad4f1 100755
--- a/lib/puppet/type/file/content.rb
+++ b/lib/puppet/type/file/content.rb
@@ -1,6 +1,9 @@
+require 'puppet/util/checksums'
+
module Puppet
Puppet::Type.type(:file).newproperty(:content) do
include Puppet::Util::Diff
+ include Puppet::Util::Checksums
desc "Specify the contents of a file as a string. Newlines, tabs, and
spaces can be specified using the escaped syntax (e.g., \\n for a
@@ -23,7 +26,11 @@ module Puppet
`PuppetTemplating templating`:trac:."
def change_to_s(currentvalue, newvalue)
- newvalue = "{md5}" + Digest::MD5.hexdigest(newvalue)
+ if source = resource.parameter(:source)
+ newvalue = source.metadata.checksum
+ else
+ newvalue = "{md5}" + Digest::MD5.hexdigest(newvalue)
+ end
if currentvalue == :absent
return "created file with contents %s" % newvalue
else
@@ -31,18 +38,41 @@ module Puppet
return "changed file contents from %s to %s" % [currentvalue, newvalue]
end
end
+
+ def content
+ self.should || (s = resource.parameter(:source) and s.content)
+ end
# Override this method to provide diffs if asked for.
# Also, fix #872: when content is used, and replace is true, the file
# should be insync when it exists
def insync?(is)
- if ! @resource.replace? and File.exists?(@resource[:path])
+ if resource.should_be_file?
+ return false if is == :absent
+ else
+ return true
+ end
+
+ return true if ! @resource.replace?
+
+ if self.should
+ return super
+ elsif source = resource.parameter(:source)
+ fail "Got a remote source with no checksum" unless source.checksum
+ unless sum_method = sumtype(source.checksum)
+ fail "Could not extract checksum type from source checksum '%s'" % source.checksum
+ end
+
+ newsum = "{%s}" % sum_method + send(sum_method, is)
+ result = (newsum == source.checksum)
+ else
+ # We've got no content specified, and no source from which to
+ # get content.
return true
end
- result = super
- if ! result and Puppet[:show_diff] and File.exists?(@resource[:path])
- string_file_diff(@resource[:path], self.should)
+ if ! result and Puppet[:show_diff]
+ string_file_diff(@resource[:path], content)
end
return result
end
@@ -50,31 +80,31 @@ module Puppet
def retrieve
return :absent unless stat = @resource.stat
- return self.should if stat.ftype == "link" and @resource[:links] == :ignore
-
- # Don't even try to manage the content on directories
+ # Don't even try to manage the content on directories or links
return nil if stat.ftype == "directory"
begin
- currentvalue = File.read(@resource[:path])
- return currentvalue
+ return File.read(@resource[:path])
rescue => detail
- raise Puppet::Error, "Could not read %s: %s" %
- [@resource.title, detail]
+ raise Puppet::Error, "Could not read %s: %s" % [@resource.title, detail]
end
end
# Make sure we're also managing the checksum property.
def should=(value)
super
- @resource.newattr(:checksum) unless @resource.property(:checksum)
+ @resource.newattr(:checksum) unless @resource.parameter(:checksum)
end
# Just write our content out to disk.
def sync
return_event = @resource.stat ? :file_changed : :file_created
- @resource.write(self.should, :content)
+ # We're safe not testing for the 'source' if there's no 'should'
+ # because we wouldn't have gotten this far if there weren't at least
+ # one valid value somewhere.
+ content = self.should || resource.parameter(:source).content
+ @resource.write(content, :content)
return return_event
end
diff --git a/lib/puppet/type/file/ensure.rb b/lib/puppet/type/file/ensure.rb
index 175b821b3..7466c5e3a 100755
--- a/lib/puppet/type/file/ensure.rb
+++ b/lib/puppet/type/file/ensure.rb
@@ -1,5 +1,5 @@
module Puppet
- Puppet.type(:file).ensurable do
+ Puppet::Type.type(:file).ensurable do
require 'etc'
desc "Whether to create files that don't currently exist.
Possible values are *absent*, *present*, *file*, and *directory*.
@@ -110,10 +110,8 @@ module Puppet
end
def change_to_s(currentvalue, newvalue)
- if property = (@resource.property(:content) || @resource.property(:source)) and ! property.insync?(currentvalue)
- currentvalue = property.retrieve
-
- return property.change_to_s(property.retrieve, property.should)
+ if property = @resource.property(:content) and content = property.retrieve and ! property.insync?(content)
+ return property.change_to_s(content, property.should)
else
super(currentvalue, newvalue)
end
@@ -165,6 +163,7 @@ module Puppet
end
def sync
+ expire
@resource.remove_existing(self.should)
if self.should == :absent
return :file_removed
diff --git a/lib/puppet/type/file/group.rb b/lib/puppet/type/file/group.rb
index 56883add6..3aeac21ff 100755
--- a/lib/puppet/type/file/group.rb
+++ b/lib/puppet/type/file/group.rb
@@ -2,7 +2,7 @@ require 'puppet/util/posix'
# Manage file group ownership.
module Puppet
- Puppet.type(:file).newproperty(:group) do
+ Puppet::Type.type(:file).newproperty(:group) do
include Puppet::Util::POSIX
require 'etc'
diff --git a/lib/puppet/type/file/mode.rb b/lib/puppet/type/file/mode.rb
index ada1b5b47..fd9c27ae6 100755
--- a/lib/puppet/type/file/mode.rb
+++ b/lib/puppet/type/file/mode.rb
@@ -2,7 +2,7 @@
# for specification (e.g., u+rwx, or -0011), but for now only supports
# specifying the full mode.
module Puppet
- Puppet.type(:file).newproperty(:mode) do
+ Puppet::Type.type(:file).newproperty(:mode) do
require 'etc'
desc "Mode the file should be. Currently relatively limited:
you must specify the exact mode the file should be."
diff --git a/lib/puppet/type/file/owner.rb b/lib/puppet/type/file/owner.rb
index 6bc40ecbd..e4339f05b 100755
--- a/lib/puppet/type/file/owner.rb
+++ b/lib/puppet/type/file/owner.rb
@@ -1,5 +1,5 @@
module Puppet
- Puppet.type(:file).newproperty(:owner) do
+ Puppet::Type.type(:file).newproperty(:owner) do
include Puppet::Util::POSIX
include Puppet::Util::Warnings
diff --git a/lib/puppet/type/file/selcontext.rb b/lib/puppet/type/file/selcontext.rb
index 990035005..717f58805 100644
--- a/lib/puppet/type/file/selcontext.rb
+++ b/lib/puppet/type/file/selcontext.rb
@@ -58,7 +58,7 @@ module Puppet
end
end
- Puppet.type(:file).newproperty(:seluser, :parent => Puppet::SELFileContext) do
+ Puppet::Type.type(:file).newproperty(:seluser, :parent => Puppet::SELFileContext) do
desc "What the SELinux user component of the context of the file should be.
Any valid SELinux user component is accepted. For example ``user_u``.
If not specified it defaults to the value returned by matchpathcon for
@@ -69,7 +69,7 @@ module Puppet
defaultto { self.retrieve_default_context(:seluser) }
end
- Puppet.type(:file).newproperty(:selrole, :parent => Puppet::SELFileContext) do
+ Puppet::Type.type(:file).newproperty(:selrole, :parent => Puppet::SELFileContext) do
desc "What the SELinux role component of the context of the file should be.
Any valid SELinux role component is accepted. For example ``role_r``.
If not specified it defaults to the value returned by matchpathcon for
@@ -80,7 +80,7 @@ module Puppet
defaultto { self.retrieve_default_context(:selrole) }
end
- Puppet.type(:file).newproperty(:seltype, :parent => Puppet::SELFileContext) do
+ Puppet::Type.type(:file).newproperty(:seltype, :parent => Puppet::SELFileContext) do
desc "What the SELinux type component of the context of the file should be.
Any valid SELinux type component is accepted. For example ``tmp_t``.
If not specified it defaults to the value returned by matchpathcon for
@@ -91,7 +91,7 @@ module Puppet
defaultto { self.retrieve_default_context(:seltype) }
end
- Puppet.type(:file).newproperty(:selrange, :parent => Puppet::SELFileContext) do
+ Puppet::Type.type(:file).newproperty(:selrange, :parent => Puppet::SELFileContext) do
desc "What the SELinux range component of the context of the file should be.
Any valid SELinux range component is accepted. For example ``s0`` or
``SystemHigh``. If not specified it defaults to the value returned by
diff --git a/lib/puppet/type/file/source.rb b/lib/puppet/type/file/source.rb
index 2514d3d1e..f7c0fa4fc 100755
--- a/lib/puppet/type/file/source.rb
+++ b/lib/puppet/type/file/source.rb
@@ -1,10 +1,14 @@
+
+require 'puppet/file_serving/content'
+require 'puppet/file_serving/metadata'
+
module Puppet
# Copy files from a local or remote source. This state *only* does any work
# when the remote file is an actual file; in that case, this state copies
# the file down. If the remote file is a dir or a link or whatever, then
# this state, during retrieval, modifies the appropriate other states
# so that things get taken care of appropriately.
- Puppet.type(:file).newproperty(:source) do
+ Puppet::Type.type(:file).newparam(:source) do
include Puppet::Util::Diff
attr_accessor :source, :local
@@ -59,218 +63,113 @@ module Puppet
to ``follow`` if any remote sources are links.
"
- uncheckable
-
- validate do |source|
- unless @resource.uri2obj(source)
- raise Puppet::Error, "Invalid source %s" % source
+ validate do |sources|
+ sources = [sources] unless sources.is_a?(Array)
+ sources.each do |source|
+ begin
+ uri = URI.parse(URI.escape(source))
+ rescue => detail
+ self.fail "Could not understand source %s: %s" % [source, detail.to_s]
+ end
+
+ unless uri.scheme.nil? or %w{file puppet}.include?(uri.scheme)
+ self.fail "Cannot use URLs of type '%s' as source for fileserving" % [uri.scheme]
+ end
end
end
- munge do |source|
- # if source.is_a? Symbol
- # return source
- # end
-
- # Remove any trailing slashes
- source.sub(/\/$/, '')
+ munge do |sources|
+ sources = [sources] unless sources.is_a?(Array)
+ sources.collect { |source| source.sub(/\/$/, '') }
end
def change_to_s(currentvalue, newvalue)
- # newvalue = "{md5}" + @stats[:checksum]
+ # newvalue = "{md5}" + @metadata.checksum
if @resource.property(:ensure).retrieve == :absent
- return "creating from source %s with contents %s" % [@source, @stats[:checksum]]
+ return "creating from source %s with contents %s" % [metadata.source, metadata.checksum]
else
- return "replacing from source %s with contents %s" % [@source, @stats[:checksum]]
+ return "replacing from source %s with contents %s" % [metadata.source, metadata.checksum]
end
end
def checksum
- if defined?(@stats)
- @stats[:checksum]
+ if metadata
+ metadata.checksum
else
nil
end
end
- # Ask the file server to describe our file.
- def describe(source)
- sourceobj, path = @resource.uri2obj(source)
- server = sourceobj.server
+ # Look up (if necessary) and return remote content.
+ cached_attr(:content) do
+ raise Puppet::DevError, "No source for content was stored with the metadata" unless metadata.source
- begin
- desc = server.describe(path, @resource[:links])
- rescue Puppet::Network::XMLRPCClientError => detail
- fail detail, "Could not describe %s: %s" % [path, detail]
+ unless tmp = Puppet::FileServing::Content.find(metadata.source)
+ fail "Could not find any content at %s" % metadata.source
end
+ tmp.content
+ end
- return nil if desc == ""
+ # Copy the values from the source to the resource. Yay.
+ def copy_source_values
+ devfail "Somehow got asked to copy source values without any metadata" unless metadata
- # Collect everything except the checksum
- values = desc.split("\t")
- other = values.pop
- args = {}
- pinparams.zip(values).each { |param, value|
- if value =~ /^[0-9]+$/
- value = value.to_i
- end
- unless value.nil?
- args[param] = value
+ # Take each of the stats and set them as states on the local file
+ # if a value has not already been provided.
+ [:owner, :mode, :group, :checksum].each do |param|
+ next if param == :owner and Puppet::Util::SUIDManager.uid != 0
+ next if param == :checksum and metadata.ftype == "directory"
+ unless value = @resource[param] and value != :absent
+ @resource[param] = metadata.send(param)
end
- }
-
- # Now decide whether we're doing checksums or symlinks
- if args[:type] == "link"
- args[:target] = other
- else
- args[:checksum] = other
end
- # we can't manage ownership unless we're root, so don't even try
- unless Puppet::Util::SUIDManager.uid == 0
- args.delete(:owner)
- end
-
- return args
- end
-
- # Use the info we get from describe() to check if we're in sync.
- def insync?(currentvalue)
- if currentvalue == :nocopy
- return true
- end
-
- # the only thing this actual state can do is copy files around. Therefore,
- # only pay attention if the remote is a file.
- unless @stats[:type] == "file"
- return true
- end
-
- #FIXARB: Inefficient? Needed to call retrieve on parent's ensure and checksum
- parentensure = @resource.property(:ensure).retrieve
- if parentensure != :absent and ! @resource.replace?
- return true
- end
- # Now, we just check to see if the checksums are the same
- parentchecksum = @resource.property(:checksum).retrieve
- result = (!parentchecksum.nil? and (parentchecksum == @stats[:checksum]))
+ # Set the 'ensure' value, unless we're trying to delete the file.
+ @resource[:ensure] = metadata.ftype unless @resource[:ensure] == :absent
- # Diff the contents if they ask it. This is quite annoying -- we need to do this in
- # 'insync?' because they might be in noop mode, but we don't want to do the file
- # retrieval twice, so we cache the value.
- if ! result and Puppet[:show_diff] and File.exists?(@resource[:path]) and ! @stats[:_diffed]
- @stats[:_remote_content] = get_remote_content
- string_file_diff(@resource[:path], @stats[:_remote_content])
- @stats[:_diffed] = true
+ if metadata.ftype == "link"
+ @resource[:target] = metadata.destination
end
- return result
end
def pinparams
- [:mode, :type, :owner, :group]
+ [:mode, :type, :owner, :group, :content]
end
def found?
- ! (@stats.nil? or @stats[:type].nil?)
+ ! (metadata.nil? or metadata.ftype.nil?)
end
- # This basically calls describe() on our file, and then sets all
- # of the local states appropriately. If the remote file is a normal
- # file then we set it to copy; if it's a directory, then we just mark
- # that the local directory should be created.
- def retrieve(remote = true)
- sum = nil
- @source = nil
-
- # This is set to false by the File#retrieve function on the second
- # retrieve, so that we do not do two describes.
- if remote
- # Find the first source that exists. @shouldorig contains
- # the sources as specified by the user.
- @should.each { |source|
- if @stats = self.describe(source)
- @source = source
+ # Provide, and retrieve if necessary, the metadata for this file. Fail
+ # if we can't find data about this host, and fail if there are any
+ # problems in our query.
+ cached_attr(:metadata) do
+ return nil unless value
+ result = nil
+ value.each do |source|
+ begin
+ if data = Puppet::FileServing::Metadata.find(source)
+ result = data
+ result.source = source
break
end
- }
- end
-
- if !found?
- raise Puppet::Error, "No specified source was found from" + @should.inject("") { |s, source| s + " #{source},"}.gsub(/,$/,"")
- end
-
- case @stats[:type]
- when "directory", "file", "link":
- @resource[:ensure] = @stats[:type] unless @resource.deleting?
- else
- self.info @stats.inspect
- self.err "Cannot use files of type %s as sources" % @stats[:type]
- return :nocopy
+ rescue => detail
+ fail detail, "Could not retrieve file metadata for %s: %s" % [source, detail]
+ end
end
-
- # Take each of the stats and set them as states on the local file
- # if a value has not already been provided.
- @stats.each { |stat, value|
- next if stat == :checksum
- next if stat == :type
-
- # was the stat already specified, or should the value
- # be inherited from the source?
- @resource[stat] = value unless @resource.argument?(stat)
- }
-
- return @stats[:checksum]
+ fail "Could not retrieve information from source(s) %s" % value.join(", ") unless result
+ result
end
-
- def should
- @should
- end
-
+
# Make sure we're also checking the checksum
- def should=(value)
+ def value=(value)
super
checks = (pinparams + [:ensure])
checks.delete(:checksum)
- @resource[:check] = checks
- @resource[:checksum] = :md5 unless @resource.property(:checksum)
- end
-
- def sync
- contents = @stats[:_remote_content] || get_remote_content()
-
- exists = File.exists?(@resource[:path])
-
- @resource.write(contents, :source, @stats[:checksum])
-
- if exists
- return :file_changed
- else
- return :file_created
- end
- end
-
- private
-
- def get_remote_content
- raise Puppet::DevError, "Got told to copy non-file %s" % @resource[:path] unless @stats[:type] == "file"
-
- sourceobj, path = @resource.uri2obj(@source)
-
- begin
- contents = sourceobj.server.retrieve(path, @resource[:links])
- rescue => detail
- self.fail "Could not retrieve %s: %s" % [path, detail]
- end
-
- contents = CGI.unescape(contents) unless sourceobj.server.local
-
- if contents == ""
- self.notice "Could not retrieve contents for %s" % @source
- end
-
- return contents
+ resource[:check] = checks
+ resource[:checksum] = :md5 unless resource.property(:checksum)
end
end
end
diff --git a/lib/puppet/type/file/target.rb b/lib/puppet/type/file/target.rb
index 8949c2af6..1d85e05bc 100644
--- a/lib/puppet/type/file/target.rb
+++ b/lib/puppet/type/file/target.rb
@@ -1,5 +1,5 @@
module Puppet
- Puppet.type(:file).newproperty(:target) do
+ Puppet::Type.type(:file).newproperty(:target) do
desc "The target for creating a link. Currently, symlinks are the
only type supported."
diff --git a/lib/puppet/type/file/type.rb b/lib/puppet/type/file/type.rb
index 65539795b..1835078a1 100755
--- a/lib/puppet/type/file/type.rb
+++ b/lib/puppet/type/file/type.rb
@@ -1,5 +1,5 @@
module Puppet
- Puppet.type(:file).newproperty(:type) do
+ Puppet::Type.type(:file).newproperty(:type) do
require 'etc'
desc "A read-only state to check the file type."
diff --git a/lib/puppet/type/filebucket.rb b/lib/puppet/type/filebucket.rb
index b268610e9..03970aee8 100755
--- a/lib/puppet/type/filebucket.rb
+++ b/lib/puppet/type/filebucket.rb
@@ -54,21 +54,9 @@ module Puppet
defaultto { Puppet[:clientbucketdir] }
end
- # get the actual filebucket object
- def self.bucket(name)
- if object = self[name]
- return object.bucket
- else
- return nil
- end
- end
-
# Create a default filebucket.
def self.mkdefaultbucket
- unless default = self["puppet"]
- return self.create(:name => "puppet", :path => Puppet[:clientbucketdir])
- end
- return nil
+ new(:name => "puppet", :path => Puppet[:clientbucketdir])
end
def self.instances
diff --git a/lib/puppet/type/maillist.rb b/lib/puppet/type/maillist.rb
index 7273488ee..088004a1c 100755
--- a/lib/puppet/type/maillist.rb
+++ b/lib/puppet/type/maillist.rb
@@ -46,7 +46,7 @@ module Puppet
if atype[name]
nil
else
- malias = Puppet::Type.type(:mailalias).create(:name => name, :recipient => recipient, :ensure => should)
+ malias = Puppet::Type.type(:mailalias).new(:name => name, :recipient => recipient, :ensure => should)
end
end.compact
end
diff --git a/lib/puppet/type/package.rb b/lib/puppet/type/package.rb
index 0cea39197..9ed1bf1c8 100644
--- a/lib/puppet/type/package.rb
+++ b/lib/puppet/type/package.rb
@@ -306,28 +306,6 @@ module Puppet
@provider.get(:ensure) != :absent
end
- def initialize(params)
- self.initvars
- provider = nil
- [:provider, "use"].each { |label|
- if params.include?(label)
- provider = params[label]
- params.delete(label)
- end
- }
- if provider
- self[:provider] = provider
- else
- self.setdefaults(:provider)
- end
-
- super(params)
-
- unless @parameters.include?(:provider)
- raise Puppet::DevError, "No package provider set"
- end
- end
-
def retrieve
@provider.properties.inject({}) do |props, ary|
name, value = ary
@@ -337,6 +315,6 @@ module Puppet
props
end
end
- end # Puppet.type(:package)
+ end # Puppet::Type.type(:package)
end
diff --git a/lib/puppet/type/schedule.rb b/lib/puppet/type/schedule.rb
index b8479e01d..5c9cfd7f4 100755
--- a/lib/puppet/type/schedule.rb
+++ b/lib/puppet/type/schedule.rb
@@ -241,7 +241,9 @@ module Puppet
:daily => :day,
:monthly => :month,
:weekly => proc do |prev, now|
- prev.strftime("%U") != now.strftime("%U")
+ # Run the resource if the previous day was after this weekday (e.g., prev is wed, current is tue)
+ # or if it's been more than a week since we ran
+ prev.wday > now.wday or (now - prev) > (24 * 3600 * 7)
end
}
@@ -314,24 +316,20 @@ module Puppet
end
def self.mkdefaultschedules
- return [] if self["puppet"]
-
result = []
Puppet.debug "Creating default schedules"
- result << self.create(
+ result << self.new(
:name => "puppet",
:period => :hourly,
:repeat => "2"
)
# And then one for every period
- @parameters.find { |p| p.name == :period }.values.each { |value|
- unless self[value.to_s]
- result << self.create(
- :name => value.to_s,
- :period => value
- )
- end
+ @parameters.find { |p| p.name == :period }.value_collection.values.each { |value|
+ result << self.new(
+ :name => value.to_s,
+ :period => value
+ )
}
result
diff --git a/lib/puppet/type/tidy.rb b/lib/puppet/type/tidy.rb
index b4df4a9a2..98d69bc3a 100755
--- a/lib/puppet/type/tidy.rb
+++ b/lib/puppet/type/tidy.rb
@@ -1,332 +1,327 @@
-module Puppet
- newtype(:tidy, :parent => Puppet.type(:file)) do
- @doc = "Remove unwanted files based on specific criteria. Multiple
- criteria are OR'd together, so a file that is too large but is not
- old enough will still get tidied.
-
- You must specify either the size or age of the file (or both) for
- files to be tidied."
-
- newparam(:path) do
- desc "The path to the file or directory to manage. Must be fully
- qualified."
- isnamevar
- end
+Puppet::Type.newtype(:tidy) do
+ require 'puppet/file_serving/fileset'
- newparam(:matches) do
- desc "One or more file glob patterns, which restrict the list of
- files to be tidied to those whose basenames match at least one
- of the patterns specified. Multiple patterns can be specified
- using an array."
- end
+ @doc = "Remove unwanted files based on specific criteria. Multiple
+ criteria are OR'd together, so a file that is too large but is not
+ old enough will still get tidied.
- copyparam(Puppet.type(:file), :backup)
-
- newproperty(:ensure) do
- desc "An internal attribute used to determine which files should be removed."
-
- @nodoc = true
-
- TATTRS = [:age, :size]
-
- defaultto :anything # just so we always get this property
-
- def change_to_s(currentvalue, newvalue)
- start = "Tidying"
- if @out.include?(:age)
- start += ", older than %s seconds" % @resource.should(:age)
- end
- if @out.include?(:size)
- start += ", larger than %s bytes" % @resource.should(:size)
- end
-
- start
- end
+ If you don't specify either 'age' or 'size', then all files will
+ be removed.
+
+ This resource type works by generating a file resource for every file
+ that should be deleted and then letting that resource perform the
+ actual deletion.
+ "
- def insync?(is)
- begin
- stat = File.lstat(resource[:path])
- rescue Errno::ENOENT
- info "Tidy target does not exist; ignoring"
- return true
- end
-
- if stat.ftype == "directory" and ! @resource[:rmdirs]
- self.debug "Not tidying directories"
- return true
- end
-
- if is.is_a?(Symbol)
- if [:absent, :notidy].include?(is)
- return true
- else
- return false
- end
- else
- @out = []
- if @resource[:matches]
- basename = File.basename(@resource[:path])
- flags = File::FNM_DOTMATCH | File::FNM_PATHNAME
- unless @resource[:matches].any? {|pattern| File.fnmatch(pattern, basename, flags) }
- self.debug "No patterns specified match basename, skipping"
- return true
- end
- end
- TATTRS.each do |param|
- if property = @resource.property(param)
- self.debug "No is value for %s", [param] if is[property].nil?
- unless property.insync?(is[property])
- @out << param
- end
- end
- end
+ newparam(:path) do
+ desc "The path to the file or directory to manage. Must be fully
+ qualified."
+ isnamevar
+ end
+
+ newparam(:matches) do
+ desc "One or more (shell type) file glob patterns, which restrict
+ the list of files to be tidied to those whose basenames match
+ at least one of the patterns specified. Multiple patterns can
+ be specified using an array.
- if @out.length > 0
- return false
- else
- return true
- end
- end
- end
-
- def retrieve
- stat = nil
- unless stat = @resource.stat
- return { self => :absent}
- end
-
- if stat.ftype == "directory" and ! @resource[:rmdirs]
- return {self => :notidy}
- end
-
- allprops = TATTRS.inject({}) { |prophash, param|
- if property = @resource.property(param)
- prophash[property] = property.assess(stat)
- end
- prophash
- }
- return { self => allprops }
- end
+ tidy { \"/tmp\":
+ age => \"1w\",
+ recurse => false,
+ matches => [ \"[0-9]pub*.tmp\", \"*.temp\", \"tmpfile?\" ]
+ }
+
+ The example above removes files from \/tmp if they are one week
+ old or older, are not in a subdirectory and match one of the shell
+ globs given.
+
+ Note that the patterns are matched against the
+ basename of each file -- that is, your glob patterns should not
+ have any '/' characters in them, since you are only specifying
+ against the last bit of the file."
+
+ # Make sure we convert to an array.
+ munge do |value|
+ value = [value] unless value.is_a?(Array)
+ value
+ end
- def sync
- file = @resource[:path]
- case File.lstat(file).ftype
- when "directory":
- # If 'rmdirs' is disabled, then we would have never
- # gotten to this method.
- subs = Dir.entries(@resource[:path]).reject { |d|
- d == "." or d == ".."
- }.length
- if subs > 0
- self.info "%s has %s children; not tidying" %
- [@resource[:path], subs]
- self.info Dir.entries(@resource[:path]).inspect
- else
- Dir.rmdir(@resource[:path])
- end
- when "file":
- @resource.handlebackup(file)
- File.unlink(file)
- when "link":
- File.unlink(file)
- else
- self.fail "Cannot tidy files of type %s" %
- File.lstat(file).ftype
- end
-
- return :file_tidied
- end
+ # Does a given path match our glob patterns, if any? Return true
+ # if no patterns have been provided.
+ def tidy?(path, stat)
+ basename = File.basename(path)
+ flags = File::FNM_DOTMATCH | File::FNM_PATHNAME
+ return true if value.find {|pattern| File.fnmatch(pattern, basename, flags) }
+ return false
end
+ end
- newproperty(:age) do
- desc "Tidy files whose age is equal to or greater than
- the specified time. You can choose seconds, minutes,
- hours, days, or weeks by specifying the first letter of any
- of those words (e.g., '1w')."
-
- @@ageconvertors = {
- :s => 1,
- :m => 60
- }
-
- @@ageconvertors[:h] = @@ageconvertors[:m] * 60
- @@ageconvertors[:d] = @@ageconvertors[:h] * 24
- @@ageconvertors[:w] = @@ageconvertors[:d] * 7
-
- def assess(stat)
- type = nil
- if stat.ftype == "directory"
- type = :mtime
- else
- type = @resource[:type] || :atime
- end
-
- return stat.send(type).to_i
- end
+ newparam(:backup) do
+ desc "Whether tidied files should be backed up. Any values are passed
+ directly to the file resources used for actual file deletion, so use
+ its backup documentation to determine valid values."
+ end
- def convert(unit, multi)
- if num = @@ageconvertors[unit]
- return num * multi
- else
- self.fail "Invalid age unit '%s'" % unit
- end
- end
+ newparam(:age) do
+ desc "Tidy files whose age is equal to or greater than
+ the specified time. You can choose seconds, minutes,
+ hours, days, or weeks by specifying the first letter of any
+ of those words (e.g., '1w').
+
+ Specifying 0 will remove all files."
- def insync?(is)
- if (Time.now.to_i - is) > self.should
- return false
- end
+ @@ageconvertors = {
+ :s => 1,
+ :m => 60
+ }
- true
- end
+ @@ageconvertors[:h] = @@ageconvertors[:m] * 60
+ @@ageconvertors[:d] = @@ageconvertors[:h] * 24
+ @@ageconvertors[:w] = @@ageconvertors[:d] * 7
- munge do |age|
- unit = multi = nil
- case age
- when /^([0-9]+)(\w)\w*$/:
- multi = Integer($1)
- unit = $2.downcase.intern
- when /^([0-9]+)$/:
- multi = Integer($1)
- unit = :d
- else
- self.fail "Invalid tidy age %s" % age
- end
-
- convert(unit, multi)
+ def convert(unit, multi)
+ if num = @@ageconvertors[unit]
+ return num * multi
+ else
+ self.fail "Invalid age unit '%s'" % unit
end
end
- newproperty(:size) do
- desc "Tidy files whose size is equal to or greater than
- the specified size. Unqualified values are in kilobytes, but
- *b*, *k*, and *m* can be appended to specify *bytes*, *kilobytes*,
- and *megabytes*, respectively. Only the first character is
- significant, so the full word can also be used."
-
- @@sizeconvertors = {
- :b => 0,
- :k => 1,
- :m => 2,
- :g => 3
- }
-
- # Retrieve the size from a File::Stat object
- def assess(stat)
- return stat.size
+ def tidy?(path, stat)
+ # If the file's older than we allow, we should get rid of it.
+ if (Time.now.to_i - stat.send(resource[:type]).to_i) > value
+ return true
+ else
+ return false
end
+ end
- def convert(unit, multi)
- if num = @@sizeconvertors[unit]
- result = multi
- num.times do result *= 1024 end
- return result
- else
- self.fail "Invalid size unit '%s'" % unit
- end
+ munge do |age|
+ unit = multi = nil
+ case age
+ when /^([0-9]+)(\w)\w*$/:
+ multi = Integer($1)
+ unit = $2.downcase.intern
+ when /^([0-9]+)$/:
+ multi = Integer($1)
+ unit = :d
+ else
+ self.fail "Invalid tidy age %s" % age
end
-
- def insync?(is)
- if is > self.should
- return false
- end
- true
- end
-
- munge do |size|
- case size
- when /^([0-9]+)(\w)\w*$/:
- multi = Integer($1)
- unit = $2.downcase.intern
- when /^([0-9]+)$/:
- multi = Integer($1)
- unit = :k
- else
- self.fail "Invalid tidy size %s" % age
- end
-
- convert(unit, multi)
- end
+ convert(unit, multi)
end
+ end
- newparam(:type) do
- desc "Set the mechanism for determining age."
-
- newvalues(:atime, :mtime, :ctime)
-
- defaultto :atime
+ newparam(:size) do
+ desc "Tidy files whose size is equal to or greater than
+ the specified size. Unqualified values are in kilobytes, but
+ *b*, *k*, and *m* can be appended to specify *bytes*, *kilobytes*,
+ and *megabytes*, respectively. Only the first character is
+ significant, so the full word can also be used."
+
+ @@sizeconvertors = {
+ :b => 0,
+ :k => 1,
+ :m => 2,
+ :g => 3
+ }
+
+ def convert(unit, multi)
+ if num = @@sizeconvertors[unit]
+ result = multi
+ num.times do result *= 1024 end
+ return result
+ else
+ self.fail "Invalid size unit '%s'" % unit
+ end
end
-
- newparam(:recurse) do
- desc "If target is a directory, recursively descend
- into the directory looking for files to tidy."
-
- newvalues(:true, :false, :inf, /^[0-9]+$/)
-
- # Replace the validation so that we allow numbers in
- # addition to string representations of them.
- validate { |arg| }
- munge do |value|
- newval = super(value)
- case newval
- when :true, :inf: true
- when :false: false
- when Integer, Fixnum, Bignum: value
- when /^\d+$/: Integer(value)
- else
- raise ArgumentError, "Invalid recurse value %s" % value.inspect
- end
+
+ def tidy?(path, stat)
+ if stat.size > value
+ return true
+ else
+ return false
end
end
+
+ munge do |size|
+ case size
+ when /^([0-9]+)(\w)\w*$/:
+ multi = Integer($1)
+ unit = $2.downcase.intern
+ when /^([0-9]+)$/:
+ multi = Integer($1)
+ unit = :k
+ else
+ self.fail "Invalid tidy size %s" % age
+ end
- newparam(:rmdirs) do
- desc "Tidy directories in addition to files; that is, remove
- directories whose age is older than the specified criteria.
- This will only remove empty directories, so all contained
- files must also be tidied before a directory gets removed."
+ convert(unit, multi)
end
+ end
+
+ newparam(:type) do
+ desc "Set the mechanism for determining age."
- # Erase PFile's validate method
- validate do
+ newvalues(:atime, :mtime, :ctime)
+
+ defaultto :atime
+ end
+
+ newparam(:recurse) do
+ desc "If target is a directory, recursively descend
+ into the directory looking for files to tidy."
+
+ newvalues(:true, :false, :inf, /^[0-9]+$/)
+
+ # Replace the validation so that we allow numbers in
+ # addition to string representations of them.
+ validate { |arg| }
+ munge do |value|
+ newval = super(value)
+ case newval
+ when :true, :inf: true
+ when :false: false
+ when Integer, Fixnum, Bignum: value
+ when /^\d+$/: Integer(value)
+ else
+ raise ArgumentError, "Invalid recurse value %s" % value.inspect
+ end
end
+ end
+
+ newparam(:rmdirs, :boolean => true) do
+ desc "Tidy directories in addition to files; that is, remove
+ directories whose age is older than the specified criteria.
+ This will only remove empty directories, so all contained
+ files must also be tidied before a directory gets removed."
+
+ newvalues :true, :false
+ end
+
+ # Erase PFile's validate method
+ validate do
+ end
+
+ def self.instances
+ []
+ end
+
+ @depthfirst = true
- def self.instances
- []
+ def initialize(hash)
+ super
+
+ # only allow backing up into filebuckets
+ unless self[:backup].is_a? Puppet::Network::Client.dipper
+ self[:backup] = false
end
+ end
+
+ # Make a file resource to remove a given file.
+ def mkfile(path)
+ # Force deletion, so directories actually get deleted.
+ Puppet::Type.type(:file).new :path => path, :backup => self[:backup], :ensure => :absent, :force => true
+ end
- @depthfirst = true
+ def retrieve
+ # Our ensure property knows how to retrieve everything for us.
+ if obj = @parameters[:ensure]
+ return obj.retrieve
+ else
+ return {}
+ end
+ end
+
+ # Hack things a bit so we only ever check the ensure property.
+ def properties
+ []
+ end
- def initialize(hash)
- super
+ def eval_generate
+ []
+ end
- unless @parameters.include?(:age) or
- @parameters.include?(:size)
- unless FileTest.directory?(self[:path])
- # don't do size comparisons for directories
- self.fail "Tidy must specify size, age, or both"
- end
- end
+ def generate
+ return [] unless stat(self[:path])
- # only allow backing up into filebuckets
- unless self[:backup].is_a? Puppet::Network::Client.dipper
- self[:backup] = false
+ if self[:recurse]
+ files = Puppet::FileServing::Fileset.new(self[:path], :recurse => self[:recurse]).files.collect do |f|
+ f == "." ? self[:path] : File.join(self[:path], f)
end
+ else
+ files = [self[:path]]
end
-
- def retrieve
- # Our ensure property knows how to retrieve everything for us.
- if obj = @parameters[:ensure]
- return obj.retrieve
+ result = files.find_all { |path| tidy?(path) }.collect { |path| mkfile(path) }.each { |file| notice "Tidying %s" % file.ref }.sort { |a,b| b[:path] <=> a[:path] }
+
+ # No need to worry about relationships if we don't have rmdirs; there won't be
+ # any directories.
+ return result unless rmdirs?
+
+ # Now make sure that all directories require the files they contain, if all are available,
+ # so that a directory is emptied before we try to remove it.
+ files_by_name = result.inject({}) { |hash, file| hash[file[:path]] = file; hash }
+
+ files_by_name.keys.sort { |a,b| b <=> b }.each do |path|
+ dir = File.dirname(path)
+ next unless resource = files_by_name[dir]
+ if resource[:require]
+ resource[:require] << Puppet::Resource::Reference.new(:file, path)
else
- return {}
+ resource[:require] = [Puppet::Resource::Reference.new(:file, path)]
end
end
-
- # Hack things a bit so we only ever check the ensure property.
- def properties
- []
+
+ return result
+ end
+
+ # Does a given path match our glob patterns, if any? Return true
+ # if no patterns have been provided.
+ def matches?(path)
+ return true unless self[:matches]
+
+ basename = File.basename(path)
+ flags = File::FNM_DOTMATCH | File::FNM_PATHNAME
+ if self[:matches].find {|pattern| File.fnmatch(pattern, basename, flags) }
+ return true
+ else
+ debug "No specified patterns match %s, not tidying" % path
+ return false
end
end
-end
+ # Should we remove the specified file?
+ def tidy?(path)
+ return false unless stat = self.stat(path)
+
+ return false if stat.ftype == "directory" and ! rmdirs?
+
+ # The 'matches' parameter isn't OR'ed with the other tests --
+ # it's just used to reduce the list of files we can match.
+ return false if param = parameter(:matches) and ! param.tidy?(path, stat)
+
+ tested = false
+ [:age, :size].each do |name|
+ next unless param = parameter(name)
+ tested = true
+ return true if param.tidy?(path, stat)
+ end
+
+ # If they don't specify either, then the file should always be removed.
+ return true unless tested
+ return false
+ end
+
+ def stat(path)
+ begin
+ File.lstat(path)
+ rescue Errno::ENOENT => error
+ info "File does not exist"
+ return nil
+ rescue Errno::EACCES => error
+ warning "Could not stat; permission denied"
+ return nil
+ end
+ end
+end
diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb
index 6b6ff82ab..8efc2ef1c 100755
--- a/lib/puppet/type/user.rb
+++ b/lib/puppet/type/user.rb
@@ -229,11 +229,8 @@ module Puppet
groups.each { |group|
case group
when Integer:
- if obj = Puppet.type(:group).find { |gobj|
- gobj.should(:gid) == group
- }
- autos << obj
-
+ if resource = catalog.resources.find { |r| r.is_a?(Puppet::Type.type(:group)) and r.should(:gid) == group }
+ autos << resource
end
else
autos << group
diff --git a/lib/puppet/type/yumrepo.rb b/lib/puppet/type/yumrepo.rb
index 164deaffc..4f7b26b8a 100644
--- a/lib/puppet/type/yumrepo.rb
+++ b/lib/puppet/type/yumrepo.rb
@@ -66,8 +66,9 @@ module Puppet
class << self
attr_accessor :filetype
# The writer is only used for testing, there should be no need
- # to change yumconf in any other context
+ # to change yumconf or inifile in any other context
attr_accessor :yumconf
+ attr_writer :inifile
end
self.filetype = Puppet::Util::FileType.filetype(:flat)
@@ -183,11 +184,11 @@ module Puppet
end
end
+ # This is only used during testing.
def self.clear
@inifile = nil
@yumconf = "/etc/yum.conf"
@defaultrepodir = nil
- super
end
# Return the Puppet::Util::IniConfig::Section for this yumrepo resource
diff --git a/lib/puppet/type/zone.rb b/lib/puppet/type/zone.rb
index 8c4261241..6e5d784b3 100644
--- a/lib/puppet/type/zone.rb
+++ b/lib/puppet/type/zone.rb
@@ -77,6 +77,7 @@ Puppet::Type.newtype(:zone) do
used to stop zones."
@states = {}
+ @parametervalues = []
def self.alias_state(values)
@state_aliases ||= {}
diff --git a/lib/puppet/util/cacher.rb b/lib/puppet/util/cacher.rb
new file mode 100644
index 000000000..a9fb890c6
--- /dev/null
+++ b/lib/puppet/util/cacher.rb
@@ -0,0 +1,104 @@
+module Puppet::Util::Cacher
+ module Expirer
+ attr_reader :timestamp
+
+ # Cause all cached values to be considered expired.
+ def expire
+ @timestamp = Time.now
+ end
+
+ # Is the provided timestamp earlier than our expiration timestamp?
+ # If it is, then the associated value is expired.
+ def expired?(ts)
+ return false unless timestamp
+
+ return timestamp > ts
+ end
+ end
+
+ extend Expirer
+
+ # Our module has been extended in a class; we can only add the Instance methods,
+ # which become *class* methods in the class.
+ def self.extended(other)
+ class << other
+ extend ClassMethods
+ include InstanceMethods
+ end
+ end
+
+ # Our module has been included in a class, which means the class gets the class methods
+ # and all of its instances get the instance methods.
+ def self.included(other)
+ other.extend(ClassMethods)
+ other.send(:include, InstanceMethods)
+ end
+
+ # Methods that can get added to a class.
+ module ClassMethods
+ private
+
+ # Provide a means of defining an attribute whose value will be cached.
+ # Must provide a block capable of defining the value if it's flushed..
+ def cached_attr(name, &block)
+ init_method = "init_" + name.to_s
+ define_method(init_method, &block)
+
+ define_method(name) do
+ cached_value(name)
+ end
+
+ define_method(name.to_s + "=") do |value|
+ # Make sure the cache timestamp is set
+ cache_timestamp
+ value_cache[name] = value
+ end
+ end
+ end
+
+ # Methods that get added to instances.
+ module InstanceMethods
+ def expire
+ # Only expire if we have an expirer. This is
+ # mostly so that we can comfortably handle cases
+ # like Puppet::Type instances, which use their
+ # catalog as their expirer, and they often don't
+ # have a catalog.
+ if e = expirer
+ e.expire
+ end
+ end
+
+ def expirer
+ Puppet::Util::Cacher
+ end
+
+ private
+
+ def cache_timestamp
+ unless defined?(@cache_timestamp)
+ @cache_timestamp = Time.now
+ end
+ @cache_timestamp
+ end
+
+ def cached_value(name)
+ # Allow a nil expirer, in which case we regenerate the value every time.
+ if expirer.nil? or expirer.expired?(cache_timestamp)
+ value_cache.clear
+ @cache_timestamp = Time.now
+ end
+ unless value_cache.include?(name)
+ value_cache[name] = send("init_%s" % name)
+ end
+ value_cache[name]
+ end
+
+ def value_cache
+ unless defined?(@value_cache) and @value_cache
+ @value_cache = {}
+ end
+ @value_cache
+ end
+ end
+end
diff --git a/lib/puppet/util/checksums.rb b/lib/puppet/util/checksums.rb
index 15d2eadd1..4b51789f6 100644
--- a/lib/puppet/util/checksums.rb
+++ b/lib/puppet/util/checksums.rb
@@ -1,6 +1,15 @@
# A stand-alone module for calculating checksums
# in a generic way.
module Puppet::Util::Checksums
+ # Strip the checksum type from an existing checksum
+ def sumtype(checksum)
+ if checksum =~ /^\{(\w+)\}/
+ return $1
+ else
+ return nil
+ end
+ end
+
# Calculate a checksum using Digest::MD5.
def md5(content)
require 'digest/md5'
diff --git a/lib/puppet/util/filetype.rb b/lib/puppet/util/filetype.rb
index 40c028cc2..34feeab90 100755
--- a/lib/puppet/util/filetype.rb
+++ b/lib/puppet/util/filetype.rb
@@ -74,8 +74,10 @@ class Puppet::Util::FileType
# Pick or create a filebucket to use.
def bucket
- filebucket = Puppet::Type.type(:filebucket)
- (filebucket["puppet"] || filebucket.mkdefaultbucket).bucket
+ unless defined?(@bucket)
+ @bucket = Puppet::Type.type(:filebucket).mkdefaultbucket.bucket
+ end
+ @bucket
end
def initialize(path)
diff --git a/lib/puppet/util/graph.rb b/lib/puppet/util/graph.rb
index d1ef36f8e..fc05cafd9 100644
--- a/lib/puppet/util/graph.rb
+++ b/lib/puppet/util/graph.rb
@@ -2,7 +2,7 @@
# Copyright (c) 2006. All rights reserved.
require 'puppet'
-require 'puppet/pgraph'
+require 'puppet/simple_graph'
# A module that handles the small amount of graph stuff in Puppet.
module Puppet::Util::Graph
@@ -12,7 +12,7 @@ module Puppet::Util::Graph
def to_graph(graph = nil, &block)
# Allow our calling function to send in a graph, so that we
# can call this recursively with one graph.
- graph ||= Puppet::PGraph.new
+ graph ||= Puppet::SimpleGraph.new
self.each do |child|
unless block_given? and ! yield(child)
diff --git a/lib/puppet/util/log.rb b/lib/puppet/util/log.rb
index b57faad42..a74432021 100644
--- a/lib/puppet/util/log.rb
+++ b/lib/puppet/util/log.rb
@@ -104,6 +104,13 @@ class Puppet::Util::Log
end
end
+ def self.close_all
+ # And close all logs except the console.
+ destinations.each do |dest|
+ close(dest)
+ end
+ end
+
# Flush any log destinations that support such operations.
def Log.flush
@destinations.each { |type, dest|
diff --git a/lib/puppet/util/package.rb b/lib/puppet/util/package.rb
index 00e04f64a..613aa6b1e 100644
--- a/lib/puppet/util/package.rb
+++ b/lib/puppet/util/package.rb
@@ -28,4 +28,6 @@ module Puppet::Util::Package
end
return version_a <=> version_b;
end
+
+ module_function :versioncmp
end
diff --git a/lib/puppet/util/posix.rb b/lib/puppet/util/posix.rb
index b969a041c..3f6c1f6e3 100755
--- a/lib/puppet/util/posix.rb
+++ b/lib/puppet/util/posix.rb
@@ -58,45 +58,6 @@ module Puppet::Util::POSIX
return nil
end
- # Look in memory for an already-managed type and use its info if available.
- # Currently unused.
- def get_provider_value(type, field, id)
- unless typeklass = Puppet::Type.type(type)
- raise ArgumentError, "Invalid type %s" % type
- end
-
- id = id.to_s
-
- chkfield = idfield(type)
- obj = typeklass.find { |obj|
- if id =~ /^\d+$/
- obj.should(chkfield).to_s == id ||
- obj.provider.send(chkfield) == id
- else
- obj[:name] == id
- end
- }
-
- return nil unless obj
-
- if obj.provider
- begin
- val = obj.provider.send(field)
- if val == :absent
- return nil
- else
- return val
- end
- rescue => detail
- if Puppet[:trace]
- puts detail.backtrace
- Puppet.err detail
- return nil
- end
- end
- end
- end
-
# Determine what the field name is for users and groups.
def idfield(space)
case Puppet::Util.symbolize(space)
diff --git a/lib/puppet/util/rdoc.rb b/lib/puppet/util/rdoc.rb
index b33e67c71..a18fa1b96 100644
--- a/lib/puppet/util/rdoc.rb
+++ b/lib/puppet/util/rdoc.rb
@@ -22,6 +22,7 @@ module Puppet::Util::RDoc
# specify our own format & where to output
options = [ "--fmt", "puppet",
"--quiet",
+ "--force-update",
"--op", outputdir ]
options += files
diff --git a/lib/puppet/util/resource_template.rb b/lib/puppet/util/resource_template.rb
index 53066a192..4e333571f 100644
--- a/lib/puppet/util/resource_template.rb
+++ b/lib/puppet/util/resource_template.rb
@@ -18,7 +18,7 @@ require 'erb'
# def generate
# template = Puppet::Util::ResourceTemplate.new("/path/to/template", self)
#
-# return Puppet::Type.type(:file).create :path => "/my/file",
+# return Puppet::Type.type(:file).new :path => "/my/file",
# :content => template.evaluate
# end
#
diff --git a/lib/puppet/util/settings.rb b/lib/puppet/util/settings.rb
index ac25e0815..0af842c8d 100644
--- a/lib/puppet/util/settings.rb
+++ b/lib/puppet/util/settings.rb
@@ -3,11 +3,11 @@ require 'sync'
require 'puppet/transportable'
require 'getoptlong'
+require 'puppet/external/event-loop'
# The class for handling configuration files.
class Puppet::Util::Settings
include Enumerable
- include Puppet::Util
attr_accessor :file
attr_reader :timer
@@ -19,7 +19,7 @@ class Puppet::Util::Settings
# Set a config value. This doesn't set the defaults, it sets the value itself.
def []=(param, value)
- param = symbolize(param)
+ param = param.to_sym
unless element = @config[param]
raise ArgumentError,
"Attempt to assign a value to unknown configuration parameter %s" % param.inspect
@@ -37,6 +37,8 @@ class Puppet::Util::Settings
@sync.synchronize do # yay, thread-safe
@values[:memory][param] = value
@cache.clear
+
+ clearused
end
return value
@@ -45,9 +47,6 @@ class Puppet::Util::Settings
# Generate the list of valid arguments, in a format that GetoptLong can
# understand, and add them to the passed option list.
def addargs(options)
- # Hackish, but acceptable. Copy the current ARGV for restarting.
- Puppet.args = ARGV.dup
-
# Add all of the config parameters as valid options.
self.each { |name, element|
element.getopt_args.each { |args| options << args }
@@ -56,24 +55,9 @@ class Puppet::Util::Settings
return options
end
- def apply
- trans = self.to_transportable
- begin
- config = trans.to_catalog
- config.store_state = false
- config.apply
- config.clear
- rescue => detail
- if Puppet[:trace]
- puts detail.backtrace
- end
- Puppet.err "Could not configure myself: %s" % detail
- end
- end
-
# Is our parameter a boolean parameter?
def boolean?(param)
- param = symbolize(param)
+ param = param.to_sym
if @config.include?(param) and @config[param].kind_of? CBoolean
return true
else
@@ -126,7 +110,7 @@ class Puppet::Util::Settings
# Return a value's description.
def description(name)
- if obj = @config[symbolize(name)]
+ if obj = @config[name.to_sym]
obj.desc
else
nil
@@ -153,7 +137,7 @@ class Puppet::Util::Settings
# Return an object by name.
def element(param)
- param = symbolize(param)
+ param = param.to_sym
@config[param]
end
@@ -277,7 +261,7 @@ class Puppet::Util::Settings
# Return a given object's file metadata.
def metadata(param)
- if obj = @config[symbolize(param)] and obj.is_a?(CFile)
+ if obj = @config[param.to_sym] and obj.is_a?(CFile)
return [:owner, :group, :mode].inject({}) do |meta, p|
if v = obj.send(p)
meta[p] = v
@@ -383,115 +367,30 @@ class Puppet::Util::Settings
end
end
- private :unsafe_parse
-
- # Parse the configuration file. As of May 2007, this is a backward-compatibility method and
- # will be deprecated soon.
- def old_parse(file)
- text = nil
-
- if file.is_a? Puppet::Util::LoadedFile
- @file = file
- else
- @file = Puppet::Util::LoadedFile.new(file)
- end
-
- # Don't create a timer for the old style parsing.
- # settimer()
-
- begin
- text = File.read(@file.file)
- rescue Errno::ENOENT
- raise Puppet::Error, "No such file %s" % file
- rescue Errno::EACCES
- raise Puppet::Error, "Permission denied to file %s" % file
- end
-
- @sync.synchronize do
- @values = Hash.new { |names, name|
- names[name] = {}
- }
- end
-
- # Get rid of the values set by the file, keeping cli values.
- self.clear(true)
-
- section = "puppet"
- metas = %w{owner group mode}
- values = Hash.new { |hash, key| hash[key] = {} }
- @sync.synchronize do
- text.split(/\n/).each { |line|
- case line
- when /^\[(\w+)\]$/: section = $1 # Section names
- when /^\s*#/: next # Skip comments
- when /^\s*$/: next # Skip blanks
- when /^\s*(\w+)\s*=\s*(.+)$/: # settings
- var = $1.intern
- if var == :mode
- value = $2
- else
- value = munge_value($2)
- end
-
- # Only warn if we don't know what this config var is. This
- # prevents exceptions later on.
- unless @config.include?(var) or metas.include?(var.to_s)
- Puppet.warning "Discarded unknown configuration parameter %s" % var.inspect
- next # Skip this line.
- end
-
- # Mmm, "special" attributes
- if metas.include?(var.to_s)
- unless values.include?(section)
- values[section] = {}
- end
- values[section][var.to_s] = value
-
- # If the parameter is valid, then set it.
- if section == Puppet[:name] and @config.include?(var)
- #@config[var].value = value
- @values[:main][var] = value
- end
- next
- end
-
- # Don't override set parameters, since the file is parsed
- # after cli arguments are handled.
- unless @config.include?(var) and @config[var].setbycli
- Puppet.debug "%s: Setting %s to '%s'" % [section, var, value]
- @values[:main][var] = value
- end
- @config[var].section = symbolize(section)
-
- metas.each { |meta|
- if values[section][meta]
- if @config[var].respond_to?(meta + "=")
- @config[var].send(meta + "=", values[section][meta])
- end
- end
- }
- else
- raise Puppet::Error, "Could not match line %s" % line
- end
- }
- end
- end
-
- # Create a new config option.
+ # Create a new element. The value is passed in because it's used to determine
+ # what kind of element we're creating, but the value itself might be either
+ # a default or a value, so we can't actually assign it.
def newelement(hash)
klass = nil
if hash[:section]
- hash[:section] = symbolize(hash[:section])
- end
- case hash[:default]
- when true, false, "true", "false":
- klass = CBoolean
- when /^\$\w+\//, /^\//:
- klass = CFile
- when String, Integer, Float: # nothing
- klass = CElement
+ hash[:section] = hash[:section].to_sym
+ end
+ if type = hash[:type]
+ unless klass = {:element => CElement, :file => CFile, :boolean => CBoolean}[type]
+ raise ArgumentError, "Invalid setting type '%s'" % type
+ end
+ hash.delete(:type)
else
- raise Puppet::Error, "Invalid value '%s' for %s" % [value.inspect, hash[:name]]
+ case hash[:default]
+ when true, false, "true", "false":
+ klass = CBoolean
+ when /^\$\w+\//, /^\//:
+ klass = CFile
+ when String, Integer, Float: # nothing
+ klass = CElement
+ else
+ raise Puppet::Error, "Invalid value '%s' for %s" % [value.inspect, hash[:name]]
+ end
end
hash[:settings] = self
element = klass.new(hash)
@@ -504,7 +403,7 @@ class Puppet::Util::Settings
# Iterate across all of the objects in a given section.
def persection(section)
- section = symbolize(section)
+ section = section.to_sym
self.each { |name, obj|
if obj.section == section
yield obj
@@ -526,10 +425,9 @@ class Puppet::Util::Settings
def reuse
return unless defined? @used
@sync.synchronize do # yay, thread-safe
- @used.each do |section|
- @used.delete(section)
- self.use(section)
- end
+ new = @used
+ @used = []
+ self.use(*new)
end
end
@@ -557,45 +455,10 @@ class Puppet::Util::Settings
return sectionlist, sections
end
- # Convert a single section into transportable objects.
- def section_to_transportable(section, done = nil)
- done ||= Hash.new { |hash, key| hash[key] = {} }
- objects = []
- persection(section) do |obj|
- if @config[:mkusers] and value(:mkusers)
- objects += add_user_resources(section, obj, done)
- end
-
- value = obj.value
-
- # Only files are convertable to transportable resources.
- next unless obj.respond_to? :to_transportable and transobjects = obj.to_transportable
-
- transobjects = [transobjects] unless transobjects.is_a? Array
- transobjects.each do |trans|
- # transportable could return nil
- next unless trans
- unless done[:file].include? trans.name
- @created << trans.name
- objects << trans
- done[:file][trans.name] = trans
- end
- end
- end
-
- bucket = Puppet::TransBucket.new
- bucket.type = "Settings"
- bucket.name = section
- bucket.push(*objects)
- bucket.keyword = "class"
-
- return bucket
- end
-
# Set a bunch of defaults in a given section. The sections are actually pretty
# pointless, but they help break things up a bit, anyway.
def setdefaults(section, defs)
- section = symbolize(section)
+ section = section.to_sym
call = []
defs.each { |name, hash|
if hash.is_a? Array
@@ -606,7 +469,7 @@ class Puppet::Util::Settings
hash = {}
[:default, :desc].zip(tmp).each { |p,v| hash[p] = v }
end
- name = symbolize(name)
+ name = name.to_sym
hash[:name] = name
hash[:section] = section
if @config.include?(name)
@@ -631,22 +494,30 @@ class Puppet::Util::Settings
end
# Create a timer to check whether the file should be reparsed.
- def settimer
- if Puppet[:filetimeout] > 0
- @timer = Puppet.newtimer(
- :interval => Puppet[:filetimeout],
- :tolerance => 1,
- :start? => true
- ) do
- self.reparse()
- end
- end
+ def set_filetimeout_timer
+ return unless timeout = self[:filetimeout] and timeout > 0
+ EventLoop::Timer.new(:interval => timeout, :tolerance => 1, :start? => true) { self.reparse() }
end
- # Convert our list of objects into a component that can be applied.
- def to_configuration
- transport = self.to_transportable
- return transport.to_catalog
+ # Convert the settings we manage into a catalog full of resources that model those settings.
+ # We currently have to go through Trans{Object,Bucket} instances,
+ # because this hasn't been ported yet.
+ def to_catalog(*sections)
+ sections = nil if sections.empty?
+
+ catalog = Puppet::Resource::Catalog.new("Settings")
+
+ @config.values.find_all { |value| value.is_a?(CFile) }.each do |file|
+ next unless (sections.nil? or sections.include?(file.section))
+ next unless resource = file.to_resource
+ next if catalog.resource(resource.ref)
+
+ catalog.add_resource(resource)
+ end
+
+ add_user_resources(catalog, sections)
+
+ catalog
end
# Convert our list of config elements into a configuration file.
@@ -677,62 +548,29 @@ Generated on #{Time.now}.
return str
end
- # Convert our configuration into a list of transportable objects.
- def to_transportable(*sections)
- done = Hash.new { |hash, key|
- hash[key] = {}
- }
-
- topbucket = Puppet::TransBucket.new
- if defined? @file.file and @file.file
- topbucket.name = @file.file
- else
- topbucket.name = "top"
- end
- topbucket.type = "Settings"
- topbucket.top = true
-
- # Now iterate over each section
- if sections.empty?
- eachsection do |section|
- sections << section
- end
- end
- sections.each do |section|
- obj = section_to_transportable(section, done)
- topbucket.push obj
- end
-
- topbucket
- end
-
# Convert to a parseable manifest
def to_manifest
- transport = self.to_transportable
-
- manifest = transport.to_manifest + "\n"
- eachsection { |section|
- manifest += "include #{section}\n"
- }
-
- return manifest
+ catalog = to_catalog
+ # The resource list is a list of references, not actual instances.
+ catalog.resources.collect do |ref|
+ catalog.resource(ref).to_manifest
+ end.join("\n\n")
end
# Create the necessary objects to use a section. This is idempotent;
# you can 'use' a section as many times as you want.
def use(*sections)
+ sections = sections.collect { |s| s.to_sym }
@sync.synchronize do # yay, thread-safe
- sections = sections.reject { |s| @used.include?(s.to_sym) }
+ sections = sections.reject { |s| @used.include?(s) }
return if sections.empty?
- bucket = to_transportable(*sections)
-
begin
- catalog = bucket.to_catalog
+ catalog = to_catalog(*sections).to_ral
rescue => detail
puts detail.backtrace if Puppet[:trace]
- Puppet.err "Could not create resources for managing Puppet's files and directories: %s" % detail
+ Puppet.err "Could not create resources for managing Puppet's files and directories in sections %s: %s" % [sections.inspect, detail]
# We need some way to get rid of any resources created during the catalog creation
# but not cleaned up.
@@ -748,8 +586,6 @@ Generated on #{Time.now}.
raise "Got %s failure(s) while initializing: %s" % [failures.length, failures.collect { |l| l.to_s }.join("; ")]
end
end
- ensure
- catalog.clear
end
sections.each { |s| @used << s }
@@ -758,15 +594,15 @@ Generated on #{Time.now}.
end
def valid?(param)
- param = symbolize(param)
+ param = param.to_sym
@config.has_key?(param)
end
# Find the correct value using our search path. Optionally accept an environment
# in which to search before the other configuration sections.
def value(param, environment = nil)
- param = symbolize(param)
- environment = symbolize(environment) if environment
+ param = param.to_sym
+ environment = environment.to_sym if environment
# Short circuit to nil for undefined parameters.
return nil unless @config.include?(param)
@@ -850,20 +686,26 @@ Generated on #{Time.now}.
end
sync.synchronize(Sync::EX) do
- File.open(file, "r+", 0600) do |rf|
+ File.open(file, ::File::CREAT|::File::RDWR, 0600) do |rf|
rf.lock_exclusive do
if File.exist?(tmpfile)
raise Puppet::Error, ".tmp file already exists for %s; Aborting locked write. Check the .tmp file and delete if appropriate" %
[file]
end
- writesub(default, tmpfile, *args, &bloc)
+ # If there's a failure, remove our tmpfile
+ begin
+ writesub(default, tmpfile, *args, &bloc)
+ rescue
+ File.unlink(tmpfile) if FileTest.exist?(tmpfile)
+ raise
+ end
begin
File.rename(tmpfile, file)
rescue => detail
- Puppet.err "Could not rename %s to %s: %s" %
- [file, tmpfile, detail]
+ Puppet.err "Could not rename %s to %s: %s" % [file, tmpfile, detail]
+ File.unlink(tmpfile) if FileTest.exist?(tmpfile)
end
end
end
@@ -886,45 +728,25 @@ Generated on #{Time.now}.
end
# Create the transportable objects for users and groups.
- def add_user_resources(section, obj, done)
- resources = []
- [:owner, :group].each do |attr|
- type = nil
- if attr == :owner
- type = :user
- else
- type = attr
- end
- # If a user and/or group is set, then make sure we're
- # managing that object
- if obj.respond_to? attr and name = obj.send(attr)
- # Skip root or wheel
- next if %w{root wheel}.include?(name.to_s)
-
- # Skip owners and groups we've already done, but tag
- # them with our section if necessary
- if done[type].include?(name)
- tags = done[type][name].tags
- unless tags.include?(section)
- done[type][name].tags = tags << section
- end
- else
- newobj = Puppet::TransObject.new(name, type.to_s)
- newobj.tags = ["puppet", "configuration", section]
- newobj[:ensure] = :present
- if type == :user
- newobj[:comment] ||= "%s user" % name
- end
- # Set the group appropriately for the user
- if type == :user
- newobj[:gid] = Puppet[:group]
- end
- done[type][name] = newobj
- resources << newobj
+ def add_user_resources(catalog, sections)
+ return unless Puppet.features.root?
+ return unless self[:mkusers]
+
+ @config.each do |name, element|
+ next unless element.respond_to?(:owner)
+ next unless sections.nil? or sections.include?(element.section)
+
+ if user = element.owner and user != "root" and catalog.resource(:user, user).nil?
+ resource = Puppet::Resource.new(:user, user, :ensure => :present)
+ if self[:group]
+ resource[:gid] = self[:group]
end
+ catalog.add_resource resource
+ end
+ if group = element.group and ! %w{root wheel}.include?(group) and catalog.resource(:group, group).nil?
+ catalog.add_resource Puppet::Resource.new(:group, group, :ensure => :present)
end
end
- resources
end
# Yield each search source in turn.
@@ -991,7 +813,7 @@ Generated on #{Time.now}.
# Create a timer so that this file will get checked automatically
# and reparsed if necessary.
- settimer()
+ set_filetimeout_timer()
result = Hash.new { |names, name|
names[name] = {}
@@ -1096,8 +918,9 @@ Generated on #{Time.now}.
# Create the new element. Pretty much just sets the name.
def initialize(args = {})
- @settings = args.delete(:settings)
- raise ArgumentError.new("You must refer to a settings object") if @settings.nil? or !@settings.is_a?(Puppet::Util::Settings)
+ unless @settings = args.delete(:settings)
+ raise ArgumentError.new("You must refer to a settings object")
+ end
args.each do |param, value|
method = param.to_s + "="
@@ -1177,6 +1000,11 @@ Generated on #{Time.now}.
attr_writer :owner, :group
attr_accessor :mode, :create
+ # Should we create files, rather than just directories?
+ def create_files?
+ create
+ end
+
def group
if defined? @group
return @settings.convert(@group)
@@ -1222,58 +1050,35 @@ Generated on #{Time.now}.
end
end
- # Convert the object to a TransObject instance.
- def to_transportable
- type = self.type
- return nil unless type
+ # Turn our setting thing into a Puppet::Resource instance.
+ def to_resource
+ return nil unless type = self.type
path = self.value
return nil unless path.is_a?(String)
- return nil if path =~ /^\/dev/
- return nil if Puppet::Type.type(:file)[path] # skip files that are in our global resource list.
- objects = []
+ # Make sure the paths are fully qualified.
+ path = File.join(Dir.getwd, path) unless path =~ /^\//
- # Skip plain files that don't exist, since we won't be managing them anyway.
- return nil unless self.name.to_s =~ /dir$/ or File.exist?(path) or self.create
- obj = Puppet::TransObject.new(path, "file")
+ return nil unless type == :directory or create_files? or File.exist?(path)
+ return nil if path =~ /^\/dev/
- # Only create directories, or files that are specifically marked to
- # create.
- if type == :directory or self.create
- obj[:ensure] = type
- end
- [:mode].each { |var|
- if value = self.send(var)
- # Don't bother converting the mode, since the file type
- # can handle it any old way.
- obj[var] = value
- end
- }
+ resource = Puppet::Resource.new(:file, path)
+ resource[:mode] = self.mode if self.mode
- # Only chown or chgrp when root
if Puppet.features.root?
- [:group, :owner].each { |var|
- if value = self.send(var)
- obj[var] = value
- end
- }
+ resource[:owner] = self.owner if self.owner
+ resource[:group] = self.group if self.group
end
- # And set the loglevel to debug for everything
- obj[:loglevel] = "debug"
-
- # We're not actually modifying any files here, and if we allow a
- # filebucket to get used here we get into an infinite recursion
- # trying to set the filebucket up.
- obj[:backup] = false
+ resource[:ensure] = type
+ resource[:loglevel] = :debug
+ resource[:backup] = false
- if self.section
- obj.tags += ["puppet", "configuration", self.section, self.name]
- end
- objects << obj
- objects
+ resource.tag(self.section, self.name, "settings")
+
+ resource
end
# Make sure any provided variables look up to something.