summaryrefslogtreecommitdiffstats
path: root/lib/puppet
diff options
context:
space:
mode:
Diffstat (limited to 'lib/puppet')
-rw-r--r--lib/puppet/defaults.rb61
-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)14
-rw-r--r--lib/puppet/file_serving/configuration.rb18
-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.rb25
-rw-r--r--lib/puppet/file_serving/terminus_helper.rb14
-rw-r--r--lib/puppet/indirector.rb2
-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/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.rb32
-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/request.rb53
-rw-r--r--lib/puppet/indirector/rest.rb105
-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/network/client/ca.rb2
-rw-r--r--lib/puppet/network/client/master.rb1
-rw-r--r--lib/puppet/network/format.rb79
-rw-r--r--lib/puppet/network/format_handler.rb84
-rw-r--r--lib/puppet/network/formats.rb77
-rwxr-xr-xlib/puppet/network/handler/fileserver.rb9
-rwxr-xr-xlib/puppet/network/handler/resource.rb14
-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.rb84
-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.rb4
-rw-r--r--lib/puppet/network/server.rb140
-rw-r--r--lib/puppet/node.rb2
-rw-r--r--lib/puppet/node/catalog.rb6
-rw-r--r--lib/puppet/resource_reference.rb11
-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.rb285
-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.rb223
-rw-r--r--lib/puppet/ssl/inventory.rb52
-rw-r--r--lib/puppet/ssl/key.rb56
-rw-r--r--lib/puppet/transaction.rb2
-rw-r--r--lib/puppet/type.rb70
-rw-r--r--lib/puppet/type/component.rb4
-rw-r--r--lib/puppet/type/file.rb548
-rwxr-xr-xlib/puppet/type/file/source.rb201
-rwxr-xr-xlib/puppet/type/filebucket.rb14
-rwxr-xr-xlib/puppet/type/schedule.rb16
-rwxr-xr-xlib/puppet/type/user.rb14
-rw-r--r--lib/puppet/type/yumrepo.rb5
-rw-r--r--lib/puppet/util/cacher.rb93
-rwxr-xr-xlib/puppet/util/filetype.rb9
-rwxr-xr-xlib/puppet/util/posix.rb39
-rw-r--r--lib/puppet/util/settings.rb110
77 files changed, 2710 insertions, 1150 deletions
diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb
index e1b6dc423..ba47dcd25 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,20 @@ 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."]
)
hostname = Facter["hostname"].value
@@ -152,7 +159,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 +170,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 +193,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 +210,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 +246,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 +279,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,7 +312,7 @@ module Puppet
self.setdefaults(self.settings[:name],
:config => ["$confdir/puppet.conf",
"The configuration file for #{Puppet[:name]}."],
- :pidfile => ["", "The pid file"],
+ :pidfile => ["$rundir/$name.pid", "The pid file"],
:bindaddress => ["", "The address to 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
@@ -381,19 +405,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,
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..c59a54786 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
- 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..bceecc30c 100644
--- a/lib/puppet/file_serving/configuration.rb
+++ b/lib/puppet/file_serving/configuration.rb
@@ -5,25 +5,20 @@
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'
+ extend Puppet::Util::Cacher
+
@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
+ attr_cache(:configuration) { new() }
end
private_class_method :new
@@ -103,13 +98,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..c52cedbfb 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'
@@ -12,12 +13,16 @@ require 'puppet/file_serving/content'
# or content objects.
class Puppet::FileServing::Mount < Puppet::Network::AuthStore
include Puppet::Util::Logging
+ extend Puppet::Util::Cacher
- @@localmap = nil
-
- # Clear the cache. This is only ever used for testing.
- def self.clear_cache
- @@localmap = nil
+ def self.localmap
+ attr_cache(:localmap) {
+ { "h" => Facter.value("hostname"),
+ "H" => [Facter.value("hostname"),
+ Facter.value("domain")].join("."),
+ "d" => Facter.value("domain")
+ }
+ }
end
attr_reader :name
@@ -173,14 +178,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/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/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..04c3aed23 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)
@@ -54,13 +51,6 @@ class Puppet::Indirector::Indirection
@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.
@@ -273,7 +262,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,7 +274,9 @@ 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
@@ -299,4 +294,9 @@ class Puppet::Indirector::Indirection
end
return klass.new
end
+
+ # Use cached termini.
+ def termini
+ attr_cache(:termini) { Hash.new }
+ end
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/request.rb b/lib/puppet/indirector/request.rb
index 98fa38885..49cc01aab 100644
--- a/lib/puppet/indirector/request.rb
+++ b/lib/puppet/indirector/request.rb
@@ -1,10 +1,13 @@
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 :server, :port, :uri, :protocol
+
# Is this an authenticated request?
def authenticated?
# Double negative, so we just get true or false
@@ -28,7 +31,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
@@ -39,4 +50,40 @@ class Puppet::Indirector::Request
def indirection
Puppet::Indirector::Indirection.instance(@indirection_name)
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/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/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
index 5e9422b7e..fe257db2b 100644
--- a/lib/puppet/network/client/master.rb
+++ b/lib/puppet/network/client/master.rb
@@ -89,7 +89,6 @@ class Puppet::Network::Client::Master < Puppet::Network::Client
def clear
@catalog.clear(true) if @catalog
- Puppet::Type.allclear
@catalog = nil
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..f3c3380e1
--- /dev/null
+++ b/lib/puppet/network/format_handler.rb
@@ -0,0 +1,84 @@
+require 'yaml'
+require 'puppet/network'
+require 'puppet/network/format'
+
+module Puppet::Network::FormatHandler
+ @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
+
+ module ClassMethods
+ def format_handler
+ Puppet::Network::FormatHandler
+ end
+
+ def convert_from(format, data)
+ format_handler.format(format).intern(self, data)
+ end
+
+ def convert_from_multiple(format, data)
+ format_handler.format(format).intern_multiple(self, data)
+ end
+
+ def render_multiple(format, instances)
+ format_handler.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.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..160dab1bb 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,12 +498,14 @@ 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
@@ -514,6 +516,7 @@ class Puppet::Network::Handler
: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/resource.rb b/lib/puppet/network/handler/resource.rb
index f2a339751..7ec27b4dc 100755
--- a/lib/puppet/network/handler/resource.rb
+++ b/lib/puppet/network/handler/resource.rb
@@ -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
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..eacf81ec2 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,86 @@ 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 = {}
+
+ host = Puppet::SSL::Host.new
+
+ host.generate unless host.certificate
+
+ 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..78a35cc15 100644
--- a/lib/puppet/network/http_pool.rb
+++ b/lib/puppet/network/http_pool.rb
@@ -1,11 +1,13 @@
-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
+ extend Puppet::Util::Cacher
+
# 2008/03/23
# LAK:WARNING: Enabling this has a high propability of
# causing corrupt files and who knows what else. See #1010.
@@ -15,18 +17,18 @@ module Puppet::Network::HttpPool
HTTP_KEEP_ALIVE
end
- # This handles reading in the key and such-like.
- extend Puppet::SSLCertificates::Support
- @http_cache = {}
+ # Create an ssl host instance for getting certificate
+ # information.
+ def self.ssl_host
+ attr_cache(:ssl_host) { Puppet::SSL::Host.new }
+ end
# 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.invalidate
end
# Make sure we set the driver up when we read the cert in.
@@ -44,17 +46,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
+ return false unless FileTest.exist?(Puppet[:hostcert]) # ssl_host.certificate
- store = OpenSSL::X509::Store.new
- store.add_file Puppet[:localcacert]
- store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
-
- 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 +64,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,8 +98,15 @@ module Puppet::Network::HttpPool
cert_setup(http)
- @http_cache[key] = http if keep_alive?
+ http_cache[key] = http if keep_alive?
return http
end
+
+ private
+
+ def self.http_cache
+ # Default to an empty hash.
+ attr_cache(:http) { Hash.new }
+ end
end
diff --git a/lib/puppet/network/http_server/mongrel.rb b/lib/puppet/network/http_server/mongrel.rb
index 6b2325d29..e9421c781 100644
--- a/lib/puppet/network/http_server/mongrel.rb
+++ b/lib/puppet/network/http_server/mongrel.rb
@@ -64,11 +64,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/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..9650562d4 100644
--- a/lib/puppet/node.rb
+++ b/lib/puppet/node.rb
@@ -79,6 +79,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/catalog.rb b/lib/puppet/node/catalog.rb
index 17927388a..852904967 100644
--- a/lib/puppet/node/catalog.rb
+++ b/lib/puppet/node/catalog.rb
@@ -61,7 +61,7 @@ class Puppet::Node::Catalog < Puppet::PGraph
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)
@@ -315,7 +315,7 @@ 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
@@ -343,7 +343,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]
diff --git a/lib/puppet/resource_reference.rb b/lib/puppet/resource_reference.rb
index 44b518816..12b9f54a9 100644
--- a/lib/puppet/resource_reference.rb
+++ b/lib/puppet/resource_reference.rb
@@ -22,15 +22,8 @@ class Puppet::ResourceReference
# 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
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..a005bfaf0
--- /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.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..6947af11c
--- /dev/null
+++ b/lib/puppet/ssl/certificate_authority.rb
@@ -0,0 +1,285 @@
+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'
+
+ extend Puppet::Util::Cacher
+
+ 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?
+
+ attr_cache(:instance) { new }
+ 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..e345f4bb6
--- /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
+ 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..d3805eb20
--- /dev/null
+++ b/lib/puppet/ssl/host.rb
@@ -0,0 +1,223 @@
+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/constant_inflector'
+
+# 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
+
+ extend Puppet::Util::ConstantInflector
+
+ attr_reader :name
+ attr_accessor :ca
+
+ attr_writer :key, :certificate, :certificate_request
+
+ 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 or CRL, 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
+ end
+ end
+
+ # Specify how we expect to interact with our certificate authority.
+ def self.ca_location=(mode)
+ raise ArgumentError, "CA Mode can only be :local, :remote, or :none" unless [:local, :remote, :none].include?(mode)
+
+ @ca_mode = mode
+
+ case @ca_mode
+ when :local:
+ # Our ca is local, so we use it as the ultimate source of information
+ # And we cache files locally.
+ configure_indirection :ca, :file
+ when :remote:
+ configure_indirection :rest, :file
+ when :none:
+ # We have no CA, so we just look in the local file store.
+ configure_indirection :file
+ end
+ 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
+ @key.save
+ 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)
+ @certificate_request.save
+ return true
+ end
+
+ def certificate
+ return nil unless @certificate ||= Certificate.find(name)
+ @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
+
+ @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 :existing if certificate
+ begin
+ generate
+
+ return :new 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
+ return :new
+ 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..e6dc60681 100644
--- a/lib/puppet/transaction.rb
+++ b/lib/puppet/transaction.rb
@@ -154,7 +154,7 @@ 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
diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb
index 7e8654921..2f4ddf794 100644
--- a/lib/puppet/type.rb
+++ b/lib/puppet/type.rb
@@ -756,6 +756,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
@@ -790,7 +793,6 @@ class Type
obj.remove
end
@parameters.clear
- self.class.delete(self)
@parent = nil
@@ -988,11 +990,13 @@ class Type
# 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
@@ -1024,6 +1028,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(
@@ -1047,6 +1052,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)
@@ -1106,24 +1112,6 @@ class Type
# 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.
@@ -1133,8 +1121,6 @@ class Type
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
@@ -1143,14 +1129,12 @@ class Type
obj.implicit = true
end
- # Store the object by title
- self[obj.title] = obj
-
return obj
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)
@@ -1171,6 +1155,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
@@ -1179,6 +1164,7 @@ 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
@@ -1238,10 +1224,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]
@@ -1251,12 +1233,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
@@ -1551,6 +1528,7 @@ class Type
# to an object...
tname, name = value
reference = Puppet::ResourceReference.new(tname, name)
+ reference.catalog = resource.catalog
# Either of the two retrieval attempts could have returned
# nil.
@@ -1964,7 +1942,10 @@ 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.
@@ -1982,15 +1963,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
@@ -2066,9 +2047,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
@@ -2324,14 +2310,14 @@ class Type
end
# If the name and title differ, set up an alias
- if self.name != self.title
- if obj = self.class[self.name]
+ if self.name != self.title and self.catalog
+ if obj = catalog.resource(self.class.name, 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)
+ catalog.alias(self, self.name)
end
end
diff --git a/lib/puppet/type/component.rb b/lib/puppet/type/component.rb
index 356205089..1d1bc9ee8 100644
--- a/lib/puppet/type/component.rb
+++ b/lib/puppet/type/component.rb
@@ -88,8 +88,8 @@ Puppet::Type.newtype(:component) do
@reference = Puppet::ResourceReference.new(:component, @title)
- unless self.class[@reference.to_s]
- self.class.alias(@reference.to_s, self)
+ if catalog and ! catalog.resource[@reference.to_s]
+ catalog.alias(self, @reference.to_s)
end
end
diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb
index 371571ff3..35eccef83 100644
--- a/lib/puppet/type/file.rb
+++ b/lib/puppet/type/file.rb
@@ -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
@@ -278,11 +275,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)
@@ -304,30 +296,23 @@ module Puppet
# 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 +322,23 @@ module Puppet
# Create any children via recursion or whatever.
def eval_generate
- recurse()
+ return nil unless self.recurse?
+
+ raise(Puppet::DevError, "Cannot generate resources for recursion without a catalog") unless catalog
+
+ 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
end
# Deal with backups.
@@ -432,8 +433,7 @@ 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
@@ -464,194 +464,21 @@ 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
+ def newchild(path)
+ full_path = File.join(self[:path], path)
- # 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)
+ # the right-side hash wins in the merge.
+ options = to_hash.merge(:path => full_path, :implicit => true).reject { |param, value| value.nil? }
+ [:parent, :recurse, :target].each do |param|
+ options.delete(param) if options.include?(param)
end
- args[:path] = path
-
- 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
- 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.create(options)
end
# Files handle paths specially, because they just lengthen their
@@ -681,70 +508,121 @@ 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.
+ def make_children(metadata)
+ metadata.collect { |meta| newchild(meta.relative_path) }
+ end
+
+ # 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
+
+ # Recurse against our remote file.
+ def recurse_remote(children)
+ sourceselect = self[:sourceselect]
- # 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
+ total = self[:source].collect do |source|
+ next unless result = perform_recursion(source)
+ 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 == "."
+ property(: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].property(: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.
@@ -805,109 +683,22 @@ module Puppet
# 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 the file doesn't exist but we have a source, then call
+ # retrieve on the source property so it will set the 'should'
+ # values all around.
if @parameters.include?(:source)
- propertyvalues[:source] = @parameters[:source].retrieve
+ @parameters[:source].copy_source_values
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
- end
-
- unless success
- raise Puppet::Error, "None of the provided sources exist"
- end
-
- return [result, sourced]
+ currentpropvalues()
end
# Set the checksum, from another property. There are multiple
@@ -968,75 +759,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)
diff --git a/lib/puppet/type/file/source.rb b/lib/puppet/type/file/source.rb
index 2514d3d1e..e43706051 100755
--- a/lib/puppet/type/file/source.rb
+++ b/lib/puppet/type/file/source.rb
@@ -1,3 +1,7 @@
+
+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
@@ -62,8 +66,14 @@ module Puppet
uncheckable
validate do |source|
- unless @resource.uri2obj(source)
- raise Puppet::Error, "Invalid source %s" % 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
@@ -77,72 +87,67 @@ module Puppet
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 defined?(@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.
+ def content
+ 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 defined?(@content) and @content
+ unless tmp = Puppet::FileServing::Content.find(@metadata.source)
+ fail "Could not find any content at %s" % @metadata.source
+ end
+ @content = tmp.content
end
+ @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
+ 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)
+ @resource[:ensure] = metadata.ftype
+
+ if metadata.ftype == "link"
+ @resource[:target] = metadata.destination
end
-
- return args
end
-
- # Use the info we get from describe() to check if we're in sync.
+
+ # Remove any temporary attributes we manage.
+ def flush
+ @metadata = nil
+ @content = nil
+ end
+
+ # Use the remote metadata to see if we're in sync.
+ # LAK:NOTE This method should still get refactored.
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"
+ unless @metadata.ftype == "file"
return true
end
@@ -153,15 +158,13 @@ module Puppet
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]))
+ result = (!parentchecksum.nil? and (parentchecksum == @metadata.checksum))
# 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 ! result and Puppet[:show_diff] and File.exists?(@resource[:path])
+ string_file_diff(@resource[:path], content)
end
return result
end
@@ -171,57 +174,39 @@ module Puppet
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
- break
+ # 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.
+ attr_writer :metadata
+ def metadata
+ unless defined?(@metadata) and @metadata
+ return @metadata = nil unless should
+ should.each do |source|
+ begin
+ if data = Puppet::FileServing::Metadata.find(source)
+ @metadata = data
+ @metadata.source = source
+ break
+ end
+ rescue => detail
+ fail detail, "Could not retrieve file metadata for %s: %s" % [source, detail]
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
+ end
+ fail "Could not retrieve information from source(s) %s" % @should.join(", ") unless @metadata
end
+ return @metadata
+ 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]
+ # Just call out to our copy method. Hopefully we'll refactor 'source' to
+ # be a parameter soon, in which case 'retrieve' is unnecessary.
+ def retrieve
+ copy_source_values
end
+ # Return the whole array, rather than the first item.
def should
@should
end
@@ -238,11 +223,9 @@ module Puppet
end
def sync
- contents = @stats[:_remote_content] || get_remote_content()
-
- exists = File.exists?(@resource[:path])
+ exists = FileTest.exist?(@resource[:path])
- @resource.write(contents, :source, @stats[:checksum])
+ @resource.write(content, :source, @metadata.checksum)
if exists
return :file_changed
@@ -250,27 +233,5 @@ module Puppet
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
- end
end
end
diff --git a/lib/puppet/type/filebucket.rb b/lib/puppet/type/filebucket.rb
index b268610e9..872bded4e 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
+ self.create(:name => "puppet", :path => Puppet[:clientbucketdir])
end
def self.instances
diff --git a/lib/puppet/type/schedule.rb b/lib/puppet/type/schedule.rb
index b8479e01d..ecbf7d49a 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,8 +316,6 @@ module Puppet
end
def self.mkdefaultschedules
- return [] if self["puppet"]
-
result = []
Puppet.debug "Creating default schedules"
result << self.create(
@@ -326,12 +326,10 @@ module Puppet
# 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
+ result << self.create(
+ :name => value.to_s,
+ :period => value
+ )
}
result
diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb
index 29f4880c1..e0a11f00b 100755
--- a/lib/puppet/type/user.rb
+++ b/lib/puppet/type/user.rb
@@ -233,19 +233,7 @@ module Puppet
group
end
}
- groups.each { |group|
- case group
- when Integer:
- if obj = Puppet.type(:group).find { |gobj|
- gobj.should(:gid) == group
- }
- autos << obj
-
- end
- else
- autos << group
- end
- }
+ autos = groups.reject { |g| g.is_a?(Integer) }
end
if obj = @parameters[:groups] and groups = obj.should
diff --git a/lib/puppet/type/yumrepo.rb b/lib/puppet/type/yumrepo.rb
index 15e031945..1e917793b 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)
@@ -180,11 +181,11 @@ module Puppet
inifile.store
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/util/cacher.rb b/lib/puppet/util/cacher.rb
new file mode 100644
index 000000000..d0debfdf7
--- /dev/null
+++ b/lib/puppet/util/cacher.rb
@@ -0,0 +1,93 @@
+module Puppet::Util::Cacher
+ # Cause all cached values to be considered invalid.
+ def self.invalidate
+ @timestamp = Time.now
+ end
+
+ # Is the provided timestamp later than or equal to our global timestamp?
+ # If it is, then the associated value is valid, otherwise it should be flushed.
+ def self.valid?(timestamp)
+ unless defined?(@timestamp) and @timestamp
+ @timestamp = Time.now
+ return true
+ end
+ return timestamp >= @timestamp
+ end
+
+ # 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)
+ other.extend(InstanceMethods)
+ 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)
+ define_method(name) do
+ attr_cache(name, &block)
+ end
+ end
+ end
+
+ # Methods that get added to instances.
+ module InstanceMethods
+ private
+
+ # Use/define a cached value. We just use the Cache class to do all
+ # of the thinking. Note that we're using a single Cache instance
+ # for all of this instance's cached values.
+ def attr_cache(name, &block)
+ unless defined?(@cacher_caches) and @cacher_caches
+ @cacher_caches = Cache.new
+ end
+
+ @cacher_caches.value(name, &block)
+ end
+ end
+
+ # An internal class that does all of our comparisons and calculations.
+ # This both caches a given value, and determines whether a given cache is
+ # still valid.
+ class Cache
+ attr_accessor :caches, :timestamp
+
+ def initialize
+ @caches = {}
+ end
+
+ # Return a value; use the cached version if the associated timestamp is recent enough,
+ # else calculate and store a new a value using the provided block.
+ def value(name)
+ raise ArgumentError, "You must provide a block when using the cache" unless block_given?
+
+ # If we have no timestamp (because this is the first run) or our
+ # timestamp is out of date, clear the cache and get a new value.
+ # Note that if we clear the cache here, we potentially clear other
+ # cached values, too (if this instance is caching more than one value).
+ if timestamp.nil? or ! Puppet::Util::Cacher.valid?(timestamp)
+ caches.clear
+ self.timestamp = Time.now
+ end
+
+ # Generate a new value if we don't have one. Use 'include?' here
+ # rather than testing for truth, so we can cache false values.
+ unless caches.include?(name)
+ caches[name] = yield
+ end
+
+ # Finally, return our cached value.
+ caches[name]
+ end
+ end
+end
diff --git a/lib/puppet/util/filetype.rb b/lib/puppet/util/filetype.rb
index 7e799b613..b10b50fb7 100755
--- a/lib/puppet/util/filetype.rb
+++ b/lib/puppet/util/filetype.rb
@@ -79,8 +79,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)
@@ -109,6 +111,9 @@ class Puppet::Util::FileType
# Overwrite the file.
def write(text)
backup()
+
+ raise("Cannot create file %s in absent directory" % @path) unless FileTest.exist?(File.dirname(@path))
+
require "tempfile"
tf = Tempfile.new("puppet")
tf.print text; tf.flush
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/settings.rb b/lib/puppet/util/settings.rb
index 1e49a3ada..d0c16ec92 100644
--- a/lib/puppet/util/settings.rb
+++ b/lib/puppet/util/settings.rb
@@ -39,6 +39,8 @@ class Puppet::Util::Settings
end
@values[:memory][param] = value
@cache.clear
+
+ clearused
end
return value
@@ -65,7 +67,6 @@ class Puppet::Util::Settings
config = trans.to_catalog
config.store_state = false
config.apply
- config.clear
rescue => detail
if Puppet[:trace]
puts detail.backtrace
@@ -372,94 +373,6 @@ class Puppet::Util::Settings
end
end
- # 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
-
- @values = Hash.new { |names, name|
- names[name] = {}
- }
-
- # 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] = {} }
- 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
-
# 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.
@@ -719,7 +632,7 @@ Generated on #{Time.now}.
catalog = bucket.to_catalog
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.
@@ -735,8 +648,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 }
@@ -834,20 +745,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
@@ -1221,7 +1138,6 @@ Generated on #{Time.now}.
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 = []