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/configuration.rb13
-rw-r--r--lib/puppet/file_serving/mount.rb25
-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/file.rb1
-rw-r--r--lib/puppet/indirector/indirection.rb22
-rw-r--r--lib/puppet/indirector/key/ca.rb20
-rw-r--r--lib/puppet/indirector/key/file.rb42
-rw-r--r--lib/puppet/indirector/rest.rb74
-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/metatype/container.rb1
-rw-r--r--lib/puppet/metatype/instances.rb41
-rw-r--r--lib/puppet/metatype/metaparams.rb4
-rw-r--r--lib/puppet/metatype/relationships.rb7
-rw-r--r--lib/puppet/metatype/schedules.rb6
-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.rb67
-rw-r--r--lib/puppet/network/format_handler.rb81
-rw-r--r--lib/puppet/network/formats.rb72
-rwxr-xr-xlib/puppet/network/handler/fileserver.rb7
-rwxr-xr-xlib/puppet/network/handler/resource.rb14
-rw-r--r--lib/puppet/network/http.rb2
-rw-r--r--lib/puppet/network/http/handler.rb113
-rw-r--r--lib/puppet/network/http/mongrel.rb11
-rw-r--r--lib/puppet/network/http/mongrel/rest.rb32
-rw-r--r--lib/puppet/network/http/webrick.rb84
-rw-r--r--lib/puppet/network/http/webrick/rest.rb19
-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/network/xmlrpc/client.rb3
-rw-r--r--lib/puppet/node.rb2
-rw-r--r--lib/puppet/node/catalog.rb6
-rwxr-xr-xlib/puppet/provider/package/yum.rb41
-rw-r--r--lib/puppet/resource_reference.rb11
-rw-r--r--lib/puppet/ssl.rb7
-rw-r--r--lib/puppet/ssl/base.rb51
-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.rb6
-rw-r--r--lib/puppet/type/component.rb4
-rw-r--r--lib/puppet/type/file.rb28
-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.rb69
-rwxr-xr-xlib/puppet/util/filetype.rb9
-rwxr-xr-xlib/puppet/util/posix.rb39
-rw-r--r--lib/puppet/util/settings.rb110
71 files changed, 2270 insertions, 587 deletions
diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb
index 87ccd62f6..7c57dcd6d 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,
@@ -146,7 +140,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
@@ -157,7 +164,7 @@ module Puppet
fqdn = hostname
end
- Puppet.setdefaults(:ssl,
+ Puppet.setdefaults(:main,
# We have to downcase the fqdn, because the current ssl stuff (as oppsed to in master) doesn't have good facilities for
# manipulating naming.
:certname => {:default => fqdn.downcase, :desc => "The name to use when handling certificates. Defaults
@@ -168,7 +175,14 @@ module Puppet
If it's anything other than an empty string, it will be used as an alias in the created
certificate. By default, only the server gets an alias set up, and only for 'puppet'."],
:certdir => ["$ssldir/certs", "The certificate directory."],
+ :ssldir => {
+ :default => "$confdir/ssl",
+ :mode => 0771,
+ :owner => "root",
+ :desc => "Where SSL certificates are kept."
+ },
:publickeydir => ["$ssldir/public_keys", "The public key directory."],
+ :requestdir => ["$ssldir/certificate_requests", "Where host certificate requests are stored."],
:privatekeydir => { :default => "$ssldir/private_keys",
:mode => 0750,
:desc => "The private key directory."
@@ -184,7 +198,7 @@ module Puppet
},
:hostcsr => { :default => "$ssldir/csr_$certname.pem",
:mode => 0644,
- :desc => "Where individual hosts store and look for their certificates."
+ :desc => "Where individual hosts store and look for their certificate requests."
},
:hostcert => { :default => "$certdir/$certname.pem",
:mode => 0644,
@@ -201,6 +215,11 @@ module Puppet
:localcacert => { :default => "$certdir/ca.pem",
:mode => 0644,
:desc => "Where each client stores the CA certificate."
+ },
+ :hostcrl => { :default => "$ssldir/crl.pem",
+ :mode => 0644,
+ :desc => "Where the host's certificate revocation list can be found.
+ This is distinct from the certificate authority's CRL."
}
)
@@ -232,7 +251,12 @@ module Puppet
:owner => "$user",
:group => "$group",
:mode => 0664,
- :desc => "The certificate revocation list (CRL) for the CA. Set this to 'false' if you do not want to use a CRL."
+ :desc => "The certificate revocation list (CRL) for the CA. Will be used if present but otherwise ignored.",
+ :hook => proc do |value|
+ if value == 'false'
+ Puppet.warning "Setting the :cacrl to 'false' is deprecated; Puppet will just ignore the crl if yours is missing"
+ end
+ end
},
:caprivatedir => { :default => "$cadir/private",
:owner => "$user",
@@ -260,7 +284,7 @@ module Puppet
:serial => { :default => "$cadir/serial",
:owner => "$user",
:group => "$group",
- :mode => 0600,
+ :mode => 0644,
:desc => "Where the serial number for certificates is stored."
},
:autosign => { :default => "$confdir/autosign.conf",
@@ -293,7 +317,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
@@ -386,19 +410,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/configuration.rb b/lib/puppet/file_serving/configuration.rb
index ccf0957d1..9c38aaa19 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
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/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/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/indirection.rb b/lib/puppet/indirector/indirection.rb
index 4841ec532..a9fff75b8 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.
@@ -299,4 +288,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/rest.rb b/lib/puppet/indirector/rest.rb
index d33150fc2..a2767d05b 100644
--- a/lib/puppet/indirector/rest.rb
+++ b/lib/puppet/indirector/rest.rb
@@ -3,54 +3,60 @@ require 'uri'
# Access objects via REST
class Puppet::Indirector::REST < Puppet::Indirector::Terminus
+ # 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
- def rest_connection_details
- { :host => Puppet[:server], :port => Puppet[:masterport].to_i }
+ # 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
- def network_fetch(path)
- network {|conn| conn.get("/#{path}").body }
+ # Provide appropriate headers.
+ def headers
+ {"Accept" => model.supported_formats.join(", ")}
end
-
- def network_delete(path)
- network {|conn| conn.delete("/#{path}").body }
- end
-
- def network_put(path, data)
- network {|conn| conn.put("/#{path}", data).body }
+
+ def network
+ Puppet::Network::HttpPool.http_instance(Puppet[:server], Puppet[:masterport].to_i)
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.get("/#{indirection.name}/#{request.key}", 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}"
+ else
+ path = "/#{indirection.name}s"
+ end
+ unless result = deserialize(network.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)
+ deserialize network.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)
- 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}
+ deserialize network.put("/#{indirection.name}/", request.instance.render, headers)
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/metatype/container.rb b/lib/puppet/metatype/container.rb
index 2bbe3f546..dbc8a3dee 100644
--- a/lib/puppet/metatype/container.rb
+++ b/lib/puppet/metatype/container.rb
@@ -36,7 +36,6 @@ class Puppet::Type
obj.remove
end
@parameters.clear
- self.class.delete(self)
@parent = nil
diff --git a/lib/puppet/metatype/instances.rb b/lib/puppet/metatype/instances.rb
index 3f44413f8..34d542c5d 100644
--- a/lib/puppet/metatype/instances.rb
+++ b/lib/puppet/metatype/instances.rb
@@ -8,11 +8,13 @@ class Puppet::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
@@ -44,6 +46,7 @@ class Puppet::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(
@@ -67,6 +70,7 @@ class Puppet::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)
@@ -126,24 +130,6 @@ class Puppet::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.
@@ -153,8 +139,6 @@ class Puppet::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
@@ -163,14 +147,12 @@ class Puppet::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)
@@ -191,6 +173,7 @@ class Puppet::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
@@ -199,6 +182,7 @@ class Puppet::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
@@ -258,10 +242,6 @@ class Puppet::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]
@@ -271,12 +251,7 @@ class Puppet::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
diff --git a/lib/puppet/metatype/metaparams.rb b/lib/puppet/metatype/metaparams.rb
index df96733c2..9ba786ee2 100644
--- a/lib/puppet/metatype/metaparams.rb
+++ b/lib/puppet/metatype/metaparams.rb
@@ -207,9 +207,6 @@ class Puppet::Type
next
end
- # LAK:FIXME Old-school, add the alias to the class.
- @resource.class.alias(other, @resource)
-
# Newschool, add it to the catalog.
@resource.catalog.alias(@resource, other)
end
@@ -278,6 +275,7 @@ class Puppet::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.
diff --git a/lib/puppet/metatype/relationships.rb b/lib/puppet/metatype/relationships.rb
index 4fb78ae56..0070c6efb 100644
--- a/lib/puppet/metatype/relationships.rb
+++ b/lib/puppet/metatype/relationships.rb
@@ -17,7 +17,10 @@ class Puppet::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.
@@ -35,7 +38,7 @@ class Puppet::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
diff --git a/lib/puppet/metatype/schedules.rb b/lib/puppet/metatype/schedules.rb
index 96ebce0ab..b4782f852 100644
--- a/lib/puppet/metatype/schedules.rb
+++ b/lib/puppet/metatype/schedules.rb
@@ -3,9 +3,13 @@ class Puppet::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
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 6f8e2770f..5ca898dab 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..a4515e39f
--- /dev/null
+++ b/lib/puppet/network/format.rb
@@ -0,0 +1,67 @@
+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
+
+ 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
+
+ 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)
+ 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
+
+ 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..4c9f4e59e
--- /dev/null
+++ b/lib/puppet/network/format_handler.rb
@@ -0,0 +1,81 @@
+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 }
+ 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..e11748ce6
--- /dev/null
+++ b/lib/puppet/network/formats.rb
@@ -0,0 +1,72 @@
+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
+
+ # Yaml 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") do
+ # For now, use the YAML separator.
+ SEPARATOR = "\n---\n"
+
+ def intern_multiple(klass, text)
+ text.split(SEPARATOR).collect { |inst| intern(klass, inst) }
+ end
+
+ def render_multiple(instances)
+ instances.collect { |inst| render(inst) }.join(SEPARATOR)
+ end
+
+ # Everything's supported
+ def supported?(klass)
+ true
+ end
+end
diff --git a/lib/puppet/network/handler/fileserver.rb b/lib/puppet/network/handler/fileserver.rb
index 3e62cdbd9..183979429 100755
--- a/lib/puppet/network/handler/fileserver.rb
+++ b/lib/puppet/network/handler/fileserver.rb
@@ -496,12 +496,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
@@ -512,6 +514,7 @@ class Puppet::Network::Handler
:name => file_path(path, client),
:check => CHECKPARAMS
)
+ @files[file_path(path, client)] = obj
end
if links == :manage
@@ -528,7 +531,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..291481acd 100644
--- a/lib/puppet/network/http/handler.rb
+++ b/lib/puppet/network/http/handler.rb
@@ -1,4 +1,19 @@
+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)
+ accept_header(request).split(/,\s*/)[0]
+ end
def initialize_for_puppet(args = {})
raise ArgumentError unless @server = args[:server]
@@ -14,54 +29,104 @@ 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)
+ result = model.search(args)
+ 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 +144,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 +165,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..d265dde86 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]
@@ -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..13f795fb2 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
@@ -41,9 +43,18 @@ class Puppet::Network::HTTP::WEBrickREST < WEBrick::HTTPServlet::AbstractServlet
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/network/xmlrpc/client.rb b/lib/puppet/network/xmlrpc/client.rb
index e0fb5a0ab..dfd4a95a7 100644
--- a/lib/puppet/network/xmlrpc/client.rb
+++ b/lib/puppet/network/xmlrpc/client.rb
@@ -51,7 +51,8 @@ module Puppet::Network
end
["certificate verify failed", "hostname was not match", "hostname not match"].each do |str|
if detail.message.include?(str)
- Puppet.warning "Certificate validation failed; considering using the certname configuration option"
+ Puppet.warning "Certificate validation failed; consider using the certname configuration option"
+ break
end
end
raise XMLRPCClientError,
diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb
index afa18f565..3322a9ee7 100644
--- a/lib/puppet/node.rb
+++ b/lib/puppet/node.rb
@@ -85,6 +85,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/provider/package/yum.rb b/lib/puppet/provider/package/yum.rb
index 7998b92c0..56fad1af9 100755
--- a/lib/puppet/provider/package/yum.rb
+++ b/lib/puppet/provider/package/yum.rb
@@ -7,9 +7,7 @@ Puppet::Type.type(:package).provide :yum, :parent => :rpm, :source => :rpm do
YUMHELPER = File::join(File::dirname(__FILE__), "yumhelper.py")
- class << self
- attr_reader :updates
- end
+ attr_accessor :latest_info
if command('rpm')
confine :true => begin
@@ -24,22 +22,32 @@ Puppet::Type.type(:package).provide :yum, :parent => :rpm, :source => :rpm do
defaultfor :operatingsystem => [:fedora, :centos, :redhat]
def self.prefetch(packages)
- @updates = {}
if Process.euid != 0
raise Puppet::Error, "The yum provider can only be used as root"
end
super
- python(YUMHELPER).each_line do |l|
- l.chomp!
- next if l.empty?
- if l[0,4] == "_pkg"
- hash = nevra_to_hash(l[5..-1])
- [hash[:name], "#{hash[:name]}.#{hash[:arch]}"].each do |n|
- @updates[n] ||= []
- @updates[n] << hash
- end
- end
- end
+ return unless packages.detect { |name, package| package.should(:ensure) == :latest }
+
+ # collect our 'latest' info
+ updates = {}
+ python(YUMHELPER).each_line do |l|
+ l.chomp!
+ next if l.empty?
+ if l[0,4] == "_pkg"
+ hash = nevra_to_hash(l[5..-1])
+ [hash[:name], "#{hash[:name]}.#{hash[:arch]}"].each do |n|
+ updates[n] ||= []
+ updates[n] << hash
+ end
+ end
+ end
+
+ # Add our 'latest' info to the providers.
+ packages.each do |name, package|
+ if info = updates[package[:name]]
+ package.provider.latest_info = info[0]
+ end
+ end
end
def install
@@ -73,11 +81,10 @@ Puppet::Type.type(:package).provide :yum, :parent => :rpm, :source => :rpm do
# What's the latest package version available?
def latest
- upd = self.class.updates[@resource[:name]]
+ upd = latest_info
unless upd.nil?
# FIXME: there could be more than one update for a package
# because of multiarch
- upd = upd[0]
return "#{upd[:version]}-#{upd[:release]}"
else
# Yum didn't find updates, pretend the current
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..08efb314b
--- /dev/null
+++ b/lib/puppet/ssl/base.rb
@@ -0,0 +1,51 @@
+require 'puppet/ssl'
+
+# The base class for wrapping SSL instances.
+class Puppet::SSL::Base
+ 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 45dd7f5b5..a6aff152c 100644
--- a/lib/puppet/type.rb
+++ b/lib/puppet/type.rb
@@ -228,14 +228,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 3518e8bb3..ef010efda 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
@@ -304,30 +303,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
@@ -432,8 +424,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
@@ -805,7 +796,6 @@ 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
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 71507d172..0b668395d 100755
--- a/lib/puppet/type/user.rb
+++ b/lib/puppet/type/user.rb
@@ -302,19 +302,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..7b352c72a
--- /dev/null
+++ b/lib/puppet/util/cacher.rb
@@ -0,0 +1,69 @@
+module Puppet::Util::Cacher
+ # Cause all cached values to be considered invalid.
+ def self.invalidate
+ @timestamp = Time.now
+ end
+
+ def self.valid?(timestamp)
+ unless defined?(@timestamp) and @timestamp
+ @timestamp = Time.now
+ return true
+ end
+ return timestamp >= @timestamp
+ end
+
+ def self.extended(other)
+ other.extend(InstanceMethods)
+ end
+
+ def self.included(other)
+ other.extend(ClassMethods)
+ other.send(:include, InstanceMethods)
+ end
+
+ module ClassMethods
+ private
+
+ def cached_attr(name, &block)
+ define_method(name) do
+ attr_cache(name, &block)
+ end
+ end
+ end
+
+ module InstanceMethods
+ private
+
+ def attr_cache(name, &block)
+ unless defined?(@cacher_caches) and @cacher_caches
+ @cacher_caches = Cache.new
+ end
+
+ @cacher_caches.value(name, &block)
+ end
+ end
+
+ class Cache
+ attr_accessor :caches, :timestamp
+
+ def initialize
+ @caches = {}
+ end
+
+ def value(name)
+ raise ArgumentError, "You must provide a block when using the cache" unless block_given?
+
+ if timestamp.nil? or ! Puppet::Util::Cacher.valid?(timestamp)
+ caches.clear
+ self.timestamp = Time.now
+ end
+
+ # Use 'include?' here rather than testing for truth, so we
+ # can cache false values.
+ unless caches.include?(name)
+ caches[name] = yield
+ end
+ caches[name]
+ end
+ end
+end
diff --git a/lib/puppet/util/filetype.rb b/lib/puppet/util/filetype.rb
index 8dcb67286..ae078fff4 100755
--- a/lib/puppet/util/filetype.rb
+++ b/lib/puppet/util/filetype.rb
@@ -74,8 +74,10 @@ class Puppet::Util::FileType
# Pick or create a filebucket to use.
def bucket
- filebucket = Puppet::Type.type(:filebucket)
- (filebucket["puppet"] || filebucket.mkdefaultbucket).bucket
+ unless defined?(@bucket)
+ @bucket = Puppet::Type.type(:filebucket).mkdefaultbucket.bucket
+ end
+ @bucket
end
def initialize(path)
@@ -104,6 +106,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 9281169ea..aff797485 100755
--- a/lib/puppet/util/posix.rb
+++ b/lib/puppet/util/posix.rb
@@ -60,45 +60,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 f20dee401..b6855dfa5 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
@@ -370,94 +371,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.
@@ -721,7 +634,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.
@@ -737,8 +650,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 }
@@ -836,20 +747,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
@@ -1223,7 +1140,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 = []