diff options
| author | Dominic Maraglia <dominic@puppetlabs.com> | 2011-08-03 15:43:20 -0700 |
|---|---|---|
| committer | Dominic Maraglia <dominic@puppetlabs.com> | 2011-08-03 15:43:20 -0700 |
| commit | e45f08bf1fef8842554ca0d8cb6fb13711e888e7 (patch) | |
| tree | 9bb476a0fe381d53060abc1f1c240cf1291b2ee4 /lib | |
| parent | a97c86e7d01384aa06f5d4d69da427fc355aebe7 (diff) | |
| parent | c833fde370d43023f52c8f2e11fd77e720d0f577 (diff) | |
| download | puppet-e45f08bf1fef8842554ca0d8cb6fb13711e888e7.tar.gz puppet-e45f08bf1fef8842554ca0d8cb6fb13711e888e7.tar.xz puppet-e45f08bf1fef8842554ca0d8cb6fb13711e888e7.zip | |
Merge branch 'master' of github.com:puppetlabs/puppet
Diffstat (limited to 'lib')
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 |
