summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorDominic Maraglia <dominic@puppetlabs.com>2011-08-03 15:43:20 -0700
committerDominic Maraglia <dominic@puppetlabs.com>2011-08-03 15:43:20 -0700
commite45f08bf1fef8842554ca0d8cb6fb13711e888e7 (patch)
tree9bb476a0fe381d53060abc1f1c240cf1291b2ee4 /lib
parenta97c86e7d01384aa06f5d4d69da427fc355aebe7 (diff)
parentc833fde370d43023f52c8f2e11fd77e720d0f577 (diff)
downloadpuppet-e45f08bf1fef8842554ca0d8cb6fb13711e888e7.tar.gz
puppet-e45f08bf1fef8842554ca0d8cb6fb13711e888e7.tar.xz
puppet-e45f08bf1fef8842554ca0d8cb6fb13711e888e7.zip
Merge branch 'master' of github.com:puppetlabs/puppet
Diffstat (limited to 'lib')
-rw-r--r--lib/puppet.rb2
-rw-r--r--lib/puppet/application/agent.rb8
-rw-r--r--lib/puppet/application/apply.rb15
-rw-r--r--lib/puppet/application/ca.rb5
-rw-r--r--lib/puppet/application/cert.rb3
-rw-r--r--lib/puppet/application/certificate.rb5
-rw-r--r--lib/puppet/application/device.rb8
-rw-r--r--lib/puppet/application/doc.rb35
-rw-r--r--lib/puppet/application/face_base.rb3
-rw-r--r--lib/puppet/application/inspect.rb7
-rw-r--r--lib/puppet/application/master.rb2
-rw-r--r--lib/puppet/configurer.rb103
-rw-r--r--lib/puppet/defaults.rb18
-rw-r--r--lib/puppet/face/ca.rb233
-rw-r--r--lib/puppet/face/certificate.rb26
-rw-r--r--lib/puppet/face/certificate_request.rb20
-rw-r--r--lib/puppet/face/certificate_revocation_list.rb12
-rw-r--r--lib/puppet/face/status.rb1
-rw-r--r--lib/puppet/feature/base.rb11
-rw-r--r--lib/puppet/file_bucket/dipper.rb5
-rw-r--r--lib/puppet/file_bucket/file.rb6
-rw-r--r--lib/puppet/file_serving/base.rb5
-rw-r--r--lib/puppet/file_serving/configuration.rb16
-rw-r--r--lib/puppet/file_serving/configuration/parser.rb19
-rw-r--r--lib/puppet/file_serving/fileset.rb9
-rw-r--r--lib/puppet/file_serving/indirection_hooks.rb1
-rw-r--r--lib/puppet/file_serving/mount.rb1
-rw-r--r--lib/puppet/file_serving/mount/file.rb21
-rw-r--r--lib/puppet/indirector/face.rb24
-rw-r--r--lib/puppet/indirector/facts/facter.rb4
-rw-r--r--lib/puppet/indirector/file_server.rb2
-rw-r--r--lib/puppet/indirector/indirection.rb8
-rw-r--r--lib/puppet/indirector/report/processor.rb2
-rw-r--r--lib/puppet/indirector/request.rb59
-rw-r--r--lib/puppet/indirector/rest.rb49
-rw-r--r--lib/puppet/interface.rb9
-rw-r--r--lib/puppet/interface/action.rb78
-rw-r--r--lib/puppet/interface/action_manager.rb15
-rw-r--r--lib/puppet/interface/face_collection.rb95
-rw-r--r--lib/puppet/interface/option.rb21
-rw-r--r--lib/puppet/interface/option_builder.rb13
-rw-r--r--lib/puppet/interface/option_manager.rb3
-rw-r--r--lib/puppet/module.rb5
-rw-r--r--lib/puppet/network/authconfig.rb4
-rw-r--r--lib/puppet/network/client.rb5
-rw-r--r--lib/puppet/network/http_pool.rb56
-rw-r--r--lib/puppet/network/rest_authconfig.rb7
-rw-r--r--lib/puppet/network/rest_authorization.rb2
-rw-r--r--lib/puppet/node.rb26
-rw-r--r--lib/puppet/node/environment.rb20
-rwxr-xr-xlib/puppet/node/facts.rb15
-rw-r--r--lib/puppet/parameter.rb6
-rw-r--r--lib/puppet/parser/ast/leaf.rb9
-rw-r--r--lib/puppet/parser/compiler.rb14
-rw-r--r--lib/puppet/parser/functions.rb12
-rw-r--r--lib/puppet/parser/functions/create_resources.rb11
-rw-r--r--lib/puppet/parser/functions/extlookup.rb12
-rw-r--r--lib/puppet/parser/functions/fqdn_rand.rb2
-rw-r--r--lib/puppet/parser/functions/versioncmp.rb6
-rw-r--r--lib/puppet/parser/resource.rb5
-rw-r--r--lib/puppet/parser/scope.rb75
-rw-r--r--lib/puppet/parser/templatewrapper.rb7
-rw-r--r--lib/puppet/parser/type_loader.rb3
-rw-r--r--lib/puppet/provider/host/parsed.rb3
-rw-r--r--lib/puppet/provider/service/windows.rb110
-rw-r--r--lib/puppet/resource.rb33
-rw-r--r--lib/puppet/resource/catalog.rb41
-rw-r--r--lib/puppet/resource/type.rb52
-rw-r--r--lib/puppet/ssl/certificate.rb6
-rw-r--r--lib/puppet/ssl/certificate_authority.rb12
-rw-r--r--lib/puppet/ssl/host.rb16
-rw-r--r--lib/puppet/ssl/inventory.rb2
-rw-r--r--lib/puppet/transaction.rb26
-rw-r--r--lib/puppet/transaction/report.rb4
-rw-r--r--lib/puppet/type.rb8
-rw-r--r--lib/puppet/type/file.rb43
-rwxr-xr-xlib/puppet/type/file/source.rb25
-rw-r--r--lib/puppet/type/service.rb54
-rwxr-xr-xlib/puppet/type/user.rb8
-rw-r--r--lib/puppet/util.rb1
-rw-r--r--lib/puppet/util/autoload.rb2
-rw-r--r--lib/puppet/util/cacher.rb82
-rw-r--r--lib/puppet/util/rdoc/parser.rb4
-rw-r--r--lib/puppet/util/run_mode.rb4
-rw-r--r--lib/puppet/util/settings.rb11
-rw-r--r--lib/semver.rb65
86 files changed, 1246 insertions, 650 deletions
diff --git a/lib/puppet.rb b/lib/puppet.rb
index bcac94d45..765c95cbf 100644
--- a/lib/puppet.rb
+++ b/lib/puppet.rb
@@ -24,7 +24,7 @@ require 'puppet/util/run_mode'
# it's also a place to find top-level commands like 'debug'
module Puppet
- PUPPETVERSION = '2.7.0'
+ PUPPETVERSION = '2.7.2'
def Puppet.version
PUPPETVERSION
diff --git a/lib/puppet/application/agent.rb b/lib/puppet/application/agent.rb
index f0442648b..ea7cbdfb5 100644
--- a/lib/puppet/application/agent.rb
+++ b/lib/puppet/application/agent.rb
@@ -187,10 +187,10 @@ configuration options can also be generated by running puppet agent with
should always at least contain MD5, MD2, SHA1 and SHA256.
* --detailed-exitcodes:
- Provide transaction information via exit codes. If this is enabled, an
- exit code of '2' means there were changes, and an exit code of '4'
- means that there were failures during the transaction. This option
- only makes sense in conjunction with --onetime.
+ Provide transaction information via exit codes. If this is enabled, an exit
+ code of '2' means there were changes, an exit code of '4' means there were
+ failures during the transaction, and an exit code of '6' means there were both
+ changes and failures.
* --disable:
Disable working on the local system. This puts a lock file in place,
diff --git a/lib/puppet/application/apply.rb b/lib/puppet/application/apply.rb
index 3ba06d34a..200309b7d 100644
--- a/lib/puppet/application/apply.rb
+++ b/lib/puppet/application/apply.rb
@@ -82,9 +82,10 @@ configuration options can also be generated by running puppet with
Enable full debugging.
* --detailed-exitcodes:
- Provide transaction information via exit codes. If this is enabled, an
- exit code of '2' means there were changes, and an exit code of '4'
- means that there were failures during the transaction.
+ Provide transaction information via exit codes. If this is enabled, an exit
+ code of '2' means there were changes, an exit code of '4' means there were
+ failures during the transaction, and an exit code of '6' means there were both
+ changes and failures.
* --help:
Print this help message
@@ -213,7 +214,13 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License
configurer = Puppet::Configurer.new
report = configurer.run(:skip_plugin_download => true, :catalog => catalog)
- exit( options[:detailed_exitcodes] ? report.exit_status : 0 )
+ if not report
+ exit(1)
+ elsif options[:detailed_exitcodes] then
+ exit(report.exit_status)
+ else
+ exit(0)
+ end
rescue => detail
puts detail.backtrace if Puppet[:trace]
$stderr.puts detail.message
diff --git a/lib/puppet/application/ca.rb b/lib/puppet/application/ca.rb
new file mode 100644
index 000000000..d1ec2502e
--- /dev/null
+++ b/lib/puppet/application/ca.rb
@@ -0,0 +1,5 @@
+require 'puppet/application/face_base'
+
+class Puppet::Application::Ca < Puppet::Application::FaceBase
+ run_mode :master
+end
diff --git a/lib/puppet/application/cert.rb b/lib/puppet/application/cert.rb
index 162672b6a..330fba8bd 100644
--- a/lib/puppet/application/cert.rb
+++ b/lib/puppet/application/cert.rb
@@ -218,7 +218,8 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License
if sub = self.command_line.args.shift then
self.subcommand = sub
else
- help
+ puts help
+ exit
end
end
result
diff --git a/lib/puppet/application/certificate.rb b/lib/puppet/application/certificate.rb
index eacb830b2..de5b2c499 100644
--- a/lib/puppet/application/certificate.rb
+++ b/lib/puppet/application/certificate.rb
@@ -2,11 +2,6 @@ require 'puppet/application/indirection_base'
class Puppet::Application::Certificate < Puppet::Application::IndirectionBase
def setup
- unless options[:ca_location]
- raise ArgumentError, "You must have a CA location specified;\n" +
- "use --ca-location to specify the location (remote, local, only)"
- end
-
location = Puppet::SSL::Host.ca_location
if location == :local && !Puppet::SSL::CertificateAuthority.ca?
self.class.run_mode("master")
diff --git a/lib/puppet/application/device.rb b/lib/puppet/application/device.rb
index 3e2dec98c..977c5c023 100644
--- a/lib/puppet/application/device.rb
+++ b/lib/puppet/application/device.rb
@@ -113,10 +113,10 @@ parameter, so you can specify '--server <servername>' as an argument.
Enable full debugging.
* --detailed-exitcodes:
- Provide transaction information via exit codes. If this is enabled, an
- exit code of '2' means there were changes, and an exit code of '4' means
- that there were failures during the transaction. This option only makes
- sense in conjunction with --onetime.
+ Provide transaction information via exit codes. If this is enabled, an exit
+ code of '2' means there were changes, an exit code of '4' means there were
+ failures during the transaction, and an exit code of '6' means there were both
+ changes and failures.
* --help:
Print this help message
diff --git a/lib/puppet/application/doc.rb b/lib/puppet/application/doc.rb
index a88f27c78..65cd37da8 100644
--- a/lib/puppet/application/doc.rb
+++ b/lib/puppet/application/doc.rb
@@ -87,29 +87,40 @@ puppet doc will output a single manifest's documentation on stdout.
OPTIONS
-------
* --all:
- Output the docs for all of the reference types. In 'rdoc'
- modes, this also outputs documentation for all resources
+ Output the docs for all of the reference types. In 'rdoc' mode, this also
+ outputs documentation for all resources.
* --help:
Print this help message
* --outputdir:
- Specifies the directory where to output the rdoc
- documentation in 'rdoc' mode.
+ Used only in 'rdoc' mode. The directory to which the rdoc output should
+ be written.
* --mode:
- Determine the output mode. Valid modes are 'text', 'pdf' and
- 'rdoc'. The 'pdf' mode creates PDF formatted files in the
- /tmp directory. The default mode is 'text'. In 'rdoc' mode
- you must provide 'manifests-path'
+ Determine the output mode. Valid modes are 'text', 'pdf' and 'rdoc'. The 'pdf'
+ mode creates PDF formatted files in the /tmp directory. The default mode is
+ 'text'. In 'rdoc' mode you must provide 'manifests-path'
* --reference:
- Build a particular reference. Get a list of references by
- running 'puppet doc --list'.
+ Build a particular reference. Get a list of references by running
+ 'puppet doc --list'.
* --charset:
- Used only in 'rdoc' mode. It sets the charset used in the
- html files produced.
+ Used only in 'rdoc' mode. It sets the charset used in the html files produced.
+
+* --manifestdir:
+ Used only in 'rdoc' mode. The directory to scan for stand-alone manifests.
+ If not supplied, puppet doc will use the manifestdir from puppet.conf.
+
+* --modulepath:
+ Used only in 'rdoc' mode. The directory or directories to scan for modules.
+ If not supplied, puppet doc will use the modulepath from puppet.conf.
+
+* --environment:
+ Used only in 'rdoc' mode. The configuration environment from which
+ to read the modulepath and manifestdir settings, when reading said settings
+ from puppet.conf. Due to a known bug, this option is not currently effective.
EXAMPLE
diff --git a/lib/puppet/application/face_base.rb b/lib/puppet/application/face_base.rb
index ea5ba4aaf..a111518f1 100644
--- a/lib/puppet/application/face_base.rb
+++ b/lib/puppet/application/face_base.rb
@@ -100,7 +100,8 @@ class Puppet::Application::FaceBase < Puppet::Application
# action object it represents; if this is an invalid action name that
# will be nil, and handled later.
action_name = item.to_sym
- @action = @face.get_action(action_name)
+ @action = Puppet::Face.find_action(@face.name, action_name)
+ @face = @action.face if @action
end
end
diff --git a/lib/puppet/application/inspect.rb b/lib/puppet/application/inspect.rb
index 30865cfc1..b5a4ac872 100644
--- a/lib/puppet/application/inspect.rb
+++ b/lib/puppet/application/inspect.rb
@@ -1,6 +1,4 @@
-require 'puppet'
require 'puppet/application'
-require 'puppet/file_bucket/dipper'
class Puppet::Application::Inspect < Puppet::Application
@@ -98,6 +96,11 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License
Puppet::Resource::Catalog.indirection.terminus_class = :yaml
end
+ def preinit
+ require 'puppet'
+ require 'puppet/file_bucket/dipper'
+ end
+
def run_command
benchmark(:notice, "Finished inspection") do
retrieval_starttime = Time.now
diff --git a/lib/puppet/application/master.rb b/lib/puppet/application/master.rb
index 18425c8bc..b4da770f0 100644
--- a/lib/puppet/application/master.rb
+++ b/lib/puppet/application/master.rb
@@ -206,6 +206,8 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License
end
def setup
+ raise Puppet::Error.new("Puppet master is not supported on Microsoft Windows") if Puppet.features.microsoft_windows?
+
# Handle the logging settings.
if options[:debug] or options[:verbose]
if options[:debug]
diff --git a/lib/puppet/configurer.rb b/lib/puppet/configurer.rb
index 980da634e..5581917a1 100644
--- a/lib/puppet/configurer.rb
+++ b/lib/puppet/configurer.rb
@@ -5,8 +5,6 @@ require 'puppet/network/http_pool'
require 'puppet/util'
class Puppet::Configurer
- class CommandHookError < RuntimeError; end
-
require 'puppet/configurer/fact_handler'
require 'puppet/configurer/plugin_handler'
@@ -79,8 +77,6 @@ class Puppet::Configurer
download_plugins unless options[:skip_plugin_download]
download_fact_plugins unless options[:skip_plugin_download]
-
- execute_prerun_command
end
# Get the remote catalog, yo. Returns nil if no catalog can be found.
@@ -109,67 +105,69 @@ class Puppet::Configurer
catalog
end
- # The code that actually runs the catalog.
- # This just passes any options on to the catalog,
- # which accepts :tags and :ignoreschedules.
- def run(options = {})
- begin
- prepare(options)
- rescue SystemExit,NoMemoryError
- raise
- rescue Exception => detail
- puts detail.backtrace if Puppet[:trace]
- Puppet.err "Failed to prepare catalog: #{detail}"
+ # Retrieve (optionally) and apply a catalog. If a catalog is passed in
+ # the options, then apply that one, otherwise retrieve it.
+ def retrieve_and_apply_catalog(options, fact_options)
+ unless catalog = (options.delete(:catalog) || retrieve_catalog(fact_options))
+ Puppet.err "Could not retrieve catalog; skipping run"
+ return
end
- if Puppet::Resource::Catalog.indirection.terminus_class == :rest
- # This is a bit complicated. We need the serialized and escaped facts,
- # and we need to know which format they're encoded in. Thus, we
- # get a hash with both of these pieces of information.
- fact_options = facts_for_uploading
+ report = options[:report]
+ report.configuration_version = catalog.version
+
+ benchmark(:notice, "Finished catalog run") do
+ catalog.apply(options)
end
+ report.finalize_report
+ report
+ end
+
+ # The code that actually runs the catalog.
+ # This just passes any options on to the catalog,
+ # which accepts :tags and :ignoreschedules.
+ def run(options = {})
options[:report] ||= Puppet::Transaction::Report.new("apply")
report = options[:report]
- Puppet::Util::Log.newdestination(report)
- if catalog = options[:catalog]
- options.delete(:catalog)
- elsif ! catalog = retrieve_catalog(fact_options)
- Puppet.err "Could not retrieve catalog; skipping run"
- return
- end
+ Puppet::Util::Log.newdestination(report)
+ begin
+ prepare(options)
- report.configuration_version = catalog.version
+ if Puppet::Resource::Catalog.indirection.terminus_class == :rest
+ # This is a bit complicated. We need the serialized and escaped facts,
+ # and we need to know which format they're encoded in. Thus, we
+ # get a hash with both of these pieces of information.
+ fact_options = facts_for_uploading
+ end
- transaction = nil
+ # set report host name now that we have the fact
+ report.host = Puppet[:node_name_value]
- begin
- benchmark(:notice, "Finished catalog run") do
- transaction = catalog.apply(options)
+ begin
+ execute_prerun_command or return nil
+ retrieve_and_apply_catalog(options, fact_options)
+ rescue SystemExit,NoMemoryError
+ raise
+ rescue => detail
+ puts detail.backtrace if Puppet[:trace]
+ Puppet.err "Failed to apply catalog: #{detail}"
+ return nil
+ ensure
+ execute_postrun_command or return nil
end
- report
- rescue => detail
- puts detail.backtrace if Puppet[:trace]
- Puppet.err "Failed to apply catalog: #{detail}"
- return
+ ensure
+ # Make sure we forget the retained module_directories of any autoload
+ # we might have used.
+ Thread.current[:env_module_directories] = nil
end
ensure
- # Make sure we forget the retained module_directories of any autoload
- # we might have used.
- Thread.current[:env_module_directories] = nil
-
- # Now close all of our existing http connections, since there's no
- # reason to leave them lying open.
- Puppet::Network::HttpPool.clear_http_instances
- execute_postrun_command
-
Puppet::Util::Log.close(report)
- send_report(report, transaction)
+ send_report(report)
end
- def send_report(report, trans)
- report.finalize_report if trans
+ def send_report(report)
puts report.summary if Puppet[:summarize]
save_last_run_summary(report)
Puppet::Transaction::Report.indirection.save(report) if Puppet[:report]
@@ -207,12 +205,15 @@ class Puppet::Configurer
end
def execute_from_setting(setting)
- return if (command = Puppet[setting]) == ""
+ return true if (command = Puppet[setting]) == ""
begin
Puppet::Util.execute([command])
+ true
rescue => detail
- raise CommandHookError, "Could not run command from #{setting}: #{detail}"
+ puts detail.backtrace if Puppet[:trace]
+ Puppet.err "Could not run command from #{setting}: #{detail}"
+ false
end
end
diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb
index 07442d0e9..637ee8fdd 100644
--- a/lib/puppet/defaults.rb
+++ b/lib/puppet/defaults.rb
@@ -47,10 +47,14 @@ module Puppet
exits. Comma-separate multiple values. For a list of all values,
specify 'all'. This feature is only available in Puppet versions
higher than 0.18.4."],
- :color => ["ansi", "Whether to use colors when logging to the console.
+ :color => {
+ :default => (Puppet.features.microsoft_windows? ? "false" : "ansi"),
+ :type => :setting,
+ :desc => "Whether to use colors when logging to the console.
Valid values are `ansi` (equivalent to `true`), `html` (mostly
used during testing with TextMate), and `false`, which produces
- no color."],
+ no color.",
+ },
:mkusers => [false,
"Whether to create the necessary user and group that puppet agent will
run as."],
@@ -437,9 +441,11 @@ module Puppet
authorization system for `puppet master`."
],
:ca => [true, "Wether the master should function as a certificate authority."],
- :modulepath => {:default => "$confdir/modules:/usr/share/puppet/modules",
- :desc => "The search path for modules as a colon-separated list of
- directories.", :type => :setting }, # We don't want this to be considered a file, since it's multiple files.
+ :modulepath => {
+ :default => "$confdir/modules#{File::PATH_SEPARATOR}/usr/share/puppet/modules",
+ :desc => "The search path for modules as a list of directories separated by the '#{File::PATH_SEPARATOR}' character.",
+ :type => :setting # We don't want this to be considered a file, since it's multiple files.
+ },
:ssl_client_header => ["HTTP_X_CLIENT_DN", "The header containing an authenticated
client's SSL DN. Only used with Mongrel. This header must be set by the proxy
to the authenticated client's SSL DN (e.g., `/CN=puppet.puppetlabs.com`).
@@ -466,7 +472,7 @@ module Puppet
:desc => "The directory in which to store reports
received from the client. Each client gets a separate
subdirectory."},
- :reporturl => ["http://localhost:3000/reports",
+ :reporturl => ["http://localhost:3000/reports/upload",
"The URL used by the http reports processor to send reports"],
:fileserverconfig => ["$confdir/fileserver.conf", "Where the fileserver configuration is stored."],
:strict_hostname_checking => [false, "Whether to only search for the complete
diff --git a/lib/puppet/face/ca.rb b/lib/puppet/face/ca.rb
new file mode 100644
index 000000000..e643530f0
--- /dev/null
+++ b/lib/puppet/face/ca.rb
@@ -0,0 +1,233 @@
+require 'puppet/face'
+
+Puppet::Face.define(:ca, '0.1.0') do
+ copyright "Puppet Labs", 2011
+ license "Apache 2 license; see COPYING"
+
+ summary "Local Puppet Certificate Authority management."
+
+ description <<TEXT
+This provides local management of the Puppet Certificate Authority.
+
+You can use this subcommand to sign outstanding certificate requests, list
+and manage local certificates, and inspect the state of the CA.
+TEXT
+
+ action :list do
+ summary "List certificates and/or certificate requests."
+
+ description <<-end
+This will list the current certificates and certificate signing requests
+in the Puppet CA. You will also get the fingerprint, and any certificate
+verification failure reported.
+ end
+
+ option "--[no-]all" do
+ summary "Include all certificates and requests."
+ end
+
+ option "--[no-]pending" do
+ summary "Include pending certificate signing requests."
+ end
+
+ option "--[no-]signed" do
+ summary "Include signed certificates."
+ end
+
+ option "--subject PATTERN" do
+ summary "Only list if the subject matches PATTERN."
+
+ description <<TEXT
+Only include certificates or requests where subject matches PATTERN.
+
+PATTERN is interpreted as a regular expression, allowing complex
+filtering of the content.
+TEXT
+ end
+
+ when_invoked do |options|
+ raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca?
+ unless ca = Puppet::SSL::CertificateAuthority.instance
+ raise "Unable to fetch the CA"
+ end
+
+ pattern = options[:subject].nil? ? nil :
+ Regexp.new(options[:subject], Regexp::IGNORECASE)
+
+ pending = options[:pending].nil? ? options[:all] : options[:pending]
+ signed = options[:signed].nil? ? options[:all] : options[:signed]
+
+ # By default we list pending, so if nothing at all was requested...
+ unless pending or signed then pending = true end
+
+ hosts = []
+
+ pending and hosts += ca.waiting?
+ signed and hosts += ca.list
+
+ pattern and hosts = hosts.select {|hostname| pattern.match hostname }
+
+ hosts.sort.map {|host| Puppet::SSL::Host.new(host) }
+ end
+
+ when_rendering :console do |hosts|
+ unless ca = Puppet::SSL::CertificateAuthority.instance
+ raise "Unable to fetch the CA"
+ end
+
+ length = hosts.map{|x| x.name.length }.max + 1
+
+ hosts.map do |host|
+ name = host.name.ljust(length)
+ if host.certificate_request then
+ " #{name} (#{host.certificate_request.fingerprint})"
+ else
+ begin
+ ca.verify(host.certificate)
+ "+ #{name} (#{host.certificate.fingerprint})"
+ rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError => e
+ "- #{name} (#{host.certificate.fingerprint}) (#{e.to_s})"
+ end
+ end
+ end.join("\n")
+ end
+ end
+
+ action :destroy do
+ when_invoked do |host, options|
+ raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca?
+ unless ca = Puppet::SSL::CertificateAuthority.instance
+ raise "Unable to fetch the CA"
+ end
+
+ ca.destroy host
+ end
+ end
+
+ action :revoke do
+ when_invoked do |host, options|
+ raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca?
+ unless ca = Puppet::SSL::CertificateAuthority.instance
+ raise "Unable to fetch the CA"
+ end
+
+ begin
+ ca.revoke host
+ rescue ArgumentError => e
+ # This is a bit naff, but it makes the behaviour consistent with the
+ # destroy action. The underlying tools could be nicer for that sort
+ # of thing; they have fairly inconsistent reporting of failures.
+ raise unless e.to_s =~ /Could not find a serial number for /
+ "Nothing was revoked"
+ end
+ end
+ end
+
+ action :generate do
+ when_invoked do |host, options|
+ raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca?
+ unless ca = Puppet::SSL::CertificateAuthority.instance
+ raise "Unable to fetch the CA"
+ end
+
+ begin
+ ca.generate host
+ rescue RuntimeError => e
+ if e.to_s =~ /already has a requested certificate/
+ "#{host} already has a certificate request; use sign instead"
+ else
+ raise
+ end
+ rescue ArgumentError => e
+ if e.to_s =~ /A Certificate already exists for /
+ "#{host} already has a certificate"
+ else
+ raise
+ end
+ end
+ end
+ end
+
+ action :sign do
+ when_invoked do |host, options|
+ raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca?
+ unless ca = Puppet::SSL::CertificateAuthority.instance
+ raise "Unable to fetch the CA"
+ end
+
+ begin
+ ca.sign host
+ rescue ArgumentError => e
+ if e.to_s =~ /Could not find certificate request/
+ e.to_s
+ else
+ raise
+ end
+ end
+ end
+ end
+
+ action :print do
+ when_invoked do |host, options|
+ raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca?
+ unless ca = Puppet::SSL::CertificateAuthority.instance
+ raise "Unable to fetch the CA"
+ end
+
+ ca.print host
+ end
+ end
+
+ action :fingerprint do
+ option "--digest ALGORITHM" do
+ summary "The hash algorithm to use when displaying the fingerprint"
+ end
+
+ when_invoked do |host, options|
+ raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca?
+ unless ca = Puppet::SSL::CertificateAuthority.instance
+ raise "Unable to fetch the CA"
+ end
+
+ begin
+ # I want the default from the CA, not to duplicate it, but passing
+ # 'nil' explicitly means that we don't get that. This works...
+ if options.has_key? :digest
+ ca.fingerprint host, options[:digest]
+ else
+ ca.fingerprint host
+ end
+ rescue ArgumentError => e
+ raise unless e.to_s =~ /Could not find a certificate or csr for/
+ nil
+ end
+ end
+ end
+
+ action :verify do
+ when_invoked do |host, options|
+ raise "Not a CA" unless Puppet::SSL::CertificateAuthority.ca?
+ unless ca = Puppet::SSL::CertificateAuthority.instance
+ raise "Unable to fetch the CA"
+ end
+
+ begin
+ ca.verify host
+ { :host => host, :valid => true }
+ rescue ArgumentError => e
+ raise unless e.to_s =~ /Could not find a certificate for/
+ { :host => host, :valid => false, :error => e.to_s }
+ rescue Puppet::SSL::CertificateAuthority::CertificateVerificationError => e
+ { :host => host, :valid => false, :error => e.to_s }
+ end
+ end
+
+ when_rendering :console do |value|
+ if value[:valid]
+ nil
+ else
+ "Could not verify #{value[:host]}: #{value[:error]}"
+ end
+ end
+ end
+end
diff --git a/lib/puppet/face/certificate.rb b/lib/puppet/face/certificate.rb
index 9a306da37..8019b6bea 100644
--- a/lib/puppet/face/certificate.rb
+++ b/lib/puppet/face/certificate.rb
@@ -6,7 +6,7 @@ Puppet::Indirector::Face.define(:certificate, '0.0.1') do
license "Apache 2 license; see COPYING"
summary "Provide access to the CA for certificate management."
- description <<-'EOT'
+ description <<-EOT
This subcommand interacts with a local or remote Puppet certificate
authority. Currently, its behavior is not a full superset of `puppet
cert`; specifically, it is unable to mimic puppet cert's "clean" option,
@@ -15,8 +15,9 @@ Puppet::Indirector::Face.define(:certificate, '0.0.1') do
EOT
option "--ca-location LOCATION" do
+ required
summary "Which certificate authority to use (local or remote)."
- description <<-'EOT'
+ description <<-EOT
Whether to act on the local certificate authority or one provided by a
remote puppet master. Allowed values are 'local' and 'remote.'
@@ -24,6 +25,9 @@ Puppet::Indirector::Face.define(:certificate, '0.0.1') do
EOT
before_action do |action, args, options|
+ unless [:remote, :local, :only].include? options[:ca_location].to_sym
+ raise ArgumentError, "Valid values for ca-location are 'remote', 'local', 'only'."
+ end
Puppet::SSL::Host.ca_location = options[:ca_location].to_sym
end
end
@@ -32,7 +36,7 @@ Puppet::Indirector::Face.define(:certificate, '0.0.1') do
summary "Generate a new certificate signing request."
arguments "<host>"
returns "Nothing."
- description <<-'EOT'
+ description <<-EOT
Generates and submits a certificate signing request (CSR) for the
specified host. This CSR will then have to be signed by a user
with the proper authorization on the certificate authority.
@@ -41,7 +45,7 @@ Puppet::Indirector::Face.define(:certificate, '0.0.1') do
primarily useful for requesting certificates for individual users and
external applications.
EOT
- examples <<-'EOT'
+ examples <<-EOT
Request a certificate for "somenode" from the site's CA:
$ puppet certificate generate somenode.puppetlabs.lan --ca-location remote
@@ -56,7 +60,7 @@ Puppet::Indirector::Face.define(:certificate, '0.0.1') do
action :list do
summary "List all certificate signing requests."
- returns <<-'EOT'
+ returns <<-EOT
An array of #inspect output from CSR objects. This output is
currently messy, but does contain the names of nodes requesting
certificates. This action returns #inspect strings even when used
@@ -73,10 +77,10 @@ Puppet::Indirector::Face.define(:certificate, '0.0.1') do
action :sign do
summary "Sign a certificate signing request for HOST."
arguments "<host>"
- returns <<-'EOT'
+ returns <<-EOT
A string that appears to be (but isn't) an x509 certificate.
EOT
- examples <<-'EOT'
+ examples <<-EOT
Sign somenode.puppetlabs.lan's certificate:
$ puppet certificate sign somenode.puppetlabs.lan --ca-location remote
@@ -93,9 +97,9 @@ Puppet::Indirector::Face.define(:certificate, '0.0.1') do
find = get_action(:find)
find.summary "Retrieve a certificate."
find.arguments "<host>"
- find.returns <<-'EOT'
- An x509 SSL certificate. You will usually want to render this as a
- string (--render-as s).
+ find.render_as = :s
+ find.returns <<-EOT
+ An x509 SSL certificate.
Note that this action has a side effect of caching a copy of the
certificate in Puppet's `ssldir`.
@@ -105,7 +109,7 @@ Puppet::Indirector::Face.define(:certificate, '0.0.1') do
destroy.summary "Delete a certificate."
destroy.arguments "<host>"
destroy.returns "Nothing."
- destroy.description <<-'EOT'
+ destroy.description <<-EOT
Deletes a certificate. This action currently only works on the local CA.
EOT
diff --git a/lib/puppet/face/certificate_request.rb b/lib/puppet/face/certificate_request.rb
index 774821f12..cf342d51a 100644
--- a/lib/puppet/face/certificate_request.rb
+++ b/lib/puppet/face/certificate_request.rb
@@ -5,7 +5,7 @@ Puppet::Indirector::Face.define(:certificate_request, '0.0.1') do
license "Apache 2 license; see COPYING"
summary "Manage certificate requests."
- description <<-'EOT'
+ description <<-EOT
This subcommand retrieves and submits certificate signing requests (CSRs).
EOT
@@ -15,14 +15,12 @@ Puppet::Indirector::Face.define(:certificate_request, '0.0.1') do
find = get_action(:find)
find.summary "Retrieve a single CSR."
find.arguments "<host>"
- find.returns <<-'EOT'
+ find.render_as = :s
+ find.returns <<-EOT
A single certificate request. When used from the Ruby API, returns a
Puppet::SSL::CertificateRequest object.
-
- RENDERING ISSUES: In most cases, you will want to render this as a string
- ('--render-as s').
EOT
- find.examples <<-'EOT'
+ find.examples <<-EOT
Retrieve a single CSR from the puppet master's CA:
$ puppet certificate_request find somenode.puppetlabs.lan --terminus rest
@@ -31,10 +29,10 @@ Puppet::Indirector::Face.define(:certificate_request, '0.0.1') do
search = get_action(:search)
search.summary "Retrieve all outstanding CSRs."
search.arguments "<dummy_text>"
- search.returns <<-'EOT'
- A list of certificate requests; be sure to to render this as a string
- ('--render-as s'). When used from the Ruby API, returns an array of
- Puppet::SSL::CertificateRequest objects.
+ search.render_as = :s
+ search.returns <<-EOT
+ A list of certificate requests. When used from the Ruby API, returns an
+ array of Puppet::SSL::CertificateRequest objects.
EOT
search.short_description <<-EOT
Retrieves all outstanding certificate signing requests. Due to a known bug,
@@ -44,7 +42,7 @@ Puppet::Indirector::Face.define(:certificate_request, '0.0.1') do
Although this action always returns all CSRs, it requires a dummy search
key; this is a known bug.
EOT
- search.examples <<-'EOT'
+ search.examples <<-EOT
Retrieve all CSRs from the local CA (similar to 'puppet cert list'):
$ puppet certificate_request search x --terminus ca
diff --git a/lib/puppet/face/certificate_revocation_list.rb b/lib/puppet/face/certificate_revocation_list.rb
index f58368f75..022323b29 100644
--- a/lib/puppet/face/certificate_revocation_list.rb
+++ b/lib/puppet/face/certificate_revocation_list.rb
@@ -5,7 +5,7 @@ Puppet::Indirector::Face.define(:certificate_revocation_list, '0.0.1') do
license "Apache 2 license; see COPYING"
summary "Manage the list of revoked certificates."
- description <<-'EOT'
+ description <<-EOT
This subcommand is primarily for retrieving the certificate revocation
list from the CA.
EOT
@@ -13,12 +13,10 @@ Puppet::Indirector::Face.define(:certificate_revocation_list, '0.0.1') do
find = get_action(:find)
find.summary "Retrieve the certificate revocation list."
find.arguments "<dummy_text>"
- find.returns <<-'EOT'
+ find.render_as = :s
+ find.returns <<-EOT
The certificate revocation list. When used from the Ruby API: returns an
OpenSSL::X509::CRL object.
-
- RENDERING ISSUES: this should usually be rendered as a string
- ('--render-as s').
EOT
find.short_description <<-EOT
Retrieves the certificate revocation list. Due to a known bug, this action
@@ -28,7 +26,7 @@ Puppet::Indirector::Face.define(:certificate_revocation_list, '0.0.1') do
Although this action always returns the CRL from the specified terminus, it
requires a dummy argument; this is a known bug.
EOT
- find.examples <<-'EXAMPLES'
+ find.examples <<-EXAMPLES
Retrieve a copy of the puppet master's CRL:
$ puppet certificate_revocation_list find crl --terminus rest
@@ -38,7 +36,7 @@ Puppet::Indirector::Face.define(:certificate_revocation_list, '0.0.1') do
destroy.summary "Delete the certificate revocation list."
destroy.arguments "<dummy_text>"
destroy.returns "Nothing."
- destroy.description <<-'EOT'
+ destroy.description <<-EOT
Deletes the certificate revocation list. This cannot be done over REST, but
it is possible to delete the locally cached copy or the local CA's copy of
the CRL.
diff --git a/lib/puppet/face/status.rb b/lib/puppet/face/status.rb
index bdb0c4d26..e8c87e98d 100644
--- a/lib/puppet/face/status.rb
+++ b/lib/puppet/face/status.rb
@@ -12,6 +12,7 @@ Puppet::Indirector::Face.define(:status, '0.0.1') do
get_action(:search).summary "Invalid for this subcommand."
find = get_action(:find)
+ find.default = true
find.summary "Check status of puppet master server."
find.arguments "<dummy_text>"
find.returns <<-'EOT'
diff --git a/lib/puppet/feature/base.rb b/lib/puppet/feature/base.rb
index c983f5c12..b4b1313f8 100644
--- a/lib/puppet/feature/base.rb
+++ b/lib/puppet/feature/base.rb
@@ -43,7 +43,16 @@ Puppet.features.add(:posix) do
end
# We can use Microsoft Windows functions
-Puppet.features.add(:microsoft_windows, :libs => ["sys/admin", "win32/process", "win32/dir"])
+Puppet.features.add(:microsoft_windows) do
+ begin
+ require 'sys/admin'
+ require 'win32/process'
+ require 'win32/dir'
+ require 'win32/service'
+ rescue LoadError => err
+ warn "Cannot run on Microsoft Windows without the sys-admin, win32-process, win32-dir & win32-service gems: #{err}" unless Puppet.features.posix?
+ end
+end
raise Puppet::Error,"Cannot determine basic system flavour" unless Puppet.features.posix? or Puppet.features.microsoft_windows?
diff --git a/lib/puppet/file_bucket/dipper.rb b/lib/puppet/file_bucket/dipper.rb
index d6f6a3747..870c50eec 100644
--- a/lib/puppet/file_bucket/dipper.rb
+++ b/lib/puppet/file_bucket/dipper.rb
@@ -35,11 +35,12 @@ class Puppet::FileBucket::Dipper
begin
file_bucket_file = Puppet::FileBucket::File.new(contents, :bucket_path => @local_path)
files_original_path = absolutize_path(file)
- dest_path = "#{@rest_path}#{file_bucket_file.name}#{files_original_path}"
+ dest_path = "#{@rest_path}#{file_bucket_file.name}/#{files_original_path}"
+ file_bucket_path = "#{@rest_path}#{file_bucket_file.checksum_type}/#{file_bucket_file.checksum_data}/#{files_original_path}"
# Make a HEAD request for the file so that we don't waste time
# uploading it if it already exists in the bucket.
- unless Puppet::FileBucket::File.indirection.head("#{@rest_path}#{file_bucket_file.checksum_type}/#{file_bucket_file.checksum_data}#{files_original_path}")
+ unless Puppet::FileBucket::File.indirection.head(file_bucket_path)
Puppet::FileBucket::File.indirection.save(file_bucket_file, dest_path)
end
diff --git a/lib/puppet/file_bucket/file.rb b/lib/puppet/file_bucket/file.rb
index 08c0329f1..2a0558fde 100644
--- a/lib/puppet/file_bucket/file.rb
+++ b/lib/puppet/file_bucket/file.rb
@@ -15,11 +15,11 @@ class Puppet::FileBucket::File
attr :bucket_path
def initialize( contents, options = {} )
- raise ArgumentError if !contents.is_a?(String)
- @contents = contents
+ raise ArgumentError.new("contents must be a String, got a #{contents.class}") unless contents.is_a?(String)
+ @contents = contents
@bucket_path = options.delete(:bucket_path)
- raise ArgumentError if options != {}
+ raise ArgumentError.new("Unknown option(s): #{options.keys.join(', ')}") unless options.empty?
end
def checksum_type
diff --git a/lib/puppet/file_serving/base.rb b/lib/puppet/file_serving/base.rb
index 09cab97d9..706f67af9 100644
--- a/lib/puppet/file_serving/base.rb
+++ b/lib/puppet/file_serving/base.rb
@@ -53,7 +53,10 @@ class Puppet::FileServing::Base
# Set our base path.
attr_reader :path
def path=(path)
- raise ArgumentError.new("Paths must be fully qualified") unless path =~ /^#{::File::SEPARATOR}/
+ unless path =~ /^#{::File::SEPARATOR}/ or path =~ /^[a-z]:[\/\\]/i
+ raise ArgumentError.new("Paths must be fully qualified")
+ end
+
@path = path
end
diff --git a/lib/puppet/file_serving/configuration.rb b/lib/puppet/file_serving/configuration.rb
index 78e4de6cb..d88d57cb0 100644
--- a/lib/puppet/file_serving/configuration.rb
+++ b/lib/puppet/file_serving/configuration.rb
@@ -2,29 +2,27 @@
# Created by Luke Kanies on 2007-10-16.
# Copyright (c) 2007. All rights reserved.
+require 'monitor'
require 'puppet'
require 'puppet/file_serving'
require 'puppet/file_serving/mount'
require 'puppet/file_serving/mount/file'
require 'puppet/file_serving/mount/modules'
require 'puppet/file_serving/mount/plugins'
-require 'puppet/util/cacher'
class Puppet::FileServing::Configuration
require 'puppet/file_serving/configuration/parser'
- class << self
- include Puppet::Util::Cacher
- cached_attr(:configuration) { new }
+ extend MonitorMixin
+
+ def self.configuration
+ synchronize do
+ @configuration ||= new
+ end
end
Mount = Puppet::FileServing::Mount
- # Create our singleton configuration.
- def self.create
- configuration
- end
-
private_class_method :new
attr_reader :mounts
diff --git a/lib/puppet/file_serving/configuration/parser.rb b/lib/puppet/file_serving/configuration/parser.rb
index 334201d37..83b75e28f 100644
--- a/lib/puppet/file_serving/configuration/parser.rb
+++ b/lib/puppet/file_serving/configuration/parser.rb
@@ -24,9 +24,10 @@ class Puppet::FileServing::Configuration::Parser < Puppet::Util::LoadedFile
when /^\s*$/; next # skip blank lines
when /\[([-\w]+)\]/
mount = newmount($1)
- when /^\s*(\w+)\s+(.+)$/
+ when /^\s*(\w+)\s+(.+?)(\s*#.*)?$/
var = $1
value = $2
+ value.strip!
raise(ArgumentError, "Fileserver configuration file does not use '=' as a separator") if value =~ /^=/
case var
when "path"
@@ -58,12 +59,8 @@ class Puppet::FileServing::Configuration::Parser < Puppet::Util::LoadedFile
begin
mount.info "allowing #{val} access"
mount.allow(val)
- rescue AuthStoreError => detail
-
- raise ArgumentError.new(
- detail.to_s,
-
- @count, file)
+ rescue Puppet::AuthStoreError => detail
+ raise ArgumentError.new(detail.to_s, @count, file)
end
}
end
@@ -75,12 +72,8 @@ class Puppet::FileServing::Configuration::Parser < Puppet::Util::LoadedFile
begin
mount.info "denying #{val} access"
mount.deny(val)
- rescue AuthStoreError => detail
-
- raise ArgumentError.new(
- detail.to_s,
-
- @count, file)
+ rescue Puppet::AuthStoreError => detail
+ raise ArgumentError.new(detail.to_s, @count, file)
end
}
end
diff --git a/lib/puppet/file_serving/fileset.rb b/lib/puppet/file_serving/fileset.rb
index f29f70a53..b4f1457df 100644
--- a/lib/puppet/file_serving/fileset.rb
+++ b/lib/puppet/file_serving/fileset.rb
@@ -59,8 +59,13 @@ class Puppet::FileServing::Fileset
end
def initialize(path, options = {})
- path = path.chomp(File::SEPARATOR) unless path == File::SEPARATOR
- raise ArgumentError.new("Fileset paths must be fully qualified") unless File.expand_path(path) == path
+ if Puppet.features.microsoft_windows?
+ # REMIND: UNC path
+ path = path.chomp(File::SEPARATOR) unless path =~ /^[A-Za-z]:\/$/
+ else
+ path = path.chomp(File::SEPARATOR) unless path == File::SEPARATOR
+ end
+ raise ArgumentError.new("Fileset paths must be fully qualified: #{path}") unless File.expand_path(path) == path
@path = path
diff --git a/lib/puppet/file_serving/indirection_hooks.rb b/lib/puppet/file_serving/indirection_hooks.rb
index a85e90ef1..2a0dc1792 100644
--- a/lib/puppet/file_serving/indirection_hooks.rb
+++ b/lib/puppet/file_serving/indirection_hooks.rb
@@ -17,6 +17,7 @@ module Puppet::FileServing::IndirectionHooks
# Short-circuit to :file if it's a fully-qualified path or specifies a 'file' protocol.
return PROTOCOL_MAP["file"] if request.key =~ /^#{::File::SEPARATOR}/
+ return PROTOCOL_MAP["file"] if request.key =~ /^[a-z]:[\/\\]/i
return PROTOCOL_MAP["file"] if request.protocol == "file"
# We're heading over the wire the protocol is 'puppet' and we've got a server name or we're not named 'apply' or 'puppet'
diff --git a/lib/puppet/file_serving/mount.rb b/lib/puppet/file_serving/mount.rb
index 37dd89537..79290ab81 100644
--- a/lib/puppet/file_serving/mount.rb
+++ b/lib/puppet/file_serving/mount.rb
@@ -4,7 +4,6 @@
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'
diff --git a/lib/puppet/file_serving/mount/file.rb b/lib/puppet/file_serving/mount/file.rb
index 7d622e4bf..7f5af7f52 100644
--- a/lib/puppet/file_serving/mount/file.rb
+++ b/lib/puppet/file_serving/mount/file.rb
@@ -1,18 +1,15 @@
-require 'puppet/util/cacher'
-
require 'puppet/file_serving/mount'
class Puppet::FileServing::Mount::File < Puppet::FileServing::Mount
- class << self
- include Puppet::Util::Cacher
-
- cached_attr(:localmap) do
- { "h" => Facter.value("hostname"),
- "H" => [Facter.value("hostname"),
- Facter.value("domain")].join("."),
- "d" => Facter.value("domain")
- }
- end
+ def self.localmap
+ @localmap ||= {
+ "h" => Facter.value("hostname"),
+ "H" => [
+ Facter.value("hostname"),
+ Facter.value("domain")
+ ].join("."),
+ "d" => Facter.value("domain")
+ }
end
def complete_path(relative_path, node)
diff --git a/lib/puppet/indirector/face.rb b/lib/puppet/indirector/face.rb
index ead3f4b46..adb6b688b 100644
--- a/lib/puppet/indirector/face.rb
+++ b/lib/puppet/indirector/face.rb
@@ -48,16 +48,26 @@ class Puppet::Indirector::Face < Puppet::Face
return result
end
+ option "--extra HASH" do
+ summary "Extra arguments to pass to the indirection request"
+ description <<-end
+ A terminus can take additional arguments to refine the operation, which
+ are passed as an arbitrary hash to the back-end. Anything passed as
+ the extra value is just send direct to the back-end.
+ end
+ default_to do Hash.new end
+ end
+
action :destroy do
summary "Delete an object."
arguments "<key>"
- when_invoked { |key, options| call_indirection_method(:destroy, key, options) }
+ when_invoked {|key, options| call_indirection_method :destroy, key, options[:extra] }
end
action :find do
summary "Retrieve an object by name."
arguments "<key>"
- when_invoked { |key, options| call_indirection_method(:find, key, options) }
+ when_invoked {|key, options| call_indirection_method :find, key, options[:extra] }
end
action :save do
@@ -68,13 +78,13 @@ class Puppet::Indirector::Face < Puppet::Face
currently accept data from STDIN, save actions cannot currently be invoked
from the command line.
EOT
- when_invoked { |key, options| call_indirection_method(:save, key, options) }
+ when_invoked {|key, options| call_indirection_method :save, key, options[:extra] }
end
action :search do
summary "Search for an object or retrieve multiple objects."
arguments "<query>"
- when_invoked { |key, options| call_indirection_method(:search, key, options) }
+ when_invoked {|key, options| call_indirection_method :search, key, options[:extra] }
end
# Print the configuration for the current terminus class
@@ -86,11 +96,11 @@ class Puppet::Indirector::Face < Puppet::Face
run mode with the '--mode' option.
EOT
- when_invoked do |*args|
+ when_invoked do |options|
if t = indirection.terminus_class
- puts "Run mode '#{Puppet.run_mode.name}': #{t}"
+ "Run mode '#{Puppet.run_mode.name}': #{t}"
else
- $stderr.puts "No default terminus class for run mode '#{Puppet.run_mode.name}'"
+ "No default terminus class for run mode '#{Puppet.run_mode.name}'"
end
end
end
diff --git a/lib/puppet/indirector/facts/facter.rb b/lib/puppet/indirector/facts/facter.rb
index ab7378a34..6312a95fb 100644
--- a/lib/puppet/indirector/facts/facter.rb
+++ b/lib/puppet/indirector/facts/facter.rb
@@ -9,12 +9,12 @@ class Puppet::Node::Facts::Facter < Puppet::Indirector::Code
def self.load_fact_plugins
# Add any per-module fact directories to the factpath
- module_fact_dirs = Puppet[:modulepath].split(":").collect do |d|
+ module_fact_dirs = Puppet[:modulepath].split(File::PATH_SEPARATOR).collect do |d|
["lib", "plugins"].map do |subdirectory|
Dir.glob("#{d}/*/#{subdirectory}/facter")
end
end.flatten
- dirs = module_fact_dirs + Puppet[:factpath].split(":")
+ dirs = module_fact_dirs + Puppet[:factpath].split(File::PATH_SEPARATOR)
x = dirs.each do |dir|
load_facts_in_dir(dir)
end
diff --git a/lib/puppet/indirector/file_server.rb b/lib/puppet/indirector/file_server.rb
index 46a08c97d..d6a8ab872 100644
--- a/lib/puppet/indirector/file_server.rb
+++ b/lib/puppet/indirector/file_server.rb
@@ -64,6 +64,6 @@ class Puppet::Indirector::FileServer < Puppet::Indirector::Terminus
# Our fileserver configuration, if needed.
def configuration
- Puppet::FileServing::Configuration.create
+ Puppet::FileServing::Configuration.configuration
end
end
diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb
index d958a82ac..20b260b83 100644
--- a/lib/puppet/indirector/indirection.rb
+++ b/lib/puppet/indirector/indirection.rb
@@ -1,13 +1,11 @@
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 = []
@@ -33,6 +31,8 @@ class Puppet::Indirector::Indirection
attr_accessor :name, :model
+ attr_reader :termini
+
# Create and return our cache terminus.
def cache
raise(Puppet::DevError, "Tried to cache when no cache class was set") unless cache_class
@@ -88,6 +88,7 @@ class Puppet::Indirector::Indirection
def initialize(model, name, options = {})
@model = model
@name = name
+ @termini = {}
@cache_class = nil
@terminus_class = nil
@@ -313,7 +314,4 @@ class Puppet::Indirector::Indirection
end
klass.new
end
-
- # Cache our terminus instances indefinitely, but make it easy to clean them up.
- cached_attr(:termini) { Hash.new }
end
diff --git a/lib/puppet/indirector/report/processor.rb b/lib/puppet/indirector/report/processor.rb
index 88fe4b487..81b379eb8 100644
--- a/lib/puppet/indirector/report/processor.rb
+++ b/lib/puppet/indirector/report/processor.rb
@@ -20,9 +20,11 @@ class Puppet::Transaction::Report::Processor < Puppet::Indirector::Code
# LAK:NOTE This isn't necessarily the best design, but it's backward
# compatible and that's good enough for now.
def process(report)
+ Puppet.debug "Recieved report to process from #{report.host}"
return if Puppet[:reports] == "none"
reports.each do |name|
+ Puppet.debug "Processing report from #{report.host} with processor #{name}"
if mod = Puppet::Reports.report(name)
# We have to use a dup because we're including a module in the
# report.
diff --git a/lib/puppet/indirector/request.rb b/lib/puppet/indirector/request.rb
index fd8d654dd..4dfbac9ab 100644
--- a/lib/puppet/indirector/request.rb
+++ b/lib/puppet/indirector/request.rb
@@ -1,6 +1,7 @@
require 'cgi'
require 'uri'
require 'puppet/indirector'
+require 'puppet/util/pson'
# This class encapsulates all of the information you need to make an
# Indirection call, and as a a result also handles REST calls. It's somewhat
@@ -14,6 +15,54 @@ class Puppet::Indirector::Request
OPTION_ATTRIBUTES = [:ip, :node, :authenticated, :ignore_terminus, :ignore_cache, :instance, :environment]
+ # Load json before trying to register.
+ Puppet.features.pson? and ::PSON.register_document_type('IndirectorRequest',self)
+
+ def self.from_pson(json)
+ raise ArgumentError, "No indirection name provided in json data" unless indirection_name = json['type']
+ raise ArgumentError, "No method name provided in json data" unless method = json['method']
+ raise ArgumentError, "No key provided in json data" unless key = json['key']
+
+ request = new(indirection_name, method, key, json['attributes'])
+
+ if instance = json['instance']
+ klass = Puppet::Indirector::Indirection.instance(request.indirection_name).model
+ if instance.is_a?(klass)
+ request.instance = instance
+ else
+ request.instance = klass.from_pson(instance)
+ end
+ end
+
+ request
+ end
+
+ def to_pson(*args)
+ result = {
+ 'document_type' => 'IndirectorRequest',
+ 'data' => {
+ 'type' => indirection_name,
+ 'method' => method,
+ 'key' => key
+ }
+ }
+ data = result['data']
+ attributes = {}
+ OPTION_ATTRIBUTES.each do |key|
+ next unless value = send(key)
+ attributes[key] = value
+ end
+
+ options.each do |opt, value|
+ attributes[opt] = value
+ end
+
+ data['attributes'] = attributes unless attributes.empty?
+ data['instance'] = instance if instance
+
+ result.to_pson(*args)
+ end
+
# Is this an authenticated request?
def authenticated?
# Double negative, so we just get true or false
@@ -61,9 +110,11 @@ class Puppet::Indirector::Request
self.indirection_name = indirection_name
self.method = method
+ options = options.inject({}) { |hash, ary| hash[ary[0].to_sym] = ary[1]; hash }
+
set_attributes(options)
- @options = options.inject({}) { |hash, ary| hash[ary[0].to_sym] = ary[1]; hash }
+ @options = options
if key_or_instance.is_a?(String) || key_or_instance.is_a?(Symbol)
key = key_or_instance
@@ -76,7 +127,9 @@ class Puppet::Indirector::Request
# because it rewrites the key. We could otherwise strip server/port/etc
# info out in the REST class, but it seemed bad design for the REST
# class to rewrite the key.
- if key.to_s =~ /^\w+:\/\// # it's a URI
+ if key.to_s =~ /^[a-z]:[\/\\]/i # It's an absolute path for Windows.
+ @key = key
+ elsif key.to_s =~ /^\w+:\/\// # it's a URI
set_uri_key(key)
else
@key = key
@@ -153,7 +206,7 @@ class Puppet::Indirector::Request
def set_attributes(options)
OPTION_ATTRIBUTES.each do |attribute|
- if options.include?(attribute)
+ if options.include?(attribute.to_sym)
send(attribute.to_s + "=", options[attribute])
options.delete(attribute)
end
diff --git a/lib/puppet/indirector/rest.rb b/lib/puppet/indirector/rest.rb
index 0d3997221..19daff51d 100644
--- a/lib/puppet/indirector/rest.rb
+++ b/lib/puppet/indirector/rest.rb
@@ -71,16 +71,51 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus
Puppet::Network::HttpPool.http_instance(request.server || self.class.server, request.port || self.class.port)
end
+ [:get, :post, :head, :delete, :put].each do |method|
+ define_method "http_#{method}" do |request, *args|
+ http_request(method, request, *args)
+ end
+ end
+
+ def http_request(method, request, *args)
+ http_connection = network(request)
+ peer_certs = []
+
+ # We add the callback to collect the certificates for use in constructing
+ # the error message if the verification failed. This is necessary since we
+ # don't have direct access to the cert that we expected the connection to
+ # use otherwise.
+ #
+ http_connection.verify_callback = proc do |preverify_ok, ssl_context|
+ peer_certs << Puppet::SSL::Certificate.from_s(ssl_context.current_cert.to_pem)
+ preverify_ok
+ end
+
+ http_connection.send(method, *args)
+ rescue OpenSSL::SSL::SSLError => error
+ if error.message.include? "certificate verify failed"
+ raise Puppet::Error, "#{error.message}. This is often because the time is out of sync on the server or client"
+ elsif error.message.include? "hostname was not match"
+ raise unless cert = peer_certs.find { |c| c.name !~ /^puppet ca/i }
+
+ valid_certnames = [cert.name, *cert.alternate_names].uniq
+ msg = valid_certnames.length > 1 ? "one of #{valid_certnames.join(', ')}" : valid_certnames.first
+
+ raise Puppet::Error, "Server hostname '#{http_connection.address}' did not match server certificate; expected #{msg}"
+ else
+ raise
+ end
+ end
+
def find(request)
uri, body = request_to_uri_and_body(request)
uri_with_query_string = "#{uri}?#{body}"
- http_connection = network(request)
# WEBrick in Ruby 1.9.1 only supports up to 1024 character lines in an HTTP request
# http://redmine.ruby-lang.org/issues/show/3991
response = if "GET #{uri_with_query_string} HTTP/1.1\r\n".length > 1024
- http_connection.post(uri, body, headers)
+ http_post(request, uri, body, headers)
else
- http_connection.get(uri_with_query_string, headers)
+ http_get(request, uri_with_query_string, headers)
end
result = deserialize response
result.name = request.key if result.respond_to?(:name=)
@@ -88,7 +123,7 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus
end
def head(request)
- response = network(request).head(indirection2uri(request), headers)
+ response = http_head(request, indirection2uri(request), headers)
case response.code
when "404"
return false
@@ -101,7 +136,7 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus
end
def search(request)
- unless result = deserialize(network(request).get(indirection2uri(request), headers), true)
+ unless result = deserialize(http_get(request, indirection2uri(request), headers), true)
return []
end
result
@@ -109,12 +144,12 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus
def destroy(request)
raise ArgumentError, "DELETE does not accept options" unless request.options.empty?
- deserialize network(request).delete(indirection2uri(request), headers)
+ deserialize http_delete(request, indirection2uri(request), headers)
end
def save(request)
raise ArgumentError, "PUT does not accept options" unless request.options.empty?
- deserialize network(request).put(indirection2uri(request), request.instance.render, headers.merge({ "Content-Type" => request.instance.mime }))
+ deserialize http_put(request, indirection2uri(request), request.instance.render, headers.merge({ "Content-Type" => request.instance.mime }))
end
private
diff --git a/lib/puppet/interface.rb b/lib/puppet/interface.rb
index 6be8b6930..eba99d6be 100644
--- a/lib/puppet/interface.rb
+++ b/lib/puppet/interface.rb
@@ -2,6 +2,7 @@ require 'puppet'
require 'puppet/util/autoload'
require 'puppet/interface/documentation'
require 'prettyprint'
+require 'semver'
class Puppet::Interface
include FullDocs
@@ -63,6 +64,10 @@ class Puppet::Interface
end
face
end
+
+ def find_action(name, action, version = :current)
+ Puppet::Interface::FaceCollection.get_action_for_face(name, action, version)
+ end
end
def set_default_format(format)
@@ -84,12 +89,12 @@ class Puppet::Interface
attr_reader :name, :version
def initialize(name, version, &block)
- unless Puppet::Interface::FaceCollection.validate_version(version)
+ unless SemVer.valid?(version)
raise ArgumentError, "Cannot create face #{name.inspect} with invalid version number '#{version}'!"
end
@name = Puppet::Interface::FaceCollection.underscorize(name)
- @version = version
+ @version = SemVer.new(version)
# The few bits of documentation we actually demand. The default license
# is a favour to our end users; if you happen to get that in a core face
diff --git a/lib/puppet/interface/action.rb b/lib/puppet/interface/action.rb
index 185302b07..bd47a36ea 100644
--- a/lib/puppet/interface/action.rb
+++ b/lib/puppet/interface/action.rb
@@ -38,6 +38,7 @@ class Puppet::Interface::Action
def to_s() "#{@face}##{@name}" end
attr_reader :name
+ attr_reader :face
attr_accessor :default
def default?
!!@default
@@ -195,14 +196,12 @@ class Puppet::Interface::Action
wrapper = <<WRAPPER
def #{@name}(#{decl.join(", ")})
#{optn}
- args = #{args}
- options = args.last
-
- action = get_action(#{name.inspect})
- action.validate_args(args)
- __invoke_decorations(:before, action, args, options)
+ args = #{args}
+ action = get_action(#{name.inspect})
+ args << action.validate_and_clean(args.pop)
+ __invoke_decorations(:before, action, args, args.last)
rval = self.__send__(#{internal_name.inspect}, *args)
- __invoke_decorations(:after, action, args, options)
+ __invoke_decorations(:after, action, args, args.last)
return rval
end
WRAPPER
@@ -227,8 +226,9 @@ WRAPPER
end
end
+ @options << option.name
+
option.aliases.each do |name|
- @options << name
@options_hash[name] = option
end
@@ -251,27 +251,59 @@ WRAPPER
option
end
- def validate_args(args)
- # Check for multiple aliases for the same option...
- args.last.keys.each do |name|
- # #7290: If this isn't actually an option, ignore it for now. We should
- # probably fail, but that wasn't our API, and I don't want to perturb
- # behaviour this late in the RC cycle. --daniel 2011-04-29
+ def validate_and_clean(original)
+ # The final set of arguments; effectively a hand-rolled shallow copy of
+ # the original, which protects the caller from the surprises they might
+ # get if they passed us a hash and we mutated it...
+ result = {}
+
+ # Check for multiple aliases for the same option, and canonicalize the
+ # name of the argument while we are about it.
+ overlap = Hash.new do |h, k| h[k] = [] end
+ unknown = []
+ original.keys.each do |name|
if option = get_option(name) then
- overlap = (option.aliases & args.last.keys)
- unless overlap.length == 1 then
- raise ArgumentError, "Multiple aliases for the same option passed: #{overlap.join(', ')}"
+ canonical = option.name
+ if result.has_key? canonical
+ overlap[canonical] << name
+ else
+ result[canonical] = original[name]
end
+ else
+ unknown << name
+ end
+ end
+
+ unless overlap.empty?
+ msg = overlap.map {|k, v| "(#{k}, #{v.sort.join(', ')})" }.join(", ")
+ raise ArgumentError, "Multiple aliases for the same option passed: #{msg}"
+ end
+
+ unless unknown.empty?
+ msg = unknown.sort.join(", ")
+ raise ArgumentError, "Unknown options passed: #{msg}"
+ end
+
+ # Inject default arguments and check for missing mandating options.
+ missing = []
+ options.map {|x| get_option(x) }.each do |option|
+ name = option.name
+ next if result.has_key? name
+
+ if option.has_default?
+ result[name] = option.default
+ elsif option.required?
+ missing << name
end
end
- # Check for missing mandatory options.
- required = options.map do |name|
- get_option(name)
- end.select(&:required?).collect(&:name) - args.last.keys
+ unless missing.empty?
+ msg = missing.sort.join(', ')
+ raise ArgumentError, "The following options are required: #{msg}"
+ end
- return if required.empty?
- raise ArgumentError, "The following options are required: #{required.join(', ')}"
+ # All done.
+ return result
end
########################################################################
diff --git a/lib/puppet/interface/action_manager.rb b/lib/puppet/interface/action_manager.rb
index fbf588d7d..5c9af4f96 100644
--- a/lib/puppet/interface/action_manager.rb
+++ b/lib/puppet/interface/action_manager.rb
@@ -7,13 +7,14 @@ module Puppet::Interface::ActionManager
require 'puppet/interface/action_builder'
@actions ||= {}
- @default_action ||= nil
raise "Action #{name} already defined for #{self}" if action?(name)
+
action = Puppet::Interface::ActionBuilder.build(self, name, &block)
- if action.default
- raise "Actions #{@default_action.name} and #{name} cannot both be default" if @default_action
- @default_action = action
+
+ if action.default and current = get_default_action
+ raise "Actions #{current.name} and #{name} cannot both be default"
end
+
@actions[action.name] = action
end
@@ -61,7 +62,11 @@ module Puppet::Interface::ActionManager
end
def get_default_action
- @default_action
+ default = actions.map {|x| get_action(x) }.select {|x| x.default }
+ if default.length > 1
+ raise "The actions #{default.map(&:name).join(", ")} cannot all be default"
+ end
+ default.first
end
def action?(name)
diff --git a/lib/puppet/interface/face_collection.rb b/lib/puppet/interface/face_collection.rb
index 12d3c56b1..b1f6ba398 100644
--- a/lib/puppet/interface/face_collection.rb
+++ b/lib/puppet/interface/face_collection.rb
@@ -1,8 +1,6 @@
require 'puppet/interface'
module Puppet::Interface::FaceCollection
- SEMVER_VERSION = /^(\d+)\.(\d+)\.(\d+)([A-Za-z][0-9A-Za-z-]*|)$/
-
@faces = Hash.new { |hash, key| hash[key] = {} }
def self.faces
@@ -17,55 +15,36 @@ module Puppet::Interface::FaceCollection
@faces.keys.select {|name| @faces[name].length > 0 }
end
- def self.validate_version(version)
- !!(SEMVER_VERSION =~ version.to_s)
- end
-
- def self.semver_to_array(v)
- parts = SEMVER_VERSION.match(v).to_a[1..4]
- parts[0..2] = parts[0..2].map { |e| e.to_i }
- parts
- end
-
- def self.cmp_semver(a, b)
- a, b = [a, b].map do |x| semver_to_array(x) end
-
- cmp = a[0..2] <=> b[0..2]
- if cmp == 0
- cmp = a[3] <=> b[3]
- cmp = +1 if a[3].empty? && !b[3].empty?
- cmp = -1 if b[3].empty? && !a[3].empty?
- end
- cmp
+ def self.[](name, version)
+ name = underscorize(name)
+ get_face(name, version) or load_face(name, version)
end
- def self.prefix_match?(desired, target)
- # Can't meaningfully do a prefix match with current on either side.
- return false if desired == :current
- return false if target == :current
-
- # REVISIT: Should probably fail if the matcher is not valid.
- prefix = desired.split('.').map {|x| x =~ /^\d+$/ and x.to_i }
- have = semver_to_array(target)
+ def self.get_action_for_face(name, action_name, version)
+ name = underscorize(name)
- while want = prefix.shift do
- return false unless want == have.shift
+ # If the version they request specifically doesn't exist, don't search
+ # elsewhere. Usually this will start from :current and all...
+ return nil unless face = self[name, version]
+ unless action = face.get_action(action_name)
+ # ...we need to search for it bound to an o{lder,ther} version. Since
+ # we load all actions when the face is first references, this will be in
+ # memory in the known set of versions of the face.
+ (@faces[name].keys - [ :current ]).sort.reverse.each do |version|
+ break if action = @faces[name][version].get_action(action_name)
+ end
end
- return true
- end
- def self.[](name, version)
- name = underscorize(name)
- get_face(name, version) or load_face(name, version)
+ return action
end
# get face from memory, without loading.
- def self.get_face(name, desired_version)
+ def self.get_face(name, pattern)
return nil unless @faces.has_key? name
+ return @faces[name][:current] if pattern == :current
- return @faces[name][:current] if desired_version == :current
-
- found = @faces[name].keys.select {|v| prefix_match?(desired_version, v) }.sort.last
+ versions = @faces[name].keys - [ :current ]
+ found = SemVer.find_matching(pattern, versions)
return @faces[name][found]
end
@@ -77,9 +56,7 @@ module Puppet::Interface::FaceCollection
#
# We use require to avoid executing the code multiple times, like any
# other Ruby library that we might want to use. --daniel 2011-04-06
- begin
- require "puppet/face/#{name}"
-
+ if safely_require name then
# If we wanted :current, we need to index to find that; direct version
# requests just work™ as they go. --daniel 2011-04-06
if version == :current then
@@ -108,21 +85,35 @@ module Puppet::Interface::FaceCollection
# versions here and return the last item in that set.
#
# --daniel 2011-04-06
- latest_ver = @faces[name].keys.sort {|a, b| cmp_semver(a, b) }.last
+ latest_ver = @faces[name].keys.sort.last
@faces[name][:current] = @faces[name][latest_ver]
end
- rescue LoadError => e
- raise unless e.message =~ %r{-- puppet/face/#{name}$}
- # ...guess we didn't find the file; return a much better problem.
- rescue SyntaxError => e
- raise unless e.message =~ %r{puppet/face/#{name}\.rb:\d+: }
- Puppet.err "Failed to load face #{name}:\n#{e}"
- # ...but we just carry on after complaining.
+ end
+
+ unless version == :current or get_face(name, version)
+ # Try an obsolete version of the face, if needed, to see if that helps?
+ safely_require name, version
end
return get_face(name, version)
end
+ def self.safely_require(name, version = nil)
+ path = File.join 'puppet' ,'face', version.to_s, name.to_s
+ require path
+ true
+
+ rescue LoadError => e
+ raise unless e.message =~ %r{-- #{path}$}
+ # ...guess we didn't find the file; return a much better problem.
+ nil
+ rescue SyntaxError => e
+ raise unless e.message =~ %r{#{path}\.rb:\d+: }
+ Puppet.err "Failed to load face #{name}:\n#{e}"
+ # ...but we just carry on after complaining.
+ nil
+ end
+
def self.register(face)
@faces[underscorize(face.name)][face.version] = face
end
diff --git a/lib/puppet/interface/option.rb b/lib/puppet/interface/option.rb
index b68bdeb12..01f6f2307 100644
--- a/lib/puppet/interface/option.rb
+++ b/lib/puppet/interface/option.rb
@@ -2,12 +2,11 @@ require 'puppet/interface'
class Puppet::Interface::Option
include Puppet::Interface::TinyDocs
- # For compatibility, deprecated, and should go fairly soon...
- ['', '='].each { |x| alias :"desc#{x}" :"description#{x}" }
def initialize(parent, *declaration, &block)
@parent = parent
@optparse = []
+ @default = nil
# Collect and sort the arguments in the declaration.
dups = {}
@@ -83,8 +82,26 @@ class Puppet::Interface::Option
!!@required
end
+ def has_default?
+ !!@default
+ end
+
+ def default=(proc)
+ required and raise ArgumentError, "#{self} can't be optional and have a default value"
+ proc.is_a? Proc or raise ArgumentError, "default value for #{self} is a #{proc.class.name.inspect}, not a proc"
+ @default = proc
+ end
+
+ def default
+ @default and @default.call
+ end
+
attr_reader :parent, :name, :aliases, :optparse
attr_accessor :required
+ def required=(value)
+ has_default? and raise ArgumentError, "#{self} can't be optional and have a default value"
+ @required = value
+ end
attr_accessor :before_action
def before_action=(proc)
diff --git a/lib/puppet/interface/option_builder.rb b/lib/puppet/interface/option_builder.rb
index 5676ec977..c87adc2c0 100644
--- a/lib/puppet/interface/option_builder.rb
+++ b/lib/puppet/interface/option_builder.rb
@@ -51,4 +51,17 @@ class Puppet::Interface::OptionBuilder
def required(value = true)
@option.required = value
end
+
+ def default_to(&block)
+ block or raise ArgumentError, "#{@option} default_to requires a block"
+ if @option.has_default?
+ raise ArgumentError, "#{@option} already has a default value"
+ end
+ # Ruby 1.8 treats a block without arguments as accepting any number; 1.9
+ # gets this right, so we work around it for now... --daniel 2011-07-20
+ unless block.arity == 0 or (RUBY_VERSION =~ /^1\.8/ and block.arity == -1)
+ raise ArgumentError, "#{@option} default_to block should not take any arguments"
+ end
+ @option.default = block
+ end
end
diff --git a/lib/puppet/interface/option_manager.rb b/lib/puppet/interface/option_manager.rb
index 326a91d92..a1f300e8e 100644
--- a/lib/puppet/interface/option_manager.rb
+++ b/lib/puppet/interface/option_manager.rb
@@ -26,8 +26,9 @@ module Puppet::Interface::OptionManager
end
end
+ @options << option.name
+
option.aliases.each do |name|
- @options << name
@options_hash[name] = option
end
diff --git a/lib/puppet/module.rb b/lib/puppet/module.rb
index 059591ed8..00468df96 100644
--- a/lib/puppet/module.rb
+++ b/lib/puppet/module.rb
@@ -42,7 +42,10 @@ class Puppet::Module
def has_metadata?
return false unless metadata_file
- FileTest.exist?(metadata_file)
+ return false unless FileTest.exist?(metadata_file)
+
+ metadata = PSON.parse File.read(metadata_file)
+ return metadata.is_a?(Hash) && !metadata.keys.empty?
end
def initialize(name, environment = nil)
diff --git a/lib/puppet/network/authconfig.rb b/lib/puppet/network/authconfig.rb
index 4ba89fa71..1e486a2f9 100644
--- a/lib/puppet/network/authconfig.rb
+++ b/lib/puppet/network/authconfig.rb
@@ -102,7 +102,7 @@ module Puppet
name = $3 if $2 == "path"
name.chomp!
right = newrights.newright(name, count, @file)
- when /^\s*(allow|deny|method|environment|auth(?:enticated)?)\s+(.+)$/
+ when /^\s*(allow|deny|method|environment|auth(?:enticated)?)\s+(.+?)(\s*#.*)?$/
parse_right_directive(right, $1, $2, count)
else
raise ConfigurationError, "Invalid line #{count}: #{line}"
@@ -130,6 +130,7 @@ module Puppet
end
def parse_right_directive(right, var, value, count)
+ value.strip!
case var
when "allow"
modify_right(right, :allow, value, "allowing %s access", count)
@@ -159,6 +160,7 @@ module Puppet
def modify_right(right, method, value, msg, count)
value.split(/\s*,\s*/).each do |val|
begin
+ val.strip!
right.info msg % val
right.send(method, val)
rescue AuthStoreError => detail
diff --git a/lib/puppet/network/client.rb b/lib/puppet/network/client.rb
index c56b21393..f9c4c5fea 100644
--- a/lib/puppet/network/client.rb
+++ b/lib/puppet/network/client.rb
@@ -82,11 +82,6 @@ class Puppet::Network::Client
self.read_cert
- # We have to start the HTTP connection manually before we start
- # sending it requests or keep-alive won't work. Note that with #1010,
- # we don't currently actually want keep-alive.
- @driver.start if @driver.respond_to? :start and Puppet::Network::HttpPool.keep_alive?
-
@local = false
elsif hash.include?(driverparam)
@driver = hash[driverparam]
diff --git a/lib/puppet/network/http_pool.rb b/lib/puppet/network/http_pool.rb
index 7d227b4d4..8baf48c77 100644
--- a/lib/puppet/network/http_pool.rb
+++ b/lib/puppet/network/http_pool.rb
@@ -1,53 +1,14 @@
require 'puppet/ssl/host'
require 'net/https'
-require 'puppet/util/cacher'
module Puppet::Network; end
-# Manage Net::HTTP instances for keep-alive.
module Puppet::Network::HttpPool
- class << self
- include Puppet::Util::Cacher
-
- private
-
- cached_attr(:http_cache) { Hash.new }
- end
-
# Use the global localhost instance.
def self.ssl_host
Puppet::SSL::Host.localhost
end
- # 2008/03/23
- # LAK:WARNING: Enabling this has a high propability of
- # causing corrupt files and who knows what else. See #1010.
- HTTP_KEEP_ALIVE = false
-
- def self.keep_alive?
- HTTP_KEEP_ALIVE
- end
-
- # Clear our http cache, closing all connections.
- def self.clear_http_instances
- http_cache.each do |name, connection|
- connection.finish if connection.started?
- end
- Puppet::Util::Cacher.expire
- end
-
- # Make sure we set the driver up when we read the cert in.
- def self.read_cert
- if val = super # This calls read_cert from the Puppet::SSLCertificates::Support module.
- # Clear out all of our connections, since they previously had no cert and now they
- # should have them.
- clear_http_instances
- return val
- else
- return false
- end
- end
-
# 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.
@@ -63,21 +24,6 @@ module Puppet::Network::HttpPool
# Retrieve a cached http instance if caching is enabled, else return
# a new one.
def self.http_instance(host, port, reset = false)
- # We overwrite the uninitialized @http here with a cached one.
- key = "#{host}:#{port}"
-
- # 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]
-
- # Clean up old connections if we have them.
- if http = http_cache[key]
- http_cache.delete(key)
- http.finish if http.started?
- end
- end
-
args = [host, port]
if Puppet[:http_proxy_host] == "none"
args << nil << nil
@@ -97,8 +43,6 @@ module Puppet::Network::HttpPool
cert_setup(http)
- http_cache[key] = http if keep_alive?
-
http
end
end
diff --git a/lib/puppet/network/rest_authconfig.rb b/lib/puppet/network/rest_authconfig.rb
index dfe8f85c4..7dcc81ef4 100644
--- a/lib/puppet/network/rest_authconfig.rb
+++ b/lib/puppet/network/rest_authconfig.rb
@@ -29,10 +29,15 @@ module Puppet
@main
end
+ def allowed?(request)
+ Puppet.deprecation_warning "allowed? should not be called for REST authorization - use check_authorization instead"
+ check_authorization(request)
+ end
+
# check wether this request is allowed in our ACL
# raise an Puppet::Network::AuthorizedError if the request
# is denied.
- def allowed?(indirection, method, key, params)
+ def check_authorization(indirection, method, key, params)
read
# we're splitting the request in part because
diff --git a/lib/puppet/network/rest_authorization.rb b/lib/puppet/network/rest_authorization.rb
index 50f094e3e..d636d486a 100644
--- a/lib/puppet/network/rest_authorization.rb
+++ b/lib/puppet/network/rest_authorization.rb
@@ -16,7 +16,7 @@ module Puppet::Network
# Verify that our client has access.
def check_authorization(indirection, method, key, params)
- authconfig.allowed?(indirection, method, key, params)
+ authconfig.check_authorization(indirection, method, key, params)
end
end
end
diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb
index 5b0a98615..16a0e5c3d 100644
--- a/lib/puppet/node.rb
+++ b/lib/puppet/node.rb
@@ -19,6 +19,32 @@ class Puppet::Node
attr_accessor :name, :classes, :source, :ipaddress, :parameters
attr_reader :time
+ #
+ # Load json before trying to register.
+ Puppet.features.pson? and ::PSON.register_document_type('Node',self)
+
+ def self.from_pson(pson)
+ raise ArgumentError, "No name provided in pson data" unless name = pson['name']
+
+ node = new(name)
+ node.classes = pson['classes']
+ node.parameters = pson['parameters']
+ node.environment = pson['environment']
+ node
+ end
+
+ def to_pson(*args)
+ result = {
+ 'document_type' => "Node",
+ 'data' => {}
+ }
+ result['data']['name'] = name
+ result['data']['classes'] = classes unless classes.empty?
+ result['data']['parameters'] = parameters unless parameters.empty?
+ result['data']['environment'] = environment.name
+
+ result.to_pson(*args)
+ end
def environment
return super if @environment
diff --git a/lib/puppet/node/environment.rb b/lib/puppet/node/environment.rb
index dc631979e..4fc314a6a 100644
--- a/lib/puppet/node/environment.rb
+++ b/lib/puppet/node/environment.rb
@@ -95,7 +95,7 @@ class Puppet::Node::Environment
# Cache the modulepath, so that we aren't searching through
# all known directories all the time.
- cached_attr(:modulepath, :ttl => Puppet[:filetimeout]) do
+ cached_attr(:modulepath, Puppet[:filetimeout]) do
dirs = self[:modulepath].split(File::PATH_SEPARATOR)
dirs = ENV["PUPPETLIB"].split(File::PATH_SEPARATOR) + dirs if ENV["PUPPETLIB"]
validate_dirs(dirs)
@@ -103,7 +103,7 @@ class Puppet::Node::Environment
# Return all modules from this environment.
# Cache the list, because it can be expensive to create.
- cached_attr(:modules, :ttl => Puppet[:filetimeout]) do
+ cached_attr(:modules, Puppet[:filetimeout]) do
module_names = modulepath.collect { |path| Dir.entries(path) }.flatten.uniq
module_names.collect do |path|
begin
@@ -114,12 +114,6 @@ class Puppet::Node::Environment
end.compact
end
- # Cache the manifestdir, so that we aren't searching through
- # all known directories all the time.
- cached_attr(:manifestdir, :ttl => Puppet[:filetimeout]) do
- validate_dirs(self[:manifestdir].split(File::PATH_SEPARATOR))
- end
-
def to_s
name.to_s
end
@@ -136,14 +130,18 @@ class Puppet::Node::Environment
end
def validate_dirs(dirs)
+ dir_regex = Puppet.features.microsoft_windows? ? /^[A-Za-z]:#{File::SEPARATOR}/ : /^#{File::SEPARATOR}/
+ # REMIND: Dir.getwd on windows returns a path containing backslashes, which when joined with
+ # dir containing forward slashes, breaks our regex matching. In general, path validation needs
+ # to be refactored which will be handled in a future commit.
dirs.collect do |dir|
- if dir !~ /^#{File::SEPARATOR}/
- File.join(Dir.getwd, dir)
+ if dir !~ dir_regex
+ File.expand_path(File.join(Dir.getwd, dir))
else
dir
end
end.find_all do |p|
- p =~ /^#{File::SEPARATOR}/ && FileTest.directory?(p)
+ p =~ dir_regex && FileTest.directory?(p)
end
end
diff --git a/lib/puppet/node/facts.rb b/lib/puppet/node/facts.rb
index 577b62b62..8d0a03474 100755
--- a/lib/puppet/node/facts.rb
+++ b/lib/puppet/node/facts.rb
@@ -61,18 +61,21 @@ class Puppet::Node::Facts
def self.from_pson(data)
result = new(data['name'], data['values'])
- result.timestamp = Time.parse(data['timestamp'])
- result.expiration = Time.parse(data['expiration'])
+ result.timestamp = Time.parse(data['timestamp']) if data['timestamp']
+ result.expiration = Time.parse(data['expiration']) if data['expiration']
result
end
def to_pson(*args)
- {
- 'expiration' => expiration,
+ result = {
'name' => name,
- 'timestamp' => timestamp,
'values' => strip_internal,
- }.to_pson(*args)
+ }
+
+ result['timestamp'] = timestamp if timestamp
+ result['expiration'] = expiration if expiration
+
+ result.to_pson(*args)
end
# Add internal data to the facts for storage.
diff --git a/lib/puppet/parameter.rb b/lib/puppet/parameter.rb
index 29d60fc66..c97f93b23 100644
--- a/lib/puppet/parameter.rb
+++ b/lib/puppet/parameter.rb
@@ -2,7 +2,6 @@ require 'puppet/util/methodhelper'
require 'puppet/util/log_paths'
require 'puppet/util/logging'
require 'puppet/util/docs'
-require 'puppet/util/cacher'
class Puppet::Parameter
include Puppet::Util
@@ -10,7 +9,6 @@ class Puppet::Parameter
include Puppet::Util::LogPaths
include Puppet::Util::Logging
include Puppet::Util::MethodHelper
- include Puppet::Util::Cacher
require 'puppet/parameter/value_collection'
@@ -150,10 +148,6 @@ class Puppet::Parameter
self.fail(Puppet::DevError, msg)
end
- def expirer
- resource.catalog
- end
-
def fail(*args)
type = nil
if args[0].is_a?(Class)
diff --git a/lib/puppet/parser/ast/leaf.rb b/lib/puppet/parser/ast/leaf.rb
index c8ebc9483..3efb52f63 100644
--- a/lib/puppet/parser/ast/leaf.rb
+++ b/lib/puppet/parser/ast/leaf.rb
@@ -124,10 +124,11 @@ class Puppet::Parser::AST
# not include syntactical constructs, like '$' and '{}').
def evaluate(scope)
parsewrap do
- if (var = scope.lookupvar(@value, :file => file, :line => line)) == :undefined
- var = :undef
+ if ! scope.include?(@value)
+ :undef
+ else
+ scope[@value, {:file => file, :line => line}]
end
- var
end
end
@@ -141,7 +142,7 @@ class Puppet::Parser::AST
def evaluate_container(scope)
container = variable.respond_to?(:evaluate) ? variable.safeevaluate(scope) : variable
- (container.is_a?(Hash) or container.is_a?(Array)) ? container : scope.lookupvar(container, :file => file, :line => line)
+ (container.is_a?(Hash) or container.is_a?(Array)) ? container : scope[container, {:file => file, :line => line}]
end
def evaluate_key(scope)
diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/compiler.rb
index c1daade4c..06cd80a1e 100644
--- a/lib/puppet/parser/compiler.rb
+++ b/lib/puppet/parser/compiler.rb
@@ -139,19 +139,21 @@ class Puppet::Parser::Compiler
# evaluated later in the process.
def evaluate_classes(classes, scope, lazy_evaluate = true)
raise Puppet::DevError, "No source for scope passed to evaluate_classes" unless scope.source
- param_classes = nil
+ class_parameters = nil
# if we are a param class, save the classes hash
# and transform classes to be the keys
if classes.class == Hash
- param_classes = classes
+ class_parameters = classes
classes = classes.keys
end
classes.each do |name|
# If we can find the class, then make a resource that will evaluate it.
if klass = scope.find_hostclass(name)
- if param_classes
- resource = klass.ensure_in_catalog(scope, param_classes[name] || {})
+ # If parameters are passed, then attempt to create a duplicate resource
+ # so the appropriate error is thrown.
+ if class_parameters
+ resource = klass.ensure_in_catalog(scope, class_parameters[name] || {})
else
next if scope.class_scope(klass)
resource = klass.ensure_in_catalog(scope)
@@ -450,7 +452,7 @@ class Puppet::Parser::Compiler
# Set the node's parameters into the top-scope as variables.
def set_node_parameters
node.parameters.each do |param, value|
- @topscope.setvar(param, value)
+ @topscope[param] = value
end
# These might be nil.
@@ -473,7 +475,7 @@ class Puppet::Parser::Compiler
Puppet.settings.each do |name, setting|
next if name.to_s == "name"
- scope.setvar name.to_s, environment[name]
+ scope[name.to_s] = environment[name]
end
end
diff --git a/lib/puppet/parser/functions.rb b/lib/puppet/parser/functions.rb
index 5807c0bbe..22eee70d7 100644
--- a/lib/puppet/parser/functions.rb
+++ b/lib/puppet/parser/functions.rb
@@ -16,11 +16,9 @@ module Puppet::Parser::Functions
def self.autoloader
unless defined?(@autoloader)
-
- @autoloader = Puppet::Util::Autoload.new(
+ @autoloader = Puppet::Util::Autoload.new(
self,
"puppet/parser/functions",
-
:wrap => false
)
end
@@ -31,8 +29,11 @@ module Puppet::Parser::Functions
Environment = Puppet::Node::Environment
def self.environment_module(env = nil)
+ if env and ! env.is_a?(Puppet::Node::Environment)
+ env = Puppet::Node::Environment.new(env)
+ end
@modules.synchronize {
- @modules[ env || Environment.current || Environment.root ] ||= Module.new
+ @modules[ (env || Environment.current || Environment.root).name ] ||= Module.new
}
end
@@ -88,7 +89,6 @@ module Puppet::Parser::Functions
ret = ""
functions.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, hash|
- #ret += "#{name}\n#{hash[:type]}\n"
ret += "#{name}\n#{"-" * name.to_s.length}\n"
if hash[:doc]
ret += Puppet::Util::Docs.scrub(hash[:doc])
@@ -114,11 +114,9 @@ module Puppet::Parser::Functions
end
# Runs a newfunction to create a function for each of the log levels
-
Puppet::Util::Log.levels.each do |level|
newfunction(level, :doc => "Log a message on the server at level #{level.to_s}.") do |vals|
send(level, vals.join(" "))
end
end
-
end
diff --git a/lib/puppet/parser/functions/create_resources.rb b/lib/puppet/parser/functions/create_resources.rb
index 430f110b4..3b8bb3543 100644
--- a/lib/puppet/parser/functions/create_resources.rb
+++ b/lib/puppet/parser/functions/create_resources.rb
@@ -27,15 +27,16 @@ Takes two parameters:
args[1].each do |title, params|
raise ArgumentError, 'params should not contain title' if(params['title'])
case type_of_resource
- when :type
- res = resource.hash2resource(params.merge(:title => title))
- catalog.add_resource(res)
- when :define
+ # JJM The only difference between a type and a define is the call to instantiate_resource
+ # for a defined type.
+ when :type, :define
p_resource = Puppet::Parser::Resource.new(type_name, title, :scope => self, :source => resource)
params.merge(:name => title).each do |k,v|
p_resource.set_parameter(k,v)
end
- resource.instantiate_resource(self, p_resource)
+ if type_of_resource == :define then
+ resource.instantiate_resource(self, p_resource)
+ end
compiler.add_resource(self, p_resource)
when :class
klass = find_hostclass(title)
diff --git a/lib/puppet/parser/functions/extlookup.rb b/lib/puppet/parser/functions/extlookup.rb
index 5fbf26cec..9ffca59a7 100644
--- a/lib/puppet/parser/functions/extlookup.rb
+++ b/lib/puppet/parser/functions/extlookup.rb
@@ -91,9 +91,9 @@ This is for back compatibility to interpolate variables with %. % interpolation
raise Puppet::ParseError, ("extlookup(): wrong number of arguments (#{args.length}; must be <= 3)") if args.length > 3
- extlookup_datadir = undef_as('',lookupvar('::extlookup_datadir'))
+ extlookup_datadir = undef_as('',self['::extlookup_datadir'])
- extlookup_precedence = undef_as([],lookupvar('::extlookup_precedence')).collect { |var| var.gsub(/%\{(.+?)\}/) { lookupvar("::#{$1}") } }
+ extlookup_precedence = undef_as([],self['::extlookup_precedence']).collect { |var| var.gsub(/%\{(.+?)\}/) { self["::#{$1}"] } }
datafiles = Array.new
@@ -121,9 +121,9 @@ This is for back compatibility to interpolate variables with %. % interpolation
if result[0].length == 2
val = result[0][1].to_s
- # parse %{}'s in the CSV into local variables using lookupvar()
+ # parse %{}'s in the CSV into local variables using the current scope
while val =~ /%\{(.+?)\}/
- val.gsub!(/%\{#{$1}\}/, lookupvar($1))
+ val.gsub!(/%\{#{$1}\}/, self[$1])
end
desired = val
@@ -134,9 +134,9 @@ This is for back compatibility to interpolate variables with %. % interpolation
# Individual cells in a CSV result are a weird data type and throws
# puppets yaml parsing, so just map it all to plain old strings
desired = cells.map do |c|
- # parse %{}'s in the CSV into local variables using lookupvar()
+ # parse %{}'s in the CSV into local variables using the current scope
while c =~ /%\{(.+?)\}/
- c.gsub!(/%\{#{$1}\}/, lookupvar($1))
+ c.gsub!(/%\{#{$1}\}/, self[$1])
end
c.to_s
diff --git a/lib/puppet/parser/functions/fqdn_rand.rb b/lib/puppet/parser/functions/fqdn_rand.rb
index 93ab98bcd..668802e73 100644
--- a/lib/puppet/parser/functions/fqdn_rand.rb
+++ b/lib/puppet/parser/functions/fqdn_rand.rb
@@ -7,6 +7,6 @@ Puppet::Parser::Functions::newfunction(:fqdn_rand, :type => :rvalue, :doc =>
$random_number_seed = fqdn_rand(30,30)") do |args|
require 'digest/md5'
max = args.shift
- srand(Digest::MD5.hexdigest([lookupvar('::fqdn'),args].join(':')).hex)
+ srand(Digest::MD5.hexdigest([self['::fqdn'],args].join(':')).hex)
rand(max).to_s
end
diff --git a/lib/puppet/parser/functions/versioncmp.rb b/lib/puppet/parser/functions/versioncmp.rb
index 6091e0923..e4edb151e 100644
--- a/lib/puppet/parser/functions/versioncmp.rb
+++ b/lib/puppet/parser/functions/versioncmp.rb
@@ -1,10 +1,8 @@
require 'puppet/util/package'
- Puppet::Parser::Functions::newfunction(
- :versioncmp, :type => :rvalue,
-
- :doc => "Compares two versions
+Puppet::Parser::Functions::newfunction( :versioncmp, :type => :rvalue,
+:doc => "Compares two versions
Prototype:
diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb
index 3bb5f8601..56887c357 100644
--- a/lib/puppet/parser/resource.rb
+++ b/lib/puppet/parser/resource.rb
@@ -173,7 +173,7 @@ class Puppet::Parser::Resource < Puppet::Resource
:name => param, :value => value, :source => self.source
)
elsif ! param.is_a?(Puppet::Parser::Resource::Param)
- raise ArgumentError, "Must pass a parameter or all necessary values"
+ raise ArgumentError, "Received incomplete information - no value provided for parameter #{param}"
end
tag(*param.value) if param.name == :tag
@@ -258,7 +258,8 @@ class Puppet::Parser::Resource < Puppet::Resource
def add_backward_compatible_relationship_param(name)
# Skip metaparams for which we get no value.
- return unless val = scope.lookupvar(name.to_s) and val != :undefined
+ return unless scope.include?(name.to_s)
+ val = scope[name.to_s]
# The default case: just set the value
set_parameter(name, val) and return unless @parameters[name]
diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb
index ed67cd141..9d84c7e65 100644
--- a/lib/puppet/parser/scope.rb
+++ b/lib/puppet/parser/scope.rb
@@ -48,13 +48,42 @@ class Puppet::Parser::Scope
end
end
+ def [](name, options = {})
+ table = ephemeral?(name) ? @ephemeral.last : @symtable
+ # If the variable is qualified, then find the specified scope and look the variable up there instead.
+ if name =~ /^(.*)::(.+)$/
+ begin
+ qualified_scope($1)[$2,options]
+ rescue RuntimeError => e
+ location = (options[:file] && options[:line]) ? " at #{options[:file]}:#{options[:line]}" : ''
+ warning "Could not look up qualified variable '#{name}'; #{e.message}#{location}"
+ nil
+ end
+ elsif ephemeral_include?(name) or table.include?(name)
+ # We can't use "if table[name]" here because the value might be false
+ if options[:dynamic] and self != compiler.topscope
+ location = (options[:file] && options[:line]) ? " at #{options[:file]}:#{options[:line]}" : ''
+ Puppet.deprecation_warning "Dynamic lookup of $#{name}#{location} is deprecated. Support will be removed in Puppet 2.8. Use a fully-qualified variable name (e.g., $classname::variable) or parameterized classes."
+ end
+ table[name]
+ elsif parent
+ parent[name,options.merge(:dynamic => (dynamic || options[:dynamic]))]
+ else
+ nil
+ end
+ end
+
+ def []=(var, value)
+ setvar(var, value)
+ end
+
# A demeterific shortcut to the catalog.
def catalog
compiler.catalog
end
- def environment
- compiler.environment
+ def each
+ to_hash.each { |name, value| yield(name, value) }
end
# Proxy accessors
@@ -62,6 +91,10 @@ class Puppet::Parser::Scope
@compiler.node.name
end
+ def include?(name)
+ ! self[name].nil?
+ end
+
# Is the value true? This allows us to control the definition of truth
# in one place.
def self.true?(value)
@@ -101,7 +134,7 @@ class Puppet::Parser::Scope
# Remove this when rebasing
def environment
- compiler ? compiler.environment : nil
+ compiler ? compiler.environment : Puppet::Node::Environment.new
end
def find_hostclass(name)
@@ -211,7 +244,11 @@ class Puppet::Parser::Scope
end
def undef_as(x,v)
- (v == :undefined) ? x : (v == :undef) ? x : v
+ if v.nil? or v == :undef
+ x
+ else
+ v
+ end
end
def qualified_scope(classname)
@@ -223,29 +260,9 @@ class Puppet::Parser::Scope
private :qualified_scope
# Look up a variable. The simplest value search we do.
+ # This method is effectively deprecated - use self[] instead.
def lookupvar(name, options = {})
- table = ephemeral?(name) ? @ephemeral.last : @symtable
- # If the variable is qualified, then find the specified scope and look the variable up there instead.
- if name =~ /^(.*)::(.+)$/
- begin
- qualified_scope($1).lookupvar($2,options)
- rescue RuntimeError => e
- location = (options[:file] && options[:line]) ? " at #{options[:file]}:#{options[:line]}" : ''
- warning "Could not look up qualified variable '#{name}'; #{e.message}#{location}"
- :undefined
- end
- elsif ephemeral_include?(name) or table.include?(name)
- # We can't use "if table[name]" here because the value might be false
- if options[:dynamic] and self != compiler.topscope
- location = (options[:file] && options[:line]) ? " at #{options[:file]}:#{options[:line]}" : ''
- Puppet.deprecation_warning "Dynamic lookup of $#{name}#{location} is deprecated. Support will be removed in Puppet 2.8. Use a fully-qualified variable name (e.g., $classname::variable) or parameterized classes."
- end
- table[name]
- elsif parent
- parent.lookupvar(name,options.merge(:dynamic => (dynamic || options[:dynamic])))
- else
- :undefined
- end
+ self[name, options]
end
# Return a hash containing our variables and their values, optionally (and
@@ -312,6 +329,8 @@ class Puppet::Parser::Scope
# Set a variable in the current scope. This will override settings
# in scopes above, but will not allow variables in the current scope
# to be reassigned.
+ # It's preferred that you use self[]= instead of this; only use this
+ # when you need to set options.
def setvar(name,value, options = {})
table = options[:ephemeral] ? @ephemeral.last : @symtable
if table.include?(name)
@@ -329,7 +348,7 @@ class Puppet::Parser::Scope
table[name] = value
else # append case
# lookup the value in the scope if it exists and insert the var
- table[name] = undef_as('',lookupvar(name))
+ table[name] = undef_as('',self[name])
# concatenate if string, append if array, nothing for other types
case value
when Array
@@ -443,6 +462,6 @@ class Puppet::Parser::Scope
def extend_with_functions_module
extend Puppet::Parser::Functions.environment_module(Puppet::Node::Environment.root)
- extend Puppet::Parser::Functions.environment_module(environment)
+ extend Puppet::Parser::Functions.environment_module(environment) if environment != Puppet::Node::Environment.root
end
end
diff --git a/lib/puppet/parser/templatewrapper.rb b/lib/puppet/parser/templatewrapper.rb
index 27d75bf92..9336e704d 100644
--- a/lib/puppet/parser/templatewrapper.rb
+++ b/lib/puppet/parser/templatewrapper.rb
@@ -25,7 +25,7 @@ class Puppet::Parser::TemplateWrapper
# Should return true if a variable is defined, false if it is not
def has_variable?(name)
- scope.lookupvar(name.to_s, :file => file, :line => script_line) != :undefined
+ scope.include?(name.to_s)
end
# Allow templates to access the defined classes
@@ -56,9 +56,8 @@ class Puppet::Parser::TemplateWrapper
# the missing_method definition here until we declare the syntax finally
# dead.
def method_missing(name, *args)
- value = scope.lookupvar(name.to_s,:file => file,:line => script_line)
- if value != :undefined
- return value
+ if scope.include?(name.to_s)
+ return scope[name.to_s, {:file => file,:line => script_line}]
else
# Just throw an error immediately, instead of searching for
# other missingmethod things or whatever.
diff --git a/lib/puppet/parser/type_loader.rb b/lib/puppet/parser/type_loader.rb
index 1fba73d0b..68def068d 100644
--- a/lib/puppet/parser/type_loader.rb
+++ b/lib/puppet/parser/type_loader.rb
@@ -80,7 +80,8 @@ class Puppet::Parser::TypeLoader
loaded_asts = []
files.each do |file|
- unless file =~ /^#{File::SEPARATOR}/
+ regex = Puppet.features.microsoft_windows? ? /^[A-Za-z]:#{File::SEPARATOR}/ : /^#{File::SEPARATOR}/
+ unless file =~ regex
file = File.join(dir, file)
end
@loading_helper.do_once(file) do
diff --git a/lib/puppet/provider/host/parsed.rb b/lib/puppet/provider/host/parsed.rb
index 2ba01a41c..1a2bdb460 100644
--- a/lib/puppet/provider/host/parsed.rb
+++ b/lib/puppet/provider/host/parsed.rb
@@ -3,6 +3,9 @@ require 'puppet/provider/parsedfile'
hosts = nil
case Facter.value(:operatingsystem)
when "Solaris"; hosts = "/etc/inet/hosts"
+when "windows"
+ require 'win32/resolv'
+ hosts = Win32::Resolv.get_hosts_path
else
hosts = "/etc/hosts"
end
diff --git a/lib/puppet/provider/service/windows.rb b/lib/puppet/provider/service/windows.rb
new file mode 100644
index 000000000..f1485f268
--- /dev/null
+++ b/lib/puppet/provider/service/windows.rb
@@ -0,0 +1,110 @@
+# Windows Service Control Manager (SCM) provider
+
+require 'win32/service' if Puppet.features.microsoft_windows?
+
+Puppet::Type.type(:service).provide :windows do
+
+ desc "Support for Windows Service Control Manager (SCM).
+
+ Services are controlled according to win32-service gem.
+
+ * All SCM operations (start/stop/enable/disable/query) are supported.
+
+ * Control of service groups (dependencies) is not yet supported."
+
+ defaultfor :operatingsystem => :windows
+ confine :operatingsystem => :windows
+
+ has_feature :refreshable
+
+ def enable
+ w32ss = Win32::Service.configure( 'service_name' => @resource[:name], 'start_type' => Win32::Service::SERVICE_AUTO_START )
+ raise Puppet::Error.new("Win32 service enable of #{@resource[:name]} failed" ) if( w32ss.nil? )
+ rescue Win32::Service::Error => detail
+ raise Puppet::Error.new("Cannot enable #{@resource[:name]}, error was: #{detail}" )
+ end
+
+ def disable
+ w32ss = Win32::Service.configure( 'service_name' => @resource[:name], 'start_type' => Win32::Service::SERVICE_DISABLED )
+ raise Puppet::Error.new("Win32 service disable of #{@resource[:name]} failed" ) if( w32ss.nil? )
+ rescue Win32::Service::Error => detail
+ raise Puppet::Error.new("Cannot disable #{@resource[:name]}, error was: #{detail}" )
+ end
+
+ def manual_start
+ w32ss = Win32::Service.configure( 'service_name' => @resource[:name], 'start_type' => Win32::Service::SERVICE_DEMAND_START )
+ raise Puppet::Error.new("Win32 service manual enable of #{@resource[:name]} failed" ) if( w32ss.nil? )
+ rescue Win32::Service::Error => detail
+ raise Puppet::Error.new("Cannot enable #{@resource[:name]} for manual start, error was: #{detail}" )
+ end
+
+ def enabled?
+ w32ss = Win32::Service.config_info( @resource[:name] )
+ raise Puppet::Error.new("Win32 service query of #{@resource[:name]} failed" ) unless( !w32ss.nil? && w32ss.instance_of?( Struct::ServiceConfigInfo ) )
+ debug("Service #{@resource[:name]} start type is #{w32ss.start_type}")
+ case w32ss.start_type
+ when Win32::Service.get_start_type(Win32::Service::SERVICE_AUTO_START),
+ Win32::Service.get_start_type(Win32::Service::SERVICE_BOOT_START),
+ Win32::Service.get_start_type(Win32::Service::SERVICE_SYSTEM_START)
+ :true
+ when Win32::Service.get_start_type(Win32::Service::SERVICE_DEMAND_START)
+ :manual
+ when Win32::Service.get_start_type(Win32::Service::SERVICE_DISABLED)
+ :false
+ else
+ raise Puppet::Error.new("Unknown start type: #{w32ss.start_type}")
+ end
+ rescue Win32::Service::Error => detail
+ raise Puppet::Error.new("Cannot get start type for #{@resource[:name]}, error was: #{detail}" )
+ end
+
+ def start
+ if enabled? == :false
+ # If disabled and not managing enable, respect disabled and fail.
+ if @resource[:enable].nil?
+ raise Puppet::Error, "Will not start disabled service #{@resource[:name]} without managing enable. Specify 'enable => false' to override."
+ # Otherwise start. If enable => false, we will later sync enable and
+ # disable the service again.
+ elsif @resource[:enable] == :true
+ enable
+ else
+ manual_start
+ end
+ end
+
+ Win32::Service.start( @resource[:name] )
+ rescue Win32::Service::Error => detail
+ raise Puppet::Error.new("Cannot start #{@resource[:name]}, error was: #{detail}" )
+ end
+
+ def stop
+ Win32::Service.stop( @resource[:name] )
+ rescue Win32::Service::Error => detail
+ raise Puppet::Error.new("Cannot start #{@resource[:name]}, error was: #{detail}" )
+ end
+
+ def restart
+ self.stop
+ self.start
+ end
+
+ def status
+ w32ss = Win32::Service.status( @resource[:name] )
+ raise Puppet::Error.new("Win32 service query of #{@resource[:name]} failed" ) unless( !w32ss.nil? && w32ss.instance_of?( Struct::ServiceStatus ) )
+ state = case w32ss.current_state
+ when "stopped", "pause pending", "stop pending", "paused" then :stopped
+ when "running", "continue pending", "start pending" then :running
+ else
+ raise Puppet::Error.new("Unknown service state '#{w32ss.current_state}' for service '#{@resource[:name]}'")
+ end
+ debug("Service #{@resource[:name]} is #{w32ss.current_state}")
+ return state
+ rescue Win32::Service::Error => detail
+ raise Puppet::Error.new("Cannot get status of #{@resource[:name]}, error was: #{detail}" )
+ end
+
+ # returns all providers for all existing services and startup state
+ def self.instances
+ Win32::Service.services.collect { |s| new(:name => s.service_name) }
+ end
+end
diff --git a/lib/puppet/resource.rb b/lib/puppet/resource.rb
index 59e387d00..217eb11c8 100644
--- a/lib/puppet/resource.rb
+++ b/lib/puppet/resource.rb
@@ -343,6 +343,26 @@ class Puppet::Resource
[ type, title ].join('/')
end
+ def set_default_parameters(scope)
+ return [] unless resource_type and resource_type.respond_to?(:arguments)
+
+ result = []
+
+ resource_type.arguments.each do |param, default|
+ param = param.to_sym
+ next if parameters.include?(param)
+ unless is_a?(Puppet::Parser::Resource)
+ fail Puppet::DevError, "Cannot evaluate default parameters for #{self} - not a parser resource"
+ end
+
+ next if default.nil?
+
+ self[param] = default.safeevaluate(scope)
+ result << param
+ end
+ result
+ end
+
def to_resource
self
end
@@ -351,6 +371,19 @@ class Puppet::Resource
resource_type.valid_parameter?(name)
end
+ # Verify that all required arguments are either present or
+ # have been provided with defaults.
+ # Must be called after 'set_default_parameters'. We can't join the methods
+ # because Type#set_parameters needs specifically ordered behavior.
+ def validate_complete
+ return unless resource_type and resource_type.respond_to?(:arguments)
+
+ resource_type.arguments.each do |param, default|
+ param = param.to_sym
+ fail Puppet::ParseError, "Must pass #{param} to #{self}" unless parameters.include?(param)
+ end
+ end
+
def validate_parameter(name)
raise ArgumentError, "Invalid parameter #{name}" unless valid_parameter?(name)
end
diff --git a/lib/puppet/resource/catalog.rb b/lib/puppet/resource/catalog.rb
index b742d283f..ca9f25a5a 100644
--- a/lib/puppet/resource/catalog.rb
+++ b/lib/puppet/resource/catalog.rb
@@ -3,7 +3,6 @@ require 'puppet/indirector'
require 'puppet/simple_graph'
require 'puppet/transaction'
-require 'puppet/util/cacher'
require 'puppet/util/pson'
require 'puppet/util/tagging'
@@ -20,7 +19,6 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph
include Puppet::Util::Tagging
extend Puppet::Util::Pson
- include Puppet::Util::Cacher::Expirer
# The host name this is a catalog for.
attr_accessor :name
@@ -94,7 +92,7 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph
resource.ref =~ /^(.+)\[/
class_name = $1 || resource.class.name
- newref = [class_name, key]
+ newref = [class_name, key].flatten
if key.is_a? String
ref_string = "#{class_name}[#{key}]"
@@ -107,7 +105,10 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph
# isn't sufficient.
if existing = @resource_table[newref]
return if existing == resource
- raise(ArgumentError, "Cannot alias #{resource.ref} to #{key.inspect}; resource #{newref.inspect} already exists")
+ resource_definition = " at #{resource.file}:#{resource.line}" if resource.file and resource.line
+ existing_definition = " at #{existing.file}:#{existing.line}" if existing.file and existing.line
+ msg = "Cannot alias #{resource.ref} to #{key.inspect}#{resource_definition}; resource #{newref.inspect} already defined#{existing_definition}"
+ raise ArgumentError, msg
end
@resource_table[newref] = resource
@aliases[resource.ref] ||= []
@@ -123,14 +124,11 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph
def apply(options = {})
@applying = true
- # Expire all of the resource data -- this ensures that all
- # data we're operating against is entirely current.
- expire
-
Puppet::Util::Storage.load if host_config?
- transaction = Puppet::Transaction.new(self)
- transaction.report = options[:report] if options[:report]
+ transaction = Puppet::Transaction.new(self, options[:report])
+ register_report = options[:report].nil?
+
transaction.tags = options[:tags] if options[:tags]
transaction.ignoreschedules = true if options[:ignoreschedules]
transaction.for_network_device = options[:network_device]
@@ -138,7 +136,12 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph
transaction.add_times :config_retrieval => self.retrieval_duration || 0
begin
- transaction.evaluate
+ Puppet::Util::Log.newdestination(transaction.report) if register_report
+ begin
+ transaction.evaluate
+ ensure
+ Puppet::Util::Log.close(transaction.report) if register_report
+ end
rescue Puppet::Error => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Could not apply complete catalog: #{detail}"
@@ -156,7 +159,6 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph
return transaction
ensure
@applying = false
- cleanup
end
# Are we in the middle of applying the catalog?
@@ -191,14 +193,6 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph
resource
end
- def dependent_data_expired?(ts)
- if applying?
- return super
- else
- return true
- end
- end
-
# Turn our catalog graph into an old-style tree of TransObjects and TransBuckets.
# LAK:NOTE(20081211): This is a pre-0.25 backward compatibility method.
# It can be removed as soon as xmlrpc is killed.
@@ -430,7 +424,7 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph
res = Puppet::Resource.new(nil, type)
end
title_key = [res.type, res.title.to_s]
- uniqueness_key = [res.type, res.uniqueness_key]
+ uniqueness_key = [res.type, res.uniqueness_key].flatten
@resource_table[title_key] || @resource_table[uniqueness_key]
end
@@ -558,11 +552,6 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph
private
- def cleanup
- # Expire any cached data the resources are keeping.
- expire
- end
-
# Verify that the given resource isn't defined elsewhere.
def fail_on_duplicate_type_and_title(resource)
# Short-curcuit the common case,
diff --git a/lib/puppet/resource/type.rb b/lib/puppet/resource/type.rb
index f8d820b77..8b154ce95 100644
--- a/lib/puppet/resource/type.rb
+++ b/lib/puppet/resource/type.rb
@@ -158,11 +158,7 @@ class Puppet::Resource::Type
return resource
end
resource = Puppet::Parser::Resource.new(resource_type, name, :scope => scope, :source => self)
- if parameters
- parameters.each do |k,v|
- resource.set_parameter(k,v)
- end
- end
+ assign_parameter_values(parameters, resource)
instantiate_resource(scope, resource)
scope.compiler.add_resource(scope, resource)
resource
@@ -188,6 +184,18 @@ class Puppet::Resource::Type
@name.is_a?(Regexp)
end
+ def assign_parameter_values(parameters, resource)
+ return unless parameters
+ scope = resource.scope || {}
+
+ # It'd be nice to assign default parameter values here,
+ # but we can't because they often rely on local variables
+ # created during set_resource_parameters.
+ parameters.each do |name, value|
+ resource.set_parameter name, value
+ end
+ end
+
# MQR TODO:
#
# The change(s) introduced by the fix for #4270 are mostly silly & should be
@@ -225,40 +233,32 @@ class Puppet::Resource::Type
param = param.to_sym
fail Puppet::ParseError, "#{resource.ref} does not accept attribute #{param}" unless valid_parameter?(param)
- exceptwrap { scope.setvar(param.to_s, value) }
+ exceptwrap { scope[param.to_s] = value }
set[param] = true
end
if @type == :hostclass
- scope.setvar("title", resource.title.to_s.downcase) unless set.include? :title
- scope.setvar("name", resource.name.to_s.downcase ) unless set.include? :name
+ scope["title"] = resource.title.to_s.downcase unless set.include? :title
+ scope["name"] = resource.name.to_s.downcase unless set.include? :name
else
- scope.setvar("title", resource.title ) unless set.include? :title
- scope.setvar("name", resource.name ) unless set.include? :name
+ scope["title"] = resource.title unless set.include? :title
+ scope["name"] = resource.name unless set.include? :name
end
- scope.setvar("module_name", module_name) if module_name and ! set.include? :module_name
+ scope["module_name"] = module_name if module_name and ! set.include? :module_name
if caller_name = scope.parent_module_name and ! set.include?(:caller_module_name)
- scope.setvar("caller_module_name", caller_name)
+ scope["caller_module_name"] = caller_name
end
scope.class_set(self.name,scope) if hostclass? or node?
- # Verify that all required arguments are either present or
- # have been provided with defaults.
- arguments.each do |param, default|
- param = param.to_sym
- next if set.include?(param)
-
- # Even if 'default' is a false value, it's an AST value, so this works fine
- fail Puppet::ParseError, "Must pass #{param} to #{resource.ref}" unless default
- value = default.safeevaluate(scope)
- scope.setvar(param.to_s, value)
-
- # Set it in the resource, too, so the value makes it to the client.
- resource[param] = value
- end
+ # Evaluate the default parameters, now that all other variables are set
+ default_params = resource.set_default_parameters(scope)
+ default_params.each { |param| scope[param.to_s] = resource[param] }
+ # This has to come after the above parameters so that default values
+ # can use their values
+ resource.validate_complete
end
# Check whether a given argument is valid.
diff --git a/lib/puppet/ssl/certificate.rb b/lib/puppet/ssl/certificate.rb
index a0e600291..d57ac1a06 100644
--- a/lib/puppet/ssl/certificate.rb
+++ b/lib/puppet/ssl/certificate.rb
@@ -27,6 +27,12 @@ class Puppet::SSL::Certificate < Puppet::SSL::Base
[:s]
end
+ def alternate_names
+ alts = content.extensions.find{|ext| ext.oid == "subjectAltName"}
+ return [] unless alts
+ alts.value.split(/,\s+/).map{|al| al.sub(/^DNS:/,'')}
+ end
+
def expiration
return nil unless content
content.not_after
diff --git a/lib/puppet/ssl/certificate_authority.rb b/lib/puppet/ssl/certificate_authority.rb
index d65067c70..a4cbaf78a 100644
--- a/lib/puppet/ssl/certificate_authority.rb
+++ b/lib/puppet/ssl/certificate_authority.rb
@@ -1,6 +1,6 @@
+require 'monitor'
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
@@ -17,6 +17,8 @@ class Puppet::SSL::CertificateAuthority
require 'puppet/ssl/certificate_authority/interface'
require 'puppet/network/authstore'
+ extend MonitorMixin
+
class CertificateVerificationError < RuntimeError
attr_accessor :error_code
@@ -25,10 +27,10 @@ class Puppet::SSL::CertificateAuthority
end
end
- class << self
- include Puppet::Util::Cacher
-
- cached_attr(:singleton_instance) { new }
+ def self.singleton_instance
+ synchronize do
+ @singleton_instance ||= new
+ end
end
def self.ca?
diff --git a/lib/puppet/ssl/host.rb b/lib/puppet/ssl/host.rb
index b9215effd..a06b1e275 100644
--- a/lib/puppet/ssl/host.rb
+++ b/lib/puppet/ssl/host.rb
@@ -4,7 +4,6 @@ require 'puppet/ssl/key'
require 'puppet/ssl/certificate'
require 'puppet/ssl/certificate_request'
require 'puppet/ssl/certificate_revocation_list'
-require 'puppet/util/cacher'
# The class that manages all aspects of our SSL certificates --
# private keys, public keys, requests, etc.
@@ -27,15 +26,12 @@ class Puppet::SSL::Host
# This accessor is used in instances for indirector requests to hold desired state
attr_accessor :desired_state
- class << self
- include Puppet::Util::Cacher
-
- cached_attr(:localhost) do
- result = new
- result.generate unless result.certificate
- result.key # Make sure it's read in
- result
- end
+ def self.localhost
+ return @localhost if @localhost
+ @localhost = new
+ @localhost.generate unless @localhost.certificate
+ @localhost.key
+ @localhost
end
# This is the constant that people will use to mark that a given host is
diff --git a/lib/puppet/ssl/inventory.rb b/lib/puppet/ssl/inventory.rb
index e094da100..c210fdc35 100644
--- a/lib/puppet/ssl/inventory.rb
+++ b/lib/puppet/ssl/inventory.rb
@@ -48,5 +48,7 @@ class Puppet::SSL::Inventory
return Integer($1)
end
+
+ return nil
end
end
diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb
index 089f4d945..3152d768d 100644
--- a/lib/puppet/transaction.rb
+++ b/lib/puppet/transaction.rb
@@ -16,7 +16,7 @@ class Puppet::Transaction
attr_accessor :configurator
# The report, once generated.
- attr_accessor :report
+ attr_reader :report
# Routes and stores any events and subscriptions.
attr_reader :event_manager
@@ -92,25 +92,17 @@ class Puppet::Transaction
# collects all of the changes, executes them, and responds to any
# necessary events.
def evaluate
- # Start logging.
- Puppet::Util::Log.newdestination(@report)
-
prepare
Puppet.info "Applying configuration version '#{catalog.version}'" if catalog.version
- begin
- relationship_graph.traverse do |resource|
- if resource.is_a?(Puppet::Type::Component)
- Puppet.warning "Somehow left a component in the relationship graph"
- else
- seconds = thinmark { eval_resource(resource) }
- resource.info "Evaluated in %0.2f seconds" % seconds if Puppet[:evaltrace] and @catalog.host_config?
- end
+ relationship_graph.traverse do |resource|
+ if resource.is_a?(Puppet::Type::Component)
+ Puppet.warning "Somehow left a component in the relationship graph"
+ else
+ seconds = thinmark { eval_resource(resource) }
+ resource.info "Evaluated in %0.2f seconds" % seconds if Puppet[:evaltrace] and @catalog.host_config?
end
- ensure
- # And then close the transaction log.
- Puppet::Util::Log.close(@report)
end
Puppet.debug "Finishing transaction #{object_id}"
@@ -221,10 +213,10 @@ class Puppet::Transaction
# this should only be called by a Puppet::Type::Component resource now
# and it should only receive an array
- def initialize(catalog)
+ def initialize(catalog, report = nil)
@catalog = catalog
- @report = Puppet::Transaction::Report.new("apply")
+ @report = report || Puppet::Transaction::Report.new("apply", catalog.version)
@event_manager = Puppet::Transaction::EventManager.new(self)
diff --git a/lib/puppet/transaction/report.rb b/lib/puppet/transaction/report.rb
index 020a5efce..807163961 100644
--- a/lib/puppet/transaction/report.rb
+++ b/lib/puppet/transaction/report.rb
@@ -10,8 +10,8 @@ class Puppet::Transaction::Report
indirects :report, :terminus_class => :processor
- attr_accessor :configuration_version
- attr_reader :resource_statuses, :logs, :metrics, :host, :time, :kind, :status
+ attr_accessor :configuration_version, :host
+ attr_reader :resource_statuses, :logs, :metrics, :time, :kind, :status
# This is necessary since Marshall doesn't know how to
# dump hash with default proc (see below @records)
diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb
index 15f340f55..963b925bf 100644
--- a/lib/puppet/type.rb
+++ b/lib/puppet/type.rb
@@ -9,7 +9,6 @@ require 'puppet/metatype/manager'
require 'puppet/util/errors'
require 'puppet/util/log_paths'
require 'puppet/util/logging'
-require 'puppet/util/cacher'
require 'puppet/file_collection/lookup'
require 'puppet/util/tagging'
@@ -21,7 +20,6 @@ class Type
include Puppet::Util::Errors
include Puppet::Util::LogPaths
include Puppet::Util::Logging
- include Puppet::Util::Cacher
include Puppet::FileCollection::Lookup
include Puppet::Util::Tagging
@@ -469,12 +467,6 @@ class Type
Puppet::Transaction::Event.new({:resource => self, :file => file, :line => line, :tags => tags}.merge(options))
end
- # Let the catalog determine whether a given cached value is
- # still valid or has expired.
- def expirer
- catalog
- end
-
# retrieve the 'should' value for a specified property
def should(name)
name = attr_alias(name)
diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb
index 72e9a9495..07409a108 100644
--- a/lib/puppet/type/file.rb
+++ b/lib/puppet/type/file.rb
@@ -23,7 +23,7 @@ Puppet::Type.newtype(:file) do
location, rather than using native resources, please contact
Puppet Labs and we can hopefully work with you to develop a
native resource to support what you are doing.
-
+
**Autorequires:** If Puppet is managing the user or group that owns a file, the file resource will autorequire them. If Puppet is managing any parent directories of a file, the file resource will autorequire them."
def self.title_patterns
@@ -36,7 +36,7 @@ Puppet::Type.newtype(:file) do
validate do |value|
# accept various path syntaxes: lone slash, posix, win32, unc
- unless (Puppet.features.posix? and value =~ /^\//) or (Puppet.features.microsoft_windows? and (value =~ /^.:\// or value =~ /^\/\/[^\/]+\/[^\/]+/))
+ unless (Puppet.features.posix? and value =~ /^\//) or (value =~ /^[A-Za-z]:\// or value =~ /^\/\/[^\/]+\/[^\/]+/)
fail Puppet::Error, "File paths must be fully qualified, not '#{value}'"
end
end
@@ -44,7 +44,21 @@ Puppet::Type.newtype(:file) do
# convert the current path in an index into the collection and the last
# path name. The aim is to use less storage for all common paths in a hierarchy
munge do |value|
- path, name = ::File.split(value.gsub(/\/+/,'/'))
+ # We need to save off, and remove the volume designator in the
+ # path if it is there, since File.split does not handle paths
+ # with volume designators properly, except when run on Windows.
+ # Since we are potentially compiling a catalog for a Windows
+ # machine on a non-Windows master, we need to handle this
+ # ourselves.
+ optional_volume_designator = value.match(/^([a-z]:)[\/\\].*/i)
+ value_without_designator = value.sub(/^(?:[a-z]:)?(.*)/i, '\1')
+
+ path, name = ::File.split(value_without_designator.gsub(/\/+/,'/'))
+
+ if optional_volume_designator
+ path = optional_volume_designator[1] + path
+ end
+
{ :index => Puppet::FileCollection.collection.index(path), :name => name }
end
@@ -394,7 +408,7 @@ Puppet::Type.newtype(:file) do
@parameters.each do |name, param|
param.flush if param.respond_to?(:flush)
end
- @stat = nil
+ @stat = :needs_stat
end
def initialize(hash)
@@ -413,7 +427,7 @@ Puppet::Type.newtype(:file) do
end
end
- @stat = nil
+ @stat = :needs_stat
end
# Configure discovered resources to be purged.
@@ -623,7 +637,7 @@ Puppet::Type.newtype(:file) do
else
self.fail "Could not back up files of type #{s.ftype}"
end
- expire
+ @stat = :needs_stat
end
def retrieve
@@ -674,22 +688,27 @@ Puppet::Type.newtype(:file) do
# use either 'stat' or 'lstat', and we expect the properties to use the
# resulting stat object accordingly (mostly by testing the 'ftype'
# value).
- cached_attr(:stat) do
+ #
+ # We use the initial value :needs_stat to ensure we only stat the file once,
+ # but can also keep track of a failed stat (@stat == nil). This also allows
+ # us to re-stat on demand by setting @stat = :needs_stat.
+ def stat
+ return @stat unless @stat == :needs_stat
+
method = :stat
# Files are the only types that support links
if (self.class.name == :file and self[:links] != :follow) or self.class.name == :tidy
method = :lstat
end
- path = self[:path]
- begin
+ @stat = begin
::File.send(method, self[:path])
rescue Errno::ENOENT => error
- return nil
+ nil
rescue Errno::EACCES => error
warning "Could not stat; permission denied"
- return nil
+ nil
end
end
@@ -776,7 +795,7 @@ Puppet::Type.newtype(:file) do
next unless [:mode, :owner, :group, :seluser, :selrole, :seltype, :selrange].include?(thing.name)
# Make sure we get a new stat objct
- expire
+ @stat = :needs_stat
currentvalue = thing.retrieve
thing.sync unless thing.safe_insync?(currentvalue)
end
diff --git a/lib/puppet/type/file/source.rb b/lib/puppet/type/file/source.rb
index 76c646baf..8653a8f7a 100755
--- a/lib/puppet/type/file/source.rb
+++ b/lib/puppet/type/file/source.rb
@@ -42,7 +42,7 @@ module Puppet
on the local host, whereas `agent` will connect to the
puppet server that it received the manifest from.
- See the [fileserver configuration documentation](http://projects.puppetlabs.com/projects/puppet/wiki/File_Serving_Configuration) for information on how to configure
+ See the [fileserver configuration documentation](http://docs.puppetlabs.com/guides/file_serving.html) for information on how to configure
and use file services within Puppet.
If you specify multiple file sources for a file, then the first
@@ -72,7 +72,7 @@ module Puppet
self.fail "Could not understand source #{source}: #{detail}"
end
- self.fail "Cannot use URLs of type '#{uri.scheme}' as source for fileserving" unless uri.scheme.nil? or %w{file puppet}.include?(uri.scheme)
+ self.fail "Cannot use URLs of type '#{uri.scheme}' as source for fileserving" unless uri.scheme.nil? or %w{file puppet}.include?(uri.scheme) or (Puppet.features.microsoft_windows? and uri.scheme =~ /^[a-z]$/i)
end
end
@@ -95,13 +95,14 @@ module Puppet
end
# Look up (if necessary) and return remote content.
- cached_attr(:content) do
+ def content
+ return @content if @content
raise Puppet::DevError, "No source for content was stored with the metadata" unless metadata.source
unless tmp = Puppet::FileServing::Content.indirection.find(metadata.source)
fail "Could not find any content at %s" % metadata.source
end
- tmp.content
+ @content = tmp.content
end
# Copy the values from the source to the resource. Yay.
@@ -137,25 +138,27 @@ module Puppet
! (metadata.nil? or metadata.ftype.nil?)
end
+ attr_writer :metadata
+
# Provide, and retrieve if necessary, the metadata for this file. Fail
# if we can't find data about this host, and fail if there are any
# problems in our query.
- cached_attr(:metadata) do
+ def metadata
+ return @metadata if @metadata
return nil unless value
- result = nil
value.each do |source|
begin
if data = Puppet::FileServing::Metadata.indirection.find(source)
- result = data
- result.source = source
+ @metadata = data
+ @metadata.source = source
break
end
rescue => detail
fail detail, "Could not retrieve file metadata for #{source}: #{detail}"
end
end
- fail "Could not retrieve information from source(s) #{value.join(", ")}" unless result
- result
+ fail "Could not retrieve information from environment #{Puppet[:environment]} source(s) #{value.join(", ")}" unless @metadata
+ @metadata
end
def local?
@@ -177,6 +180,8 @@ module Puppet
private
def uri
+ return nil if metadata.source =~ /^[a-z]:[\/\\]/i # Abspath for Windows
+
@uri ||= URI.parse(URI.escape(metadata.source))
end
end
diff --git a/lib/puppet/type/service.rb b/lib/puppet/type/service.rb
index 5a2c69b87..eaf2b8ee1 100644
--- a/lib/puppet/type/service.rb
+++ b/lib/puppet/type/service.rb
@@ -8,17 +8,15 @@ module Puppet
newtype(:service) do
@doc = "Manage running services. Service support unfortunately varies
- widely by platform --- some platforms have very little if any
- concept of a running service, and some have a very codified and
- powerful concept. Puppet's service support will generally be able
- to do the right thing regardless (e.g., if there is no
- 'status' command, then Puppet will look in the process table for a
- command matching the service name), but the more information you
- can provide, the better behaviour you will get. In particular, any
- virtual services that don't have a predictable entry in the process table
- (for example, `network` on Red Hat/CentOS systems) will manifest odd
- behavior on restarts if you don't specify `hasstatus` or a `status`
- command.
+ widely by platform --- some platforms have very little if any concept of a
+ running service, and some have a very codified and powerful concept.
+ Puppet's service support is usually capable of doing the right thing, but
+ the more information you can provide, the better behaviour you will get.
+
+ Puppet 2.7 and newer expect init scripts to have a working status command.
+ If this isn't the case for any of your services' init scripts, you will
+ need to set `hasstatus` to false and possibly specify a custom status
+ command in the `status` attribute.
Note that if a `service` receives an event from another resource,
the service will get restarted. The actual command to restart the
@@ -49,9 +47,19 @@ module Puppet
provider.disable
end
+ newvalue(:manual, :event => :service_manual_start) do
+ provider.manual_start
+ end
+
def retrieve
provider.enabled?
end
+
+ validate do |value|
+ if value == :manual and !Puppet.features.microsoft_windows?
+ raise Puppet::Error.new("Setting enable to manual is only supported on Microsoft Windows.")
+ end
+ end
end
# Handle whether the service should actually be running right now.
@@ -93,19 +101,17 @@ module Puppet
end
newparam(:hasstatus) do
- desc "Declare the the service's init script has a
- functional status command. Based on testing, it was found
- that a large number of init scripts on different platforms do
- not support any kind of status command; thus, you must specify
- manually whether the service you are running has such a
- command. Alternately, you can provide a specific command using the
- `status` attribute.
-
- If you specify neither of these, then Puppet will look for the
- service name in the process table. Be aware that 'virtual' init
- scripts such as networking will respond poorly to refresh events
- (via notify and subscribe relationships) if you don't override
- this default behavior."
+ desc "Declare whether the service's init script has a functional status
+ command; defaults to `true`. This attribute's default value changed in
+ Puppet 2.7.0.
+
+ If a service's init script does not support any kind of status command,
+ you should set `hasstatus` to false and either provide a specific
+ command using the `status` attribute or expect that Puppet will look for
+ the service name in the process table. Be aware that 'virtual' init
+ scripts (like 'network' under Red Hat systems) will respond poorly to
+ refresh events from other resources if you override the default behavior
+ without providing a status command."
newvalues(:true, :false)
diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb
index 572d5796d..c64bf69e8 100755
--- a/lib/puppet/type/user.rb
+++ b/lib/puppet/type/user.rb
@@ -168,6 +168,14 @@ module Puppet
return "changed password"
end
end
+
+ def is_to_s( currentvalue )
+ return '[old password hash redacted]'
+ end
+ def should_to_s( newvalue )
+ return '[new password hash redacted]'
+ end
+
end
newproperty(:password_min_age, :required_features => :manages_password_age) do
diff --git a/lib/puppet/util.rb b/lib/puppet/util.rb
index ce9d4642b..ff09221a2 100644
--- a/lib/puppet/util.rb
+++ b/lib/puppet/util.rb
@@ -30,7 +30,6 @@ module Util
end
end
-
def self.synchronize_on(x,type)
sync_object,users = 0,1
begin
diff --git a/lib/puppet/util/autoload.rb b/lib/puppet/util/autoload.rb
index 6537a4a4e..2e8710ab1 100644
--- a/lib/puppet/util/autoload.rb
+++ b/lib/puppet/util/autoload.rb
@@ -1,5 +1,4 @@
require 'puppet/util/warnings'
-require 'puppet/util/cacher'
# Autoload paths, either based on names or all at once.
class Puppet::Util::Autoload
@@ -7,7 +6,6 @@ class Puppet::Util::Autoload
include Puppet::Util
include Puppet::Util::Warnings
- include Puppet::Util::Cacher
include Puppet::Util::Autoload::FileCache
@autoloaders = {}
diff --git a/lib/puppet/util/cacher.rb b/lib/puppet/util/cacher.rb
index 3dddec0d4..136c9973e 100644
--- a/lib/puppet/util/cacher.rb
+++ b/lib/puppet/util/cacher.rb
@@ -1,25 +1,6 @@
require 'monitor'
module Puppet::Util::Cacher
- module Expirer
- attr_reader :timestamp
-
- # Cause all cached values to be considered expired.
- def expire
- @timestamp = Time.now
- end
-
- # Is the provided timestamp earlier than our expiration timestamp?
- # If it is, then the associated value is expired.
- def dependent_data_expired?(ts)
- return false unless timestamp
-
- timestamp > ts
- end
- end
-
- extend Expirer
-
# Our module has been extended in a class; we can only add the Instance methods,
# which become *class* methods in the class.
def self.extended(other)
@@ -40,27 +21,26 @@ module Puppet::Util::Cacher
module ClassMethods
# Provide a means of defining an attribute whose value will be cached.
# Must provide a block capable of defining the value if it's flushed..
- def cached_attr(name, options = {}, &block)
+ def cached_attr(name, ttl, &block)
init_method = "init_#{name}"
define_method(init_method, &block)
+ set_attr_ttl(name, ttl)
+
define_method(name) do
cached_value(name)
end
define_method(name.to_s + "=") do |value|
# Make sure the cache timestamp is set
- cache_timestamp
- value_cache.synchronize { value_cache[name] = value }
- end
-
- if ttl = options[:ttl]
- set_attr_ttl(name, ttl)
+ value_cache.synchronize do
+ value_cache[name] = value
+ set_expiration(name)
+ end
end
end
def attr_ttl(name)
- return nil unless @attr_ttls
@attr_ttls[name]
end
@@ -72,57 +52,25 @@ module Puppet::Util::Cacher
# Methods that get added to instances.
module InstanceMethods
-
- def expire
- # Only expire if we have an expirer. This is
- # mostly so that we can comfortably handle cases
- # like Puppet::Type instances, which use their
- # catalog as their expirer, and they often don't
- # have a catalog.
- if e = expirer
- e.expire
- end
- end
-
- def expirer
- Puppet::Util::Cacher
- end
-
private
- def cache_timestamp
- @cache_timestamp ||= Time.now
- end
-
def cached_value(name)
value_cache.synchronize do
- # Allow a nil expirer, in which case we regenerate the value every time.
- if expired_by_expirer?(name)
- value_cache.clear
- @cache_timestamp = Time.now
- elsif expired_by_ttl?(name)
- value_cache.delete(name)
+ if value_cache[name].nil? or expired_by_ttl?(name)
+ value_cache[name] = send("init_#{name}")
+ set_expiration(name)
end
- value_cache[name] = send("init_#{name}") unless value_cache.include?(name)
value_cache[name]
end
end
- def expired_by_expirer?(name)
- if expirer.nil?
- return true unless self.class.attr_ttl(name)
- end
- expirer.dependent_data_expired?(cache_timestamp)
- end
-
def expired_by_ttl?(name)
- return false unless self.class.respond_to?(:attr_ttl)
- return false unless ttl = self.class.attr_ttl(name)
-
- @ttl_timestamps ||= {}
- @ttl_timestamps[name] ||= Time.now
+ @attr_expirations[name] < Time.now
+ end
- (Time.now - @ttl_timestamps[name]) > ttl
+ def set_expiration(name)
+ @attr_expirations ||= {}
+ @attr_expirations[name] = Time.now + self.class.attr_ttl(name)
end
def value_cache
diff --git a/lib/puppet/util/rdoc/parser.rb b/lib/puppet/util/rdoc/parser.rb
index 762ce25f0..a8996ee9a 100644
--- a/lib/puppet/util/rdoc/parser.rb
+++ b/lib/puppet/util/rdoc/parser.rb
@@ -113,7 +113,9 @@ class Parser
Puppet::Module.modulepath.each do |mp|
# check that fullpath is a descendant of mp
dirname = fullpath
- while (dirname = File.dirname(dirname)) != '/'
+ previous = dirname
+ while (dirname = File.dirname(previous)) != previous
+ previous = dirname
return nil if File.identical?(dirname,mp)
end
end
diff --git a/lib/puppet/util/run_mode.rb b/lib/puppet/util/run_mode.rb
index 450cbf1a6..6028aef29 100644
--- a/lib/puppet/util/run_mode.rb
+++ b/lib/puppet/util/run_mode.rb
@@ -27,14 +27,14 @@ module Puppet
def conf_dir
which_dir(
- (Puppet.features.microsoft_windows? ? File.join(Dir::WINDOWS, "puppet", "etc") : "/etc/puppet"),
+ (Puppet.features.microsoft_windows? ? File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "etc") : "/etc/puppet"),
"~/.puppet"
)
end
def var_dir
which_dir(
- (Puppet.features.microsoft_windows? ? File.join(Dir::WINDOWS, "puppet", "var") : "/var/lib/puppet"),
+ (Puppet.features.microsoft_windows? ? File.join(Dir::COMMON_APPDATA, "PuppetLabs", "puppet", "var") : "/var/lib/puppet"),
"~/.puppet/var"
)
end
diff --git a/lib/puppet/util/settings.rb b/lib/puppet/util/settings.rb
index f243b8691..caaf61b7b 100644
--- a/lib/puppet/util/settings.rb
+++ b/lib/puppet/util/settings.rb
@@ -2,13 +2,11 @@ require 'puppet'
require 'sync'
require 'getoptlong'
require 'puppet/external/event-loop'
-require 'puppet/util/cacher'
require 'puppet/util/loadedfile'
# The class for handling configuration files.
class Puppet::Util::Settings
include Enumerable
- include Puppet::Util::Cacher
require 'puppet/util/settings/setting'
require 'puppet/util/settings/file_setting'
@@ -401,11 +399,10 @@ class Puppet::Util::Settings
}
end
- # Cache this in an easily clearable way, since we were
- # having trouble cleaning it up after tests.
- cached_attr(:file) do
+ def file
+ return @file if @file
if path = self[:config] and FileTest.exist?(path)
- Puppet::Util::LoadedFile.new(path)
+ @file = Puppet::Util::LoadedFile.new(path)
end
end
@@ -721,7 +718,7 @@ if @config.include?(:run_mode)
end
Puppet::Util::SUIDManager.asuser(*chown) do
- mode = obj.mode || 0640
+ mode = obj.mode ? obj.mode.to_i : 0640
args << "w" if args.empty?
args << mode
diff --git a/lib/semver.rb b/lib/semver.rb
new file mode 100644
index 000000000..ef9435abd
--- /dev/null
+++ b/lib/semver.rb
@@ -0,0 +1,65 @@
+class SemVer
+ VERSION = /^v?(\d+)\.(\d+)\.(\d+)([A-Za-z][0-9A-Za-z-]*|)$/
+ SIMPLE_RANGE = /^v?(\d+|[xX])(?:\.(\d+|[xX])(?:\.(\d+|[xX]))?)?$/
+
+ include Comparable
+
+ def self.valid?(ver)
+ VERSION =~ ver
+ end
+
+ def self.find_matching(pattern, versions)
+ versions.select { |v| v.matched_by?("#{pattern}") }.sort.last
+ end
+
+ attr_reader :major, :minor, :tiny, :special
+
+ def initialize(ver)
+ unless SemVer.valid?(ver)
+ raise ArgumentError.new("Invalid version string '#{ver}'!")
+ end
+
+ @major, @minor, @tiny, @special = VERSION.match(ver).captures.map do |x|
+ # Because Kernel#Integer tries to interpret hex and octal strings, which
+ # we specifically do not want, and which cannot be overridden in 1.8.7.
+ Float(x).to_i rescue x
+ end
+ end
+
+ def <=>(other)
+ other = SemVer.new("#{other}") unless other.is_a? SemVer
+ return self.major <=> other.major unless self.major == other.major
+ return self.minor <=> other.minor unless self.minor == other.minor
+ return self.tiny <=> other.tiny unless self.tiny == other.tiny
+
+ return 0 if self.special == other.special
+ return 1 if self.special == ''
+ return -1 if other.special == ''
+
+ return self.special <=> other.special
+ end
+
+ def matched_by?(pattern)
+ # For the time being, this is restricted to exact version matches and
+ # simple range patterns. In the future, we should implement some or all of
+ # the comparison operators here:
+ # https://github.com/isaacs/node-semver/blob/d474801/semver.js#L340
+
+ case pattern
+ when SIMPLE_RANGE
+ pattern = SIMPLE_RANGE.match(pattern).captures
+ pattern[1] = @minor unless pattern[1] && pattern[1] !~ /x/i
+ pattern[2] = @tiny unless pattern[2] && pattern[2] !~ /x/i
+ [@major, @minor, @tiny] == pattern.map { |x| x.to_i }
+ when VERSION
+ self == SemVer.new(pattern)
+ else
+ false
+ end
+ end
+
+ def inspect
+ "v#{@major}.#{@minor}.#{@tiny}#{@special}"
+ end
+ alias :to_s :inspect
+end